Skip to content
Open
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
15 changes: 12 additions & 3 deletions code/core/src/docs-tools/argTypes/convert/flow/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import type { SBType } from 'storybook/internal/types';

import type { FlowLiteralType, FlowSigType, FlowType } from './types.ts';

// Type guards for narrowing FlowType discriminant unions
type FlowVoidType = Extract<FlowType, { name: 'void' }>;

const isLiteral = (type: FlowType): type is FlowLiteralType => type.name === 'literal';
const isVoid = (type: FlowType): type is FlowVoidType => type.name === 'void';
const toEnumOption = (element: FlowLiteralType) => element.value.replace(/['|"]/g, '');

const convertSig = (type: FlowSigType) => {
Expand Down Expand Up @@ -45,11 +49,16 @@ export const convert = (type: FlowType): SBType | void => {
}
case 'signature':
return { ...base, ...convertSig(type) };
case 'union':
if (type.elements?.every(isLiteral)) {
return { ...base, name: 'enum', value: type.elements?.map(toEnumOption) };
case 'union': {
const nonVoidElements = (type.elements ?? []).filter((element) => !isVoid(element));
const allLiterals = nonVoidElements.length > 0 && nonVoidElements.every(isLiteral);

if (allLiterals) {
const literalElements = nonVoidElements.filter(isLiteral);
return { ...base, name: 'enum', value: literalElements.map(toEnumOption) };
}
return { ...base, name, value: type.elements?.map(convert) };
}

case 'intersection':
return { ...base, name, value: type.elements?.map(convert) };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,36 @@ describe('normalizeInputTypes', () => {
});
});
});

describe('normalizeInputType - options extraction', () => {
it('extracts options from inside control object to top level', () => {
expect(
normalizeInputType(
{
control: { type: 'select', options: ['a', 'b', 'c'] },
},
'arg'
)
).toEqual({
name: 'arg',
options: ['a', 'b', 'c'],
control: { type: 'select', disable: false },
});
});

it('does not override existing top-level options with control.options', () => {
expect(
normalizeInputType(
{
options: ['x', 'y'],
control: { type: 'select', options: ['a', 'b', 'c'] },
},
'arg'
)
).toEqual({
name: 'arg',
options: ['x', 'y'],
control: { type: 'select', options: ['a', 'b', 'c'], disable: false },
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,15 @@ export const normalizeInputType = (inputType: InputType, key: string): StrictInp
normalized.type = normalizeType(type);
}
if (control) {
normalized.control = normalizeControl(control);
// Extract options from control object if present (users may mistakenly nest
// options inside control, e.g. control: { type: 'select', options: [...] })
if (typeof control === 'object' && 'options' in control && !normalized.options) {
const { options: controlOptions, ...controlRest } = control;
normalized.options = controlOptions;
normalized.control = normalizeControl(controlRest);
} else {
normalized.control = normalizeControl(control);
}
} else if (control === false) {
normalized.control = { disable: true };
}
Expand Down