From 4cfd2f4cacb7ae30610de852a9123de7a3e21a35 Mon Sep 17 00:00:00 2001 From: Emilio Martinez Date: Sat, 13 Jul 2019 03:20:10 -0700 Subject: [PATCH 1/3] Addon-knobs: improve types via generics and readonlyarray --- addons/knobs/src/KnobManager.ts | 4 +- addons/knobs/src/components/types/Array.tsx | 12 +++-- addons/knobs/src/components/types/Boolean.tsx | 10 ++--- addons/knobs/src/components/types/Button.tsx | 12 +++-- .../knobs/src/components/types/Checkboxes.tsx | 24 +++++----- addons/knobs/src/components/types/Color.tsx | 13 ++---- addons/knobs/src/components/types/Date.tsx | 13 ++---- addons/knobs/src/components/types/Files.tsx | 8 ++-- addons/knobs/src/components/types/Number.tsx | 11 ++--- addons/knobs/src/components/types/Object.tsx | 12 ++--- addons/knobs/src/components/types/Options.tsx | 15 ++++--- addons/knobs/src/components/types/Radio.tsx | 18 +++----- addons/knobs/src/components/types/Select.tsx | 30 +++++++------ addons/knobs/src/components/types/Text.tsx | 13 ++---- addons/knobs/src/components/types/index.ts | 12 +++-- addons/knobs/src/components/types/types.ts | 10 +++++ addons/knobs/src/index.ts | 45 ++++++++++++------- addons/knobs/src/type-defs.ts | 4 ++ 18 files changed, 130 insertions(+), 136 deletions(-) create mode 100644 addons/knobs/src/components/types/types.ts diff --git a/addons/knobs/src/KnobManager.ts b/addons/knobs/src/KnobManager.ts index fe875efd61ea..e9c172de39b9 100644 --- a/addons/knobs/src/KnobManager.ts +++ b/addons/knobs/src/KnobManager.ts @@ -7,7 +7,7 @@ import { getQueryParams } from '@storybook/client-api'; import { Channel } from '@storybook/channels'; import KnobStore, { KnobStoreKnob } from './KnobStore'; -import { Knob, KnobType } from './type-defs'; +import { Knob, KnobType, Mutable } from './type-defs'; import { SET } from './shared'; import { deserializers } from './converters'; @@ -72,7 +72,7 @@ export default class KnobManager { return this.options.escapeHTML ? escapeStrings(value) : value; } - knob(name: string, options: Knob): Knob['value'] { + knob(name: string, options: Knob): Mutable['value']> { this._mayCallChannel(); const knobName = options.groupId ? `${name}_${options.groupId}` : name; diff --git a/addons/knobs/src/components/types/Array.tsx b/addons/knobs/src/components/types/Array.tsx index d9f7ca82f55a..7583f2d3eefa 100644 --- a/addons/knobs/src/components/types/Array.tsx +++ b/addons/knobs/src/components/types/Array.tsx @@ -2,18 +2,16 @@ import PropTypes from 'prop-types'; import React, { ChangeEvent, Component, WeakValidationMap } from 'react'; import { Form } from '@storybook/components'; +import { KnobControlConfig, KnobControlProps } from './types'; -type ArrayTypeKnobValue = string[]; +export type ArrayTypeKnobValue = string[] | readonly string[]; -export interface ArrayTypeKnob { - name: string; - value: ArrayTypeKnobValue; +export interface ArrayTypeKnob extends KnobControlConfig { separator: string; } -interface ArrayTypeProps { +interface ArrayTypeProps extends KnobControlProps { knob: ArrayTypeKnob; - onChange: (value: ArrayTypeKnobValue) => ArrayTypeKnobValue; } function formatArray(value: string, separator: string) { @@ -41,7 +39,7 @@ export default class ArrayType extends Component { static serialize = (value: ArrayTypeKnobValue) => value; - static deserialize = (value: ArrayTypeKnobValue) => { + static deserialize = (value: string[]) => { if (Array.isArray(value)) return value; return Object.keys(value) diff --git a/addons/knobs/src/components/types/Boolean.tsx b/addons/knobs/src/components/types/Boolean.tsx index d9c1443d260a..25d30dd6605d 100644 --- a/addons/knobs/src/components/types/Boolean.tsx +++ b/addons/knobs/src/components/types/Boolean.tsx @@ -2,18 +2,14 @@ import PropTypes from 'prop-types'; import React, { FunctionComponent } from 'react'; import { styled } from '@storybook/theming'; +import { KnobControlConfig, KnobControlProps } from './types'; type BooleanTypeKnobValue = boolean; -export interface BooleanTypeKnob { - name: string; - value: BooleanTypeKnobValue; - separator: string; -} +export type BooleanTypeKnob = KnobControlConfig; -export interface BooleanTypeProps { +export interface BooleanTypeProps extends KnobControlProps { knob: BooleanTypeKnob; - onChange: (value: BooleanTypeKnobValue) => BooleanTypeKnobValue; } const Input = styled.input({ diff --git a/addons/knobs/src/components/types/Button.tsx b/addons/knobs/src/components/types/Button.tsx index 4dfe28b66391..79f3a311850d 100644 --- a/addons/knobs/src/components/types/Button.tsx +++ b/addons/knobs/src/components/types/Button.tsx @@ -2,19 +2,17 @@ import PropTypes from 'prop-types'; import React, { FunctionComponent, Validator } from 'react'; import { Form } from '@storybook/components'; +import { KnobControlConfig, KnobControlProps } from './types'; -export interface ButtonTypeKnob { - name: string; - value: unknown; -} - -export type ButtonTypeOnClickProp = (knob: ButtonTypeKnob) => any; +export type ButtonTypeKnob = KnobControlConfig; -export interface ButtonTypeProps { +export interface ButtonTypeProps extends KnobControlProps { knob: ButtonTypeKnob; onClick: ButtonTypeOnClickProp; } +export type ButtonTypeOnClickProp = (knob: ButtonTypeKnob) => any; + const serialize = (): undefined => undefined; const deserialize = (): undefined => undefined; diff --git a/addons/knobs/src/components/types/Checkboxes.tsx b/addons/knobs/src/components/types/Checkboxes.tsx index 7b12cf537673..8f5ef3d21ac1 100644 --- a/addons/knobs/src/components/types/Checkboxes.tsx +++ b/addons/knobs/src/components/types/Checkboxes.tsx @@ -1,32 +1,28 @@ import React, { Component, ChangeEvent, WeakValidationMap } from 'react'; import PropTypes from 'prop-types'; import { styled } from '@storybook/theming'; +import { KnobControlConfig, KnobControlProps } from './types'; type CheckboxesTypeKnobValue = string[]; -interface CheckboxesWrapperProps { - isInline: boolean; -} - -export interface CheckboxesTypeKnob { - name: string; - value: CheckboxesTypeKnobValue; - defaultValue: CheckboxesTypeKnobValue; - options: { - [key: string]: string; - }; +export interface CheckboxesTypeKnob extends KnobControlConfig { + options: Record; } -interface CheckboxesTypeProps { +interface CheckboxesTypeProps + extends KnobControlProps, + CheckboxesWrapperProps { knob: CheckboxesTypeKnob; - isInline: boolean; - onChange: (value: CheckboxesTypeKnobValue) => CheckboxesTypeKnobValue; } interface CheckboxesTypeState { values: CheckboxesTypeKnobValue; } +interface CheckboxesWrapperProps { + isInline: boolean; +} + const CheckboxesWrapper = styled.div(({ isInline }: CheckboxesWrapperProps) => isInline ? { diff --git a/addons/knobs/src/components/types/Color.tsx b/addons/knobs/src/components/types/Color.tsx index b6ae06cb3ba0..80d3c418683a 100644 --- a/addons/knobs/src/components/types/Color.tsx +++ b/addons/knobs/src/components/types/Color.tsx @@ -5,18 +5,11 @@ import { SketchPicker, ColorResult } from 'react-color'; import { styled } from '@storybook/theming'; import { Form } from '@storybook/components'; +import { KnobControlConfig, KnobControlProps } from './types'; type ColorTypeKnobValue = string; - -export interface ColorTypeKnob { - name: string; - value: ColorTypeKnobValue; -} - -interface ColorTypeProps { - knob: ColorTypeKnob; - onChange: (value: ColorTypeKnobValue) => ColorTypeKnobValue; -} +export type ColorTypeKnob = KnobControlConfig; +type ColorTypeProps = KnobControlProps; interface ColorTypeState { displayColorPicker: boolean; diff --git a/addons/knobs/src/components/types/Date.tsx b/addons/knobs/src/components/types/Date.tsx index e27b70eca10c..59e3a0ac71ea 100644 --- a/addons/knobs/src/components/types/Date.tsx +++ b/addons/knobs/src/components/types/Date.tsx @@ -2,18 +2,11 @@ import React, { Component, ChangeEvent, WeakValidationMap } from 'react'; import PropTypes from 'prop-types'; import { styled } from '@storybook/theming'; import { Form } from '@storybook/components'; +import { KnobControlConfig, KnobControlProps } from './types'; type DateTypeKnobValue = number; - -export interface DateTypeKnob { - name: string; - value: DateTypeKnobValue; -} - -interface DateTypeProps { - knob: DateTypeKnob; - onChange: (value: DateTypeKnobValue) => DateTypeKnobValue; -} +export type DateTypeKnob = KnobControlConfig; +type DateTypeProps = KnobControlProps; interface DateTypeState { valid: boolean | undefined; diff --git a/addons/knobs/src/components/types/Files.tsx b/addons/knobs/src/components/types/Files.tsx index 762bdd67d938..76668f1d186c 100644 --- a/addons/knobs/src/components/types/Files.tsx +++ b/addons/knobs/src/components/types/Files.tsx @@ -4,18 +4,16 @@ import React, { ChangeEvent, FunctionComponent } from 'react'; import { styled } from '@storybook/theming'; import { Form } from '@storybook/components'; +import { KnobControlConfig, KnobControlProps } from './types'; type DateTypeKnobValue = string[]; -export interface FileTypeKnob { - name: string; +export interface FileTypeKnob extends KnobControlConfig { accept: string; - value: DateTypeKnobValue; } -export interface FilesTypeProps { +export interface FilesTypeProps extends KnobControlProps { knob: FileTypeKnob; - onChange: (value: DateTypeKnobValue) => DateTypeKnobValue; } const FileInput = styled(Form.Input)({ diff --git a/addons/knobs/src/components/types/Number.tsx b/addons/knobs/src/components/types/Number.tsx index 686abd9948ee..1f7e47e84639 100644 --- a/addons/knobs/src/components/types/Number.tsx +++ b/addons/knobs/src/components/types/Number.tsx @@ -3,6 +3,7 @@ import React, { Component, ChangeEvent } from 'react'; import { styled } from '@storybook/theming'; import { Form } from '@storybook/components'; +import { KnobControlConfig, KnobControlProps } from './types'; type NumberTypeKnobValue = number; @@ -13,14 +14,14 @@ export interface NumberTypeKnobOptions { step?: number; } -export interface NumberTypeKnob extends NumberTypeKnobOptions { - name: string; - value: number; +export interface NumberTypeKnob + extends KnobControlConfig, + NumberTypeKnobOptions { + value: NumberTypeKnobValue; } -interface NumberTypeProps { +interface NumberTypeProps extends KnobControlProps { knob: NumberTypeKnob; - onChange: (value: NumberTypeKnobValue) => NumberTypeKnobValue; } const RangeInput = styled.input( diff --git a/addons/knobs/src/components/types/Object.tsx b/addons/knobs/src/components/types/Object.tsx index 3000e24cabe3..3bac4dc597c2 100644 --- a/addons/knobs/src/components/types/Object.tsx +++ b/addons/knobs/src/components/types/Object.tsx @@ -3,16 +3,10 @@ import PropTypes from 'prop-types'; import deepEqual from 'fast-deep-equal'; import { polyfill } from 'react-lifecycles-compat'; import { Form } from '@storybook/components'; +import { KnobControlConfig, KnobControlProps } from './types'; -export interface ObjectTypeKnob { - name: string; - value: T; -} - -interface ObjectTypeProps { - knob: ObjectTypeKnob; - onChange: (value: T) => T; -} +export type ObjectTypeKnob = KnobControlConfig; +type ObjectTypeProps = KnobControlProps; interface ObjectTypeState { value: string; diff --git a/addons/knobs/src/components/types/Options.tsx b/addons/knobs/src/components/types/Options.tsx index 62a972d1a415..c5d5bcb98cfb 100644 --- a/addons/knobs/src/components/types/Options.tsx +++ b/addons/knobs/src/components/types/Options.tsx @@ -3,12 +3,19 @@ import PropTypes from 'prop-types'; import ReactSelect from 'react-select'; import { ValueType } from 'react-select/lib/types'; import { styled } from '@storybook/theming'; +import { KnobControlConfig, KnobControlProps } from './types'; import RadiosType from './Radio'; import CheckboxesType from './Checkboxes'; // TODO: Apply the Storybook theme to react-select +export type OptionsTypeKnobSingleValue = string | number | null | undefined; + +export type OptionsTypeKnobValue< + T extends OptionsTypeKnobSingleValue = OptionsTypeKnobSingleValue +> = T | NonNullable[] | readonly NonNullable[]; + export type OptionsKnobOptionsDisplay = | 'radio' | 'inline-radio' @@ -21,10 +28,7 @@ export interface OptionsKnobOptions { display?: OptionsKnobOptionsDisplay; } -export interface OptionsTypeKnob { - name: string; - value: T; - defaultValue: T; +export interface OptionsTypeKnob extends KnobControlConfig { options: OptionsTypeOptionsProp; optionsObj: OptionsKnobOptions; } @@ -33,10 +37,9 @@ export interface OptionsTypeOptionsProp { [key: string]: T; } -export interface OptionsTypeProps { +export interface OptionsTypeProps extends KnobControlProps { knob: OptionsTypeKnob; display: OptionsKnobOptionsDisplay; - onChange: (value: T) => T; } // : React.ComponentType diff --git a/addons/knobs/src/components/types/Radio.tsx b/addons/knobs/src/components/types/Radio.tsx index 9cd910f67959..375ce20a629c 100644 --- a/addons/knobs/src/components/types/Radio.tsx +++ b/addons/knobs/src/components/types/Radio.tsx @@ -1,24 +1,18 @@ import React, { Component, WeakValidationMap } from 'react'; import PropTypes from 'prop-types'; import { styled } from '@storybook/theming'; +import { KnobControlConfig, KnobControlProps } from './types'; -type RadiosTypeKnobValue = string; +export type RadiosTypeKnobValue = string | number | null | undefined; -export interface RadiosTypeKnob { - name: string; - value: RadiosTypeKnobValue; - defaultValue: RadiosTypeKnobValue; - options: RadiosTypeOptionsProp; -} +export type RadiosTypeOptionsProp = Record; -export interface RadiosTypeOptionsProp { - [key: string]: RadiosTypeKnobValue; +export interface RadiosTypeKnob extends KnobControlConfig { + options: RadiosTypeOptionsProp; } -interface RadiosTypeProps { +interface RadiosTypeProps extends KnobControlProps, RadiosWrapperProps { knob: RadiosTypeKnob; - isInline: boolean; - onChange: (value: RadiosTypeKnobValue) => RadiosTypeKnobValue; } interface RadiosWrapperProps { diff --git a/addons/knobs/src/components/types/Select.tsx b/addons/knobs/src/components/types/Select.tsx index ab338edc7a98..7775403bd1bb 100644 --- a/addons/knobs/src/components/types/Select.tsx +++ b/addons/knobs/src/components/types/Select.tsx @@ -2,22 +2,23 @@ import React, { FunctionComponent, ChangeEvent } from 'react'; import PropTypes from 'prop-types'; import { Form } from '@storybook/components'; +import { KnobControlConfig, KnobControlProps } from './types'; export type SelectTypeKnobValue = string | number | null | undefined; -export interface SelectTypeKnob { - name: string; - value: SelectTypeKnobValue; - options: SelectTypeOptionsProp; -} +export type SelectTypeOptionsProp = + | Record + | T[] + | readonly T[]; -export type SelectTypeOptionsProp = - | Record - | NonNullable[]; +export interface SelectTypeKnob + extends KnobControlConfig { + options: SelectTypeOptionsProp; +} -export interface SelectTypeProps { - knob: SelectTypeKnob; - onChange: (value: SelectTypeKnobValue) => SelectTypeKnobValue; +export interface SelectTypeProps + extends KnobControlProps { + knob: SelectTypeKnob; } const serialize = (value: SelectTypeKnobValue) => value; @@ -29,8 +30,11 @@ const SelectType: FunctionComponent & { } = ({ knob, onChange }) => { const { options } = knob; const entries = Array.isArray(options) - ? options.reduce((acc, k) => Object.assign(acc, { [k]: k }), {}) - : options; + ? options.reduce>( + (acc, k) => Object.assign(acc, { [k]: k }), + {} + ) + : (options as Record); const selectedKey = Object.keys(entries).find(k => entries[k] === knob.value); diff --git a/addons/knobs/src/components/types/Text.tsx b/addons/knobs/src/components/types/Text.tsx index cf939ffa3f1e..cc4c74ec6a91 100644 --- a/addons/knobs/src/components/types/Text.tsx +++ b/addons/knobs/src/components/types/Text.tsx @@ -2,18 +2,11 @@ import PropTypes from 'prop-types'; import React, { Component, ChangeEvent, WeakValidationMap } from 'react'; import { Form } from '@storybook/components'; +import { KnobControlConfig, KnobControlProps } from './types'; type TextTypeKnobValue = string; - -export interface TextTypeKnob { - name: string; - value: TextTypeKnobValue; -} - -interface TextTypeProps { - knob: TextTypeKnob; - onChange: (value: TextTypeKnobValue) => TextTypeKnobValue; -} +export type TextTypeKnob = KnobControlConfig; +type TextTypeProps = KnobControlProps; export default class TextType extends Component { static defaultProps: TextTypeProps = { diff --git a/addons/knobs/src/components/types/index.ts b/addons/knobs/src/components/types/index.ts index 1a25315f8d0b..9340dee5699c 100644 --- a/addons/knobs/src/components/types/index.ts +++ b/addons/knobs/src/components/types/index.ts @@ -45,9 +45,15 @@ export { ColorTypeKnob } from './Color'; export { BooleanTypeKnob } from './Boolean'; export { ObjectTypeKnob } from './Object'; export { SelectTypeKnob, SelectTypeOptionsProp, SelectTypeKnobValue } from './Select'; -export { RadiosTypeKnob, RadiosTypeOptionsProp } from './Radio'; -export { ArrayTypeKnob } from './Array'; +export { RadiosTypeKnob, RadiosTypeOptionsProp, RadiosTypeKnobValue } from './Radio'; +export { ArrayTypeKnob, ArrayTypeKnobValue } from './Array'; export { DateTypeKnob } from './Date'; export { ButtonTypeKnob, ButtonTypeOnClickProp } from './Button'; export { FileTypeKnob } from './Files'; -export { OptionsTypeKnob, OptionsTypeOptionsProp, OptionsKnobOptions } from './Options'; +export { + OptionsTypeKnob, + OptionsKnobOptions, + OptionsTypeOptionsProp, + OptionsTypeKnobSingleValue, + OptionsTypeKnobValue, +} from './Options'; diff --git a/addons/knobs/src/components/types/types.ts b/addons/knobs/src/components/types/types.ts new file mode 100644 index 000000000000..91e1f36e27ed --- /dev/null +++ b/addons/knobs/src/components/types/types.ts @@ -0,0 +1,10 @@ +export interface KnobControlConfig { + name: string; + value: T; + defaultValue?: T; +} + +export interface KnobControlProps { + knob: KnobControlConfig; + onChange: (value: T) => T; +} diff --git a/addons/knobs/src/index.ts b/addons/knobs/src/index.ts index c7a060c5785c..f240cf340927 100644 --- a/addons/knobs/src/index.ts +++ b/addons/knobs/src/index.ts @@ -2,19 +2,26 @@ import addons, { makeDecorator } from '@storybook/addons'; import { SET_OPTIONS } from './shared'; import { manager, registerKnobs } from './registerKnobs'; -import { Knob, KnobType } from './type-defs'; +import { Knob, KnobType, Mutable } from './type-defs'; import { NumberTypeKnobOptions, ButtonTypeOnClickProp, RadiosTypeOptionsProp, SelectTypeOptionsProp, SelectTypeKnobValue, + OptionsTypeKnobValue, OptionsTypeOptionsProp, + OptionsTypeKnobSingleValue, OptionsKnobOptions, + RadiosTypeKnobValue, + ArrayTypeKnobValue, } from './components/types'; -export function knob(name: string, options: Knob) { - return manager.knob(name, options); +export function knob['value']>>( + name: string, + options: Knob +): V { + return manager.knob(name, options) as V; } export function text(name: string, value: string, groupId?: string) { @@ -62,25 +69,31 @@ export function object(name: string, value: T, groupId?: string): T { return manager.knob(name, { type: 'object', value, groupId }); } -export function select( +export function select( name: string, - options: SelectTypeOptionsProp, - value: SelectTypeKnobValue, + options: SelectTypeOptionsProp, + value: T, groupId?: string -) { - return manager.knob(name, { type: 'select', selectV2: true, options, value, groupId }); +): T { + return manager.knob(name, { + type: 'select', + selectV2: true, + options: options as SelectTypeOptionsProp, + value, + groupId, + }) as T; } -export function radios( +export function radios( name: string, - options: RadiosTypeOptionsProp, - value: string, + options: RadiosTypeOptionsProp, + value: T, groupId?: string -) { - return manager.knob(name, { type: 'radios', options, value, groupId }); +): T { + return manager.knob(name, { type: 'radios', options, value, groupId }) as T; } -export function array(name: string, value: string[], separator = ',', groupId?: string) { +export function array(name: string, value: ArrayTypeKnobValue, separator = ',', groupId?: string) { return manager.knob(name, { type: 'array', value, separator, groupId }); } @@ -97,10 +110,10 @@ export function files(name: string, accept: string, value: string[] = [], groupI return manager.knob(name, { type: 'files', accept, value, groupId }); } -export function optionsKnob( +export function optionsKnob( name: string, valuesObj: OptionsTypeOptionsProp, - value: T, + value: OptionsTypeKnobValue, optionsObj: OptionsKnobOptions, groupId?: string ): T { diff --git a/addons/knobs/src/type-defs.ts b/addons/knobs/src/type-defs.ts index 51c76c6c2b39..ddc8c2de34e3 100644 --- a/addons/knobs/src/type-defs.ts +++ b/addons/knobs/src/type-defs.ts @@ -14,6 +14,10 @@ import { KnobType, } from './components/types'; +export type Mutable = { + -readonly [P in keyof T]: T[P] extends readonly (infer U)[] ? U[] : T[P]; +}; + type KnobPlus = K & { type: T; groupId?: string }; export type Knob = T extends 'text' From 0ef667525037abd3d9a3d60c12b349a7d7725131 Mon Sep 17 00:00:00 2001 From: Emilio Martinez Date: Sat, 13 Jul 2019 04:39:16 -0700 Subject: [PATCH 2/3] Addon-knobs: add knobs types test cases --- addons/knobs/src/__types__/knob-test-cases.ts | 199 ++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 addons/knobs/src/__types__/knob-test-cases.ts diff --git a/addons/knobs/src/__types__/knob-test-cases.ts b/addons/knobs/src/__types__/knob-test-cases.ts new file mode 100644 index 000000000000..460eabc04271 --- /dev/null +++ b/addons/knobs/src/__types__/knob-test-cases.ts @@ -0,0 +1,199 @@ +import { + number, + color, + files, + object, + boolean, + text, + select, + date, + array, + button, + knob, + radios, + optionsKnob as options, +} from '../index'; + +// Note: this is a helper to batch test return types and avoid "declared but never read" errors +function expectKnobOfType(..._: T[]) {} + +const groupId = 'GROUP-ID1'; + +/** Text knob */ + +expectKnobOfType( + text('text simple', 'Batman'), + text('text with group', 'default', groupId) +); + +/** Date knob */ + +expectKnobOfType( + date('date simple', new Date('January 20 1887')), + date('date with group', new Date(), groupId) +); + +/** Boolean knob */ + +expectKnobOfType( + boolean('boolean simple', false), + boolean('boolean with group', true, groupId) +); + +/** Color knob */ + +expectKnobOfType( + color('color simple', 'black'), + color('color with group', '#ffffff', groupId) +); + +/** Number knob */ + +expectKnobOfType( + number('number basic', 42), + number('number with options', 72, { range: true, min: 60, max: 90, step: 1 }), + number('number with group', 1, {}, groupId) +); + +/** Radios knob */ + +expectKnobOfType( + radios( + 'radio with string values', + { + 1100: '1100', + 2200: '2200', + 3300: '3300', + }, + '2200' + ), + radios('radio with number values', { 3: 3, 7: 7, 23: 23 }, 3), + radios( + 'radio with mixed value', + { + 1100: '1100', + 2200: 2200, + 3300: '3300', + }, + null, + groupId + ) +); + +/** Select knob */ + +enum SomeEnum { + Type1 = 1, + Type2, +} +const stringLiteralArray: ('Apple' | 'Banana' | 'Grapes')[] = ['Apple', 'Banana', 'Grapes']; + +expectKnobOfType( + select( + 'select with string options', + { + None: 'none', + Underline: 'underline', + 'Line-through': 'line-through', + }, + 'none' + ), + select('select with string array', ['yes', 'no'], 'yes'), + select('select with string literal array', stringLiteralArray, stringLiteralArray[0]), + select('select with readonly array', ['red', 'blue'] as const, 'red'), + select('select with null option', { a: 'Option', b: null }, null, groupId) +); + +expectKnobOfType( + select('select with number options', { 'type a': 1, 'type b': 2 }, 1), + select( + 'select with enum options', + { 'type a': SomeEnum.Type1, 'type b': SomeEnum.Type2 }, + SomeEnum.Type2 + ), + select('select with number array', [1, 2, 3, 4], 1), + select('select with readonly number array', [1, 2] as const, 1) +); + +/** Object knob */ + +expectKnobOfType( + object('object simple', { + fontFamily: 'Arial', + padding: 20, + }), + object('object with group', {}, groupId) +); + +/** Options knob */ + +type Tool = 'hammer' | 'saw' | 'drill'; +const visibleToolOptions: Record = { + hammer: 'hammer', + saw: 'saw', + drill: 'drill', +}; + +expectKnobOfType( + options('options with single selection', visibleToolOptions, 'hammer', { + display: 'check', + }), + options('options with multi selection', visibleToolOptions, ['hammer', 'saw'], { + display: 'inline-check', + }), + options('options with readonly multi selection', visibleToolOptions, ['hammer'] as const, { + display: 'radio', + }), + options('options with group', {}, '', { display: 'check' }) +); + +/** Array knob */ + +const arrayReadonly = array('array as readonly', ['hi', 'there'] as const); + +expectKnobOfType( + array('array simple', ['red', 'green', 'blue']), + arrayReadonly, + array('array with group', [], ',', groupId) +); + +// Should return a mutable array despite the readonly input +arrayReadonly.push('Make sure that the output is still mutable although the input need not be!'); + +/** Button knob */ + +expectKnobOfType( + button('button simple', () => {}), + button('button with group', () => undefined, groupId) +); + +/** Files knob */ + +expectKnobOfType( + files('files simple', 'image/*', []), + files('files with group', 'image/*', ['img.jpg'], groupId) +); + +/** Generic knob */ + +expectKnobOfType( + knob('generic knob as text', { type: 'text', value: 'a' }), + knob('generic knob as color', { type: 'color', value: 'black' }), + knob<'select', string>('generic knob as string select', { + type: 'select', + value: 'yes', + options: ['yes', 'no'], + selectV2: true, + }) +); + +expectKnobOfType( + knob('generic knob as number', { type: 'number', value: 42 }), + knob('generic knob as select', { type: 'radios', value: 3, options: { 3: 3, 7: 7, 23: 23 } }), + knob('generic knob as number select', { + type: 'select', + value: 1, + options: [1, 2], + selectV2: true, + }) +); From 98265c72f1fef13890deb02c8c54e45f338500f2 Mon Sep 17 00:00:00 2001 From: Emilio Martinez Date: Wed, 17 Jul 2019 02:57:28 -0700 Subject: [PATCH 3/3] Addon-knobs: select support for enum-like record types --- addons/knobs/src/__types__/knob-test-cases.ts | 7 ++++++- addons/knobs/src/components/types/Select.tsx | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/addons/knobs/src/__types__/knob-test-cases.ts b/addons/knobs/src/__types__/knob-test-cases.ts index 460eabc04271..1e1e2d8684d6 100644 --- a/addons/knobs/src/__types__/knob-test-cases.ts +++ b/addons/knobs/src/__types__/knob-test-cases.ts @@ -86,6 +86,10 @@ enum SomeEnum { Type1 = 1, Type2, } +enum ButtonVariant { + primary = 'primary', + secondary = 'secondary', +} const stringLiteralArray: ('Apple' | 'Banana' | 'Grapes')[] = ['Apple', 'Banana', 'Grapes']; expectKnobOfType( @@ -101,13 +105,14 @@ expectKnobOfType( select('select with string array', ['yes', 'no'], 'yes'), select('select with string literal array', stringLiteralArray, stringLiteralArray[0]), select('select with readonly array', ['red', 'blue'] as const, 'red'), + select('select with string enum options', ButtonVariant, ButtonVariant.primary), select('select with null option', { a: 'Option', b: null }, null, groupId) ); expectKnobOfType( select('select with number options', { 'type a': 1, 'type b': 2 }, 1), select( - 'select with enum options', + 'select with numeric enum options', { 'type a': SomeEnum.Type1, 'type b': SomeEnum.Type2 }, SomeEnum.Type2 ), diff --git a/addons/knobs/src/components/types/Select.tsx b/addons/knobs/src/components/types/Select.tsx index 7775403bd1bb..18154ab68434 100644 --- a/addons/knobs/src/components/types/Select.tsx +++ b/addons/knobs/src/components/types/Select.tsx @@ -8,6 +8,7 @@ export type SelectTypeKnobValue = string | number | null | undefined; export type SelectTypeOptionsProp = | Record + | Record | T[] | readonly T[];