diff --git a/addons/docs/src/frameworks/react/react-argtypes.stories.tsx b/addons/docs/src/frameworks/react/react-argtypes.stories.tsx
index 25c7c482529a..cb1d3d6a10fa 100644
--- a/addons/docs/src/frameworks/react/react-argtypes.stories.tsx
+++ b/addons/docs/src/frameworks/react/react-argtypes.stories.tsx
@@ -18,7 +18,7 @@ const argsTableProps = (component: Component) => {
const ArgsStory = ({ component }: any) => {
const { rows } = argsTableProps(component);
- const initialArgs = mapValues(rows, () => null) as Args;
+ const initialArgs = mapValues(rows, (argType) => argType.defaultValue) as Args;
const [args, setArgs] = useState(initialArgs);
return (
diff --git a/examples/official-storybook/stories/addon-docs/props.stories.mdx b/examples/official-storybook/stories/addon-docs/props.stories.mdx
index 879ab6984e52..0f3edca85533 100644
--- a/examples/official-storybook/stories/addon-docs/props.stories.mdx
+++ b/examples/official-storybook/stories/addon-docs/props.stories.mdx
@@ -11,20 +11,14 @@ import { MemoButton } from '../../components/MemoButton';
parameters={{ controls: { expanded: false } }}
/>
-export const FooBar = ({ foo, bar, baz } = {}) => (
+export const ArgsDisplay = (args = {}) => (
-
- Foo |
- {foo && foo.toString()} |
-
-
- Bar |
- {bar} |
-
-
- Baz |
- {baz && baz.toString()} |
-
+ {Object.entries(args).map(([key, val]) => (
+
+ {key} |
+ {val && val.toString()} |
+
+ ))}
);
@@ -34,21 +28,45 @@ export const FooBar = ({ foo, bar, baz } = {}) => (
- {(args) => }
+ {(args) => }
@@ -68,7 +86,7 @@ export const FooBar = ({ foo, bar, baz } = {}) => (
bar: '',
}}
>
- {(args) => }
+ {(args) => }
diff --git a/lib/components/package.json b/lib/components/package.json
index 6dd87cc5bc76..89ca5e1ab962 100644
--- a/lib/components/package.json
+++ b/lib/components/package.json
@@ -50,7 +50,6 @@
"react-dom": "^16.8.3",
"react-helmet-async": "^1.0.2",
"react-popper-tooltip": "^2.11.0",
- "react-select": "^3.0.8",
"react-syntax-highlighter": "^12.2.1",
"react-textarea-autosize": "^7.1.0",
"ts-dedent": "^1.1.1"
diff --git a/lib/components/src/controls/options/Checkbox.tsx b/lib/components/src/controls/options/Checkbox.tsx
index 44e24b78518c..e922ae949b85 100644
--- a/lib/components/src/controls/options/Checkbox.tsx
+++ b/lib/components/src/controls/options/Checkbox.tsx
@@ -1,6 +1,7 @@
import React, { FC, ChangeEvent, useState } from 'react';
import { styled } from '@storybook/theming';
import { ControlProps, OptionsMultiSelection, NormalizedOptionsConfig } from '../types';
+import { selectedKeys, selectedValues } from './helpers';
const CheckboxesWrapper = styled.div<{ isInline: boolean }>(({ isInline }) =>
isInline
@@ -36,18 +37,19 @@ export const CheckboxControl: FC = ({
onChange,
isInline,
}) => {
- const [selected, setSelected] = useState(value || []);
+ const initial = selectedKeys(value, options);
+ const [selected, setSelected] = useState(initial);
const handleChange = (e: ChangeEvent) => {
const option = (e.target as HTMLInputElement).value;
- const newVal = [...selected];
- if (newVal.includes(option)) {
- newVal.splice(newVal.indexOf(option), 1);
+ const updated = [...selected];
+ if (updated.includes(option)) {
+ updated.splice(updated.indexOf(option), 1);
} else {
- newVal.push(option);
+ updated.push(option);
}
- onChange(name, newVal);
- setSelected(newVal);
+ onChange(name, selectedValues(updated, options));
+ setSelected(updated);
};
return (
@@ -55,17 +57,15 @@ export const CheckboxControl: FC = ({
{Object.keys(options).map((key: string) => {
const id = `${name}-${key}`;
- const optionValue = options[key];
-
return (
{key}
diff --git a/lib/components/src/controls/options/Options.stories.tsx b/lib/components/src/controls/options/Options.stories.tsx
index d8ce20c14bb8..5c70774b04db 100644
--- a/lib/components/src/controls/options/Options.stories.tsx
+++ b/lib/components/src/controls/options/Options.stories.tsx
@@ -12,10 +12,10 @@ const objectOptions = {
B: { id: 'Bat' },
C: { id: 'Cat' },
};
-const emptyOptions = null;
-const optionsHelper = (options, type) => {
- const [value, setValue] = useState([]);
+const optionsHelper = (options, type, isMulti) => {
+ const initial = Array.isArray(options) ? options[1] : options.B;
+ const [value, setValue] = useState(isMulti ? [initial] : initial);
return (
<>
{
options={options}
value={value}
type={type}
- onChange={(name, newVal) => setValue(newVal)}
+ onChange={(_name, newVal) => setValue(newVal)}
/>
{value && Array.isArray(value) ? (
// eslint-disable-next-line react/no-array-index-key
@@ -36,19 +36,19 @@ const optionsHelper = (options, type) => {
};
// Check
-export const CheckArray = () => optionsHelper(arrayOptions, 'check');
-export const InlineCheckArray = () => optionsHelper(arrayOptions, 'inline-check');
-export const CheckObject = () => optionsHelper(objectOptions, 'check');
-export const InlineCheckObject = () => optionsHelper(objectOptions, 'inline-check');
+export const CheckArray = () => optionsHelper(arrayOptions, 'check', true);
+export const InlineCheckArray = () => optionsHelper(arrayOptions, 'inline-check', true);
+export const CheckObject = () => optionsHelper(objectOptions, 'check', true);
+export const InlineCheckObject = () => optionsHelper(objectOptions, 'inline-check', true);
// Radio
-export const ArrayRadio = () => optionsHelper(arrayOptions, 'radio');
-export const ArrayInlineRadio = () => optionsHelper(arrayOptions, 'inline-radio');
-export const ObjectRadio = () => optionsHelper(objectOptions, 'radio');
-export const ObjectInlineRadio = () => optionsHelper(objectOptions, 'inline-radio');
+export const ArrayRadio = () => optionsHelper(arrayOptions, 'radio', false);
+export const ArrayInlineRadio = () => optionsHelper(arrayOptions, 'inline-radio', false);
+export const ObjectRadio = () => optionsHelper(objectOptions, 'radio', false);
+export const ObjectInlineRadio = () => optionsHelper(objectOptions, 'inline-radio', false);
// Select
-export const ArraySelect = () => optionsHelper(arrayOptions, 'select');
-export const ArrayMultiSelect = () => optionsHelper(arrayOptions, 'multi-select');
-export const ObjectSelect = () => optionsHelper(objectOptions, 'select');
-export const ObjectMultiSelect = () => optionsHelper(objectOptions, 'multi-select');
+export const ArraySelect = () => optionsHelper(arrayOptions, 'select', false);
+export const ArrayMultiSelect = () => optionsHelper(arrayOptions, 'multi-select', true);
+export const ObjectSelect = () => optionsHelper(objectOptions, 'select', false);
+export const ObjectMultiSelect = () => optionsHelper(objectOptions, 'multi-select', true);
diff --git a/lib/components/src/controls/options/Options.tsx b/lib/components/src/controls/options/Options.tsx
index 373cf58a5e11..d838b30e964d 100644
--- a/lib/components/src/controls/options/Options.tsx
+++ b/lib/components/src/controls/options/Options.tsx
@@ -5,10 +5,18 @@ import { RadioControl } from './Radio';
import { SelectControl } from './Select';
import { ControlProps, OptionsSelection, OptionsConfig, Options } from '../types';
+/**
+ * Options can accept `options` in two formats:
+ * - array: ['a', 'b', 'c'] OR
+ * - object: { a: 1, b: 2, c: 3 }
+ *
+ * We always normalize to the more generalized object format and ONLY handle
+ * the object format in the underlying control implementations.
+ */
const normalizeOptions = (options: Options) => {
if (Array.isArray(options)) {
return options.reduce((acc, item) => {
- acc[item] = item;
+ acc[item.toString()] = item;
return acc;
}, {});
}
diff --git a/lib/components/src/controls/options/Radio.tsx b/lib/components/src/controls/options/Radio.tsx
index ff39af32f8c8..1f0febb4f058 100644
--- a/lib/components/src/controls/options/Radio.tsx
+++ b/lib/components/src/controls/options/Radio.tsx
@@ -1,6 +1,7 @@
-import React, { FC, Validator } from 'react';
+import React, { FC } from 'react';
import { styled } from '@storybook/theming';
import { ControlProps, OptionsSingleSelection, NormalizedOptionsConfig } from '../types';
+import { selectedKey } from './helpers';
const RadiosWrapper = styled.div<{ isInline: boolean }>(({ isInline }) =>
isInline
@@ -24,20 +25,20 @@ const RadioLabel = styled.label({
type RadioConfig = NormalizedOptionsConfig & { isInline: boolean };
type RadioProps = ControlProps & RadioConfig;
export const RadioControl: FC = ({ name, options, value, onChange, isInline }) => {
+ const selection = selectedKey(value, options);
return (
{Object.keys(options).map((key) => {
const id = `${name}-${key}`;
- const optionValue = options[key];
return (
onChange(name, e.target.value)}
- checked={optionValue === value}
+ value={key}
+ onChange={(e) => onChange(name, options[e.currentTarget.value])}
+ checked={key === selection}
/>
{key}
diff --git a/lib/components/src/controls/options/Select.tsx b/lib/components/src/controls/options/Select.tsx
index 8709a0c6ad05..00000cd5ce61 100644
--- a/lib/components/src/controls/options/Select.tsx
+++ b/lib/components/src/controls/options/Select.tsx
@@ -1,41 +1,55 @@
-import React, { FC } from 'react';
-import ReactSelect from 'react-select';
+import React, { FC, ChangeEvent } from 'react';
import { styled } from '@storybook/theming';
import { ControlProps, OptionsSelection, NormalizedOptionsConfig } from '../types';
+import { selectedKey, selectedKeys, selectedValues } from './helpers';
-// TODO: Apply the Storybook theme to react-select
-const OptionsSelect = styled(ReactSelect)({
+const OptionsSelect = styled.select({
width: '100%',
maxWidth: '300px',
color: 'black',
});
-interface OptionsItem {
- value: any;
- label: string;
-}
-type ReactSelectOnChangeFn = { (v: OptionsItem): void } | { (v: OptionsItem[]): void };
-
type SelectConfig = NormalizedOptionsConfig & { isMulti: boolean };
type SelectProps = ControlProps & SelectConfig;
-export const SelectControl: FC = ({ name, value, options, onChange, isMulti }) => {
- // const optionsIndex = options.findIndex(i => i.value === value);
- // let defaultValue: typeof options | typeof options[0] = options[optionsIndex];
- const selectOptions = Object.entries(options).reduce((acc, [key, val]) => {
- acc.push({ label: key, value: val });
- return acc;
- }, []);
-
- const handleChange: ReactSelectOnChangeFn = isMulti
- ? (values: OptionsItem[]) => onChange(name, values && values.map((item) => item.value))
- : (e: OptionsItem) => onChange(name, e.value);
+
+const NO_SELECTION = 'Select...';
+
+const SingleSelect: FC = ({ name, value, options, onChange }) => {
+ const handleChange = (e: ChangeEvent) => {
+ onChange(name, options[e.currentTarget.value]);
+ };
+ const selection = selectedKey(value, options) || NO_SELECTION;
return (
-
+
+
+ {Object.keys(options).map((key) => (
+
+ ))}
+
);
};
+
+const MultiSelect: FC = ({ name, value, options, onChange }) => {
+ const handleChange = (e: ChangeEvent) => {
+ const selection = Array.from(e.currentTarget.options)
+ .filter((option) => option.selected)
+ .map((option) => option.value);
+ onChange(name, selectedValues(selection, options));
+ };
+ const selection = selectedKeys(value, options);
+
+ return (
+
+ {Object.keys(options).map((key) => (
+
+ ))}
+
+ );
+};
+
+export const SelectControl: FC = (props) =>
+ // eslint-disable-next-line react/destructuring-assignment
+ props.isMulti ? : ;
diff --git a/lib/components/src/controls/options/helpers.ts b/lib/components/src/controls/options/helpers.ts
new file mode 100644
index 000000000000..3a7667ade8f3
--- /dev/null
+++ b/lib/components/src/controls/options/helpers.ts
@@ -0,0 +1,14 @@
+import { OptionsObject } from '../types';
+
+export const selectedKey = (value: any, options: OptionsObject) => {
+ const entry = Object.entries(options).find(([_key, val]) => val === value);
+ return entry ? entry[0] : undefined;
+};
+
+export const selectedKeys = (value: any[], options: OptionsObject) =>
+ 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]);