Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion code/addons/docs/src/blocks/blocks/Controls.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { PlayFunctionContext } from 'storybook/internal/csf';

import type { Meta, StoryObj } from '@storybook/react-vite';

import { within } from 'storybook/test';
import { expect, within } from 'storybook/test';

import * as ExampleStories from '../examples/ControlsParameters.stories';
import * as SubcomponentsExampleStories from '../examples/ControlsWithSubcomponentsParameters.stories';
Expand Down Expand Up @@ -159,3 +159,25 @@ export const EmptyArgTypes: Story = {
of: EmptyArgTypesStories.Default,
},
};

/**
* When multiple Controls blocks for different stories are on the same docs page, each control
* should have a unique id attribute (scoped by storyId). This verifies the fix for
* https://github.com/storybookjs/storybook/issues/26144
*/
export const MultipleControlsOnSamePage: Story = {
render: () => (
<>
<Controls of={ExampleStories.NoParameters} />
<Controls of={ExampleStories.Include} />
</>
),
play: async ({ canvasElement }) => {
const allIds = Array.from(canvasElement.querySelectorAll('[id^="control-"]')).map(
(el) => el.id
);
const uniqueIds = new Set(allIds);
await expect(allIds.length).toBeGreaterThan(0);
await expect(uniqueIds.size).toBe(allIds.length);
},
};
2 changes: 2 additions & 0 deletions code/addons/docs/src/blocks/blocks/Controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const Controls: FC<ControlsProps> = (props) => {
}
return (
<PureArgsTable
storyId={story.id}
rows={filteredArgTypes as any}
sort={sort}
args={args}
Expand Down Expand Up @@ -101,6 +102,7 @@ export const Controls: FC<ControlsProps> = (props) => {
globals={globals}
updateArgs={updateArgs}
resetArgs={resetArgs}
storyId={story.id}
/>
);
};
13 changes: 11 additions & 2 deletions code/addons/docs/src/blocks/components/ArgsTable/ArgControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface ArgControlProps {
arg: any;
updateArgs: (args: Args) => void;
isHovered: boolean;
storyId?: string;
}

const Controls: Record<string, FC<any>> = {
Expand All @@ -43,7 +44,7 @@ const Controls: Record<string, FC<any>> = {

const NoControl = () => <>-</>;

export const ArgControl: FC<ArgControlProps> = ({ row, arg, updateArgs, isHovered }) => {
export const ArgControl: FC<ArgControlProps> = ({ row, arg, updateArgs, isHovered, storyId }) => {
const { key, control } = row;

const [isFocused, setFocused] = useState(false);
Expand Down Expand Up @@ -84,7 +85,15 @@ export const ArgControl: FC<ArgControlProps> = ({ row, arg, updateArgs, isHovere
}
// row.name is a display name and not a suitable DOM input id or name - i might contain whitespace etc.
// row.key is a hash key and therefore a much safer choice
const props = { name: key, argType: row, value: boxedValue.value, onChange, onBlur, onFocus };
const props = {
name: key,
storyId,
argType: row,
value: boxedValue.value,
onChange,
onBlur,
onFocus,
};
const Control = Controls[control.type] || NoControl;
return <Control {...props} {...control} controlType={control.type} />;
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ interface ArgRowProps {
compact?: boolean;
expandable?: boolean;
initialExpandedArgs?: boolean;
storyId?: string;
}

const Name = styled.span({ fontWeight: 'bold' });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ export interface ArgsTableOptionProps {
initialExpandedArgs?: boolean;
isLoading?: boolean;
sort?: SortType;
storyId?: string;
}
interface ArgsTableDataProps {
rows: ArgTypes;
Expand Down Expand Up @@ -327,6 +328,7 @@ export const ArgsTable: FC<ArgsTableProps> = (props) => {
initialExpandedArgs,
sort = 'none',
isLoading,
storyId,
} = props;

if ('error' in props) {
Expand Down Expand Up @@ -380,7 +382,7 @@ export const ArgsTable: FC<ArgsTableProps> = (props) => {
}
const expandable = Object.keys(groups.sections).length > 0;

const common = { updateArgs, compact, inAddonPanel, initialExpandedArgs };
const common = { updateArgs, compact, inAddonPanel, initialExpandedArgs, storyId };

return (
<ResetWrapper>
Expand Down
5 changes: 3 additions & 2 deletions code/addons/docs/src/blocks/controls/Boolean.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export type BooleanProps = ControlProps<BooleanValue> & BooleanConfig;
*/
export const BooleanControl: FC<BooleanProps> = ({
name,
storyId,
value,
onChange,
onBlur,
Expand All @@ -130,15 +131,15 @@ export const BooleanControl: FC<BooleanProps> = ({
ariaLabel={false}
variant="outline"
size="medium"
id={getControlSetterButtonId(name)}
id={getControlSetterButtonId(name, storyId)}
onClick={onSetFalse}
disabled={readonly}
>
Set boolean
</Button>
);
}
const controlId = getControlId(name);
const controlId = getControlId(name, storyId);

const parsedValue = typeof value === 'string' ? parse(value) : value;

Expand Down
3 changes: 2 additions & 1 deletion code/addons/docs/src/blocks/controls/Color.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ const usePresets = (
export type ColorControlProps = ControlProps<ColorValue> & ColorConfig;
export const ColorControl: FC<ColorControlProps> = ({
name,
storyId,
value: initialValue,
onChange,
onFocus,
Expand All @@ -377,7 +378,7 @@ export const ColorControl: FC<ColorControlProps> = ({
const Picker = ColorPicker[colorSpace];

const readOnly = !!argType?.table?.readonly;
const controlId = getControlId(name);
const controlId = getControlId(name, storyId);

return (
<Wrapper>
Expand Down
12 changes: 10 additions & 2 deletions code/addons/docs/src/blocks/controls/Date.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,15 @@ const FlexSpaced = styled.fieldset({
});

export type DateProps = ControlProps<DateValue> & DateConfig;
export const DateControl: FC<DateProps> = ({ name, value, onChange, onFocus, onBlur, argType }) => {
export const DateControl: FC<DateProps> = ({
name,
storyId,
value,
onChange,
onFocus,
onBlur,
argType,
}) => {
const [valid, setValid] = useState(true);
const dateRef = useRef<HTMLInputElement>();
const timeRef = useRef<HTMLInputElement>();
Expand Down Expand Up @@ -114,7 +122,7 @@ export const DateControl: FC<DateProps> = ({ name, value, onChange, onFocus, onB
setValid(!!time);
};

const controlId = getControlId(name);
const controlId = getControlId(name, storyId);

return (
<FlexSpaced>
Expand Down
3 changes: 2 additions & 1 deletion code/addons/docs/src/blocks/controls/Files.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ function revokeOldUrls(urls: string[]) {
export const FilesControl: FC<FilesControlProps> = ({
onChange,
name,
storyId,
accept = 'image/*',
value,
argType,
Expand All @@ -63,7 +64,7 @@ export const FilesControl: FC<FilesControlProps> = ({
}
}, [value, name]);

const controlId = getControlId(name);
const controlId = getControlId(name, storyId);

return (
<>
Expand Down
5 changes: 3 additions & 2 deletions code/addons/docs/src/blocks/controls/Number.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const FormInput = styled(Form.Input)(({ theme }) => ({

export const NumberControl: FC<NumberProps> = ({
name,
storyId,
value,
onChange,
min,
Expand Down Expand Up @@ -102,7 +103,7 @@ export const NumberControl: FC<NumberProps> = ({
ariaLabel={false}
variant="outline"
size="medium"
id={getControlSetterButtonId(name)}
id={getControlSetterButtonId(name, storyId)}
onClick={onForceVisible}
disabled={readonly}
>
Expand All @@ -115,7 +116,7 @@ export const NumberControl: FC<NumberProps> = ({
<Wrapper>
<FormInput
ref={htmlElRef}
id={getControlId(name)}
id={getControlId(name, storyId)}
type="number"
onChange={handleChange}
size="flex"
Expand Down
6 changes: 3 additions & 3 deletions code/addons/docs/src/blocks/controls/Object.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ const selectValue = (event: SyntheticEvent<HTMLInputElement>) => {

export type ObjectProps = ControlProps<ObjectValue> & ObjectConfig;

export const ObjectControl: FC<ObjectProps> = ({ name, value, onChange, argType }) => {
export const ObjectControl: FC<ObjectProps> = ({ name, storyId, value, onChange, argType }) => {
const theme = useTheme();
const data = useMemo(() => value && cloneDeep(value), [value]);
const hasData = data !== null && data !== undefined;
Expand Down Expand Up @@ -208,7 +208,7 @@ export const ObjectControl: FC<ObjectProps> = ({ name, value, onChange, argType
<Button
ariaLabel={false}
disabled={readonly}
id={getControlSetterButtonId(name)}
id={getControlSetterButtonId(name, storyId)}
onClick={onForceVisible}
>
Set object
Expand All @@ -219,7 +219,7 @@ export const ObjectControl: FC<ObjectProps> = ({ name, value, onChange, argType
const rawJSONForm = (
<RawInput
ref={htmlElRef}
id={getControlId(name)}
id={getControlId(name, storyId)}
minRows={3}
name={name}
key={jsonString}
Expand Down
3 changes: 2 additions & 1 deletion code/addons/docs/src/blocks/controls/Range.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ function getNumberOfDecimalPlaces(number: number) {

export const RangeControl: FC<RangeProps> = ({
name,
storyId,
value,
onChange,
min = 0,
Expand All @@ -194,7 +195,7 @@ export const RangeControl: FC<RangeProps> = ({
const numberOFDecimalsPlaces = useMemo(() => getNumberOfDecimalPlaces(step), [step]);

const readonly = !!argType?.table?.readonly;
const controlId = getControlId(name);
const controlId = getControlId(name, storyId);

return (
<RangeWrapper readOnly={readonly}>
Expand Down
5 changes: 3 additions & 2 deletions code/addons/docs/src/blocks/controls/Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const MaxLength = styled.div<{ isMaxed: boolean }>(({ isMaxed }) => ({

export const TextControl: FC<TextProps> = ({
name,
storyId,
value,
onChange,
onFocus,
Expand Down Expand Up @@ -49,7 +50,7 @@ export const TextControl: FC<TextProps> = ({
variant="outline"
size="medium"
disabled={readonly}
id={getControlSetterButtonId(name)}
id={getControlSetterButtonId(name, storyId)}
onClick={onForceVisible}
>
Set string
Expand All @@ -62,7 +63,7 @@ export const TextControl: FC<TextProps> = ({
return (
<Wrapper>
<Form.Textarea
id={getControlId(name)}
id={getControlId(name, storyId)}
maxLength={maxLength}
onChange={handleChange}
disabled={readonly}
Expand Down
8 changes: 8 additions & 0 deletions code/addons/docs/src/blocks/controls/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ describe('getControlId', () => {
])('%s', (name, input, expected) => {
expect(getControlId(input)).toBe(expected);
});

it('includes storyId when provided', () => {
expect(getControlId('some-id', 'story--name')).toBe('control-story--name-some-id');
});
});

describe('getControlSetterButtonId', () => {
Expand All @@ -22,4 +26,8 @@ describe('getControlSetterButtonId', () => {
])('%s', (name, input, expected) => {
expect(getControlSetterButtonId(input)).toBe(expected);
});

it('includes storyId when provided', () => {
expect(getControlSetterButtonId('some-id', 'story--name')).toBe('set-story--name-some-id');
});
});
10 changes: 8 additions & 2 deletions code/addons/docs/src/blocks/controls/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
*
* @link http://xahlee.info/js/html_allowed_chars_in_attribute.html
*/
export const getControlId = (value: string) => `control-${value.replace(/\s+/g, '-')}`;
export const getControlId = (value: string, storyId?: string) => {
const base = value.replace(/\s+/g, '-');
return storyId ? `control-${storyId}-${base}` : `control-${base}`;
};

/**
* Adds `set` prefix to make ID attribute more specific. Removes spaces because spaces are not
Expand All @@ -24,4 +27,7 @@ export const getControlId = (value: string) => `control-${value.replace(/\s+/g,
*
* @link http://xahlee.info/js/html_allowed_chars_in_attribute.html
*/
export const getControlSetterButtonId = (value: string) => `set-${value.replace(/\s+/g, '-')}`;
export const getControlSetterButtonId = (value: string, storyId?: string) => {
const base = value.replace(/\s+/g, '-');
return storyId ? `set-${storyId}-${base}` : `set-${base}`;
};
3 changes: 2 additions & 1 deletion code/addons/docs/src/blocks/controls/options/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type CheckboxConfig = NormalizedOptionsConfig & { isInline: boolean };
type CheckboxProps = ControlProps<OptionsMultiSelection> & CheckboxConfig;
export const CheckboxControl: FC<CheckboxProps> = ({
name,
storyId,
options,
value,
onChange,
Expand Down Expand Up @@ -88,7 +89,7 @@ export const CheckboxControl: FC<CheckboxProps> = ({
setSelected(selectedKeys(value || [], options));
}, [value]);

const controlId = getControlId(name);
const controlId = getControlId(name, storyId);

return (
<Wrapper $isInline={isInline}>
Expand Down
3 changes: 2 additions & 1 deletion code/addons/docs/src/blocks/controls/options/Radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type RadioConfig = NormalizedOptionsConfig & { isInline: boolean };
type RadioProps = ControlProps<OptionsSingleSelection> & RadioConfig;
export const RadioControl: FC<RadioProps> = ({
name,
storyId,
options,
value,
onChange,
Expand All @@ -67,7 +68,7 @@ export const RadioControl: FC<RadioProps> = ({
return <>-</>;
}
const selection = selectedKey(value, options);
const controlId = getControlId(name);
const controlId = getControlId(name, storyId);

const readonly = !!argType?.table?.readonly;

Expand Down
Loading
Loading