diff --git a/examples/official-storybook/stories/addon-controls.stories.tsx b/examples/official-storybook/stories/addon-controls.stories.tsx index 0d28d6936e02..b7bf329109b7 100644 --- a/examples/official-storybook/stories/addon-controls.stories.tsx +++ b/examples/official-storybook/stories/addon-controls.stories.tsx @@ -161,3 +161,8 @@ FilteredWithExcludeRegex.parameters = { exclude: /hello*/, }, }; + +// https://github.com/storybookjs/storybook/issues/14752 +export const MissingRadioOptions = Template.bind({}); +MissingRadioOptions.argTypes = { invalidRadio: { control: 'radio' } }; +MissingRadioOptions.args = { invalidRadio: 'someValue' }; diff --git a/lib/components/src/controls/options/Checkbox.tsx b/lib/components/src/controls/options/Checkbox.tsx index 5695e1b9a257..4343957db533 100644 --- a/lib/components/src/controls/options/Checkbox.tsx +++ b/lib/components/src/controls/options/Checkbox.tsx @@ -1,5 +1,6 @@ import React, { FC, ChangeEvent, useState, Fragment } from 'react'; import { styled } from '@storybook/theming'; +import { logger } from '@storybook/client-logger'; import { ControlProps, OptionsMultiSelection, NormalizedOptionsConfig } from '../types'; import { selectedKeys, selectedValues } from './helpers'; @@ -48,6 +49,11 @@ export const CheckboxControl: FC = ({ onChange, isInline, }) => { + if (!options) { + logger.warn(`Checkbox with no options: ${name}`); + return <>-; + } + const initial = selectedKeys(value, options); const [selected, setSelected] = useState(initial); diff --git a/lib/components/src/controls/options/Radio.tsx b/lib/components/src/controls/options/Radio.tsx index 0078c6c43d49..4ac3afc5be85 100644 --- a/lib/components/src/controls/options/Radio.tsx +++ b/lib/components/src/controls/options/Radio.tsx @@ -1,5 +1,6 @@ import React, { FC } from 'react'; import { styled } from '@storybook/theming'; +import { logger } from '@storybook/client-logger'; import { ControlProps, OptionsSingleSelection, NormalizedOptionsConfig } from '../types'; import { selectedKey } from './helpers'; @@ -48,6 +49,10 @@ const Label = styled.label({ type RadioConfig = NormalizedOptionsConfig & { isInline: boolean }; type RadioProps = ControlProps & RadioConfig; export const RadioControl: FC = ({ name, options, value, onChange, isInline }) => { + if (!options) { + logger.warn(`Radio with no options: ${name}`); + return <>-; + } const selection = selectedKey(value, options); return ( diff --git a/lib/components/src/controls/options/Select.tsx b/lib/components/src/controls/options/Select.tsx index 1e587ed2067b..f9601f243e41 100644 --- a/lib/components/src/controls/options/Select.tsx +++ b/lib/components/src/controls/options/Select.tsx @@ -1,5 +1,6 @@ import React, { FC, ChangeEvent } from 'react'; import { styled, CSSObject } from '@storybook/theming'; +import { logger } from '@storybook/client-logger'; import { ControlProps, OptionsSelection, NormalizedOptionsConfig } from '../types'; import { selectedKey, selectedKeys, selectedValues } from './helpers'; import { Icons } from '../../icon/icon'; @@ -129,6 +130,13 @@ const MultiSelect: FC = ({ name, value, options, onChange }) => { ); }; -export const SelectControl: FC = (props) => +export const SelectControl: FC = (props) => { + const { name, options } = props; + if (!options) { + logger.warn(`Select with no options: ${name}`); + return <>-; + } + // eslint-disable-next-line react/destructuring-assignment - props.isMulti ? : ; + return props.isMulti ? : ; +}; diff --git a/lib/components/src/controls/options/helpers.ts b/lib/components/src/controls/options/helpers.ts index 6bb25b3954e5..6b956b9cd4ad 100644 --- a/lib/components/src/controls/options/helpers.ts +++ b/lib/components/src/controls/options/helpers.ts @@ -1,16 +1,16 @@ import { OptionsObject } from '../types'; export const selectedKey = (value: any, options: OptionsObject) => { - const entry = Object.entries(options).find(([_key, val]) => val === value); + const entry = options && Object.entries(options).find(([_key, val]) => val === value); return entry ? entry[0] : undefined; }; export const selectedKeys = (value: any[], options: OptionsObject) => - value + value && options ? Object.entries(options) .filter((entry) => value.includes(entry[1])) .map((entry) => entry[0]) : []; export const selectedValues = (keys: string[], options: OptionsObject) => - keys.map((key) => options[key]); + keys && options && keys.map((key) => options[key]);