Skip to content

Commit

Permalink
Addon-knobs: fix knobs function return types (#7391)
Browse files Browse the repository at this point in the history
 Addon-knobs: fix knobs function return types
  • Loading branch information
ndelangen authored Jul 13, 2019
2 parents 9096f57 + 20ef9e5 commit a28e8a1
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 53 deletions.
9 changes: 5 additions & 4 deletions addons/knobs/src/KnobManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { getQueryParams } from '@storybook/client-api';
// eslint-disable-next-line import/no-extraneous-dependencies
import { Channel } from '@storybook/channels';

import KnobStore, { Knob } from './KnobStore';
import KnobStore, { KnobStoreKnob } from './KnobStore';
import { Knob, KnobType } from './type-defs';
import { SET } from './shared';

import { deserializers } from './converters';
Expand Down Expand Up @@ -71,7 +72,7 @@ export default class KnobManager {
return this.options.escapeHTML ? escapeStrings(value) : value;
}

knob(name: string, options: Knob) {
knob<T extends KnobType = any>(name: string, options: Knob<T>): Knob<T>['value'] {
this._mayCallChannel();

const knobName = options.groupId ? `${name}_${options.groupId}` : name;
Expand All @@ -93,7 +94,7 @@ export default class KnobManager {
return this.getKnobValue(existingKnob);
}

const knobInfo: Knob & { name: string; label: string; defaultValue?: any } = {
const knobInfo: Knob<T> & { name: string; label: string; defaultValue?: any } = {
...options,
name: knobName,
label: name,
Expand All @@ -110,7 +111,7 @@ export default class KnobManager {
knobInfo.defaultValue = options.value;
}

knobStore.set(knobName, knobInfo);
knobStore.set(knobName, knobInfo as KnobStoreKnob);
return this.getKnobValue(knobStore.get(knobName));
}

Expand Down
31 changes: 1 addition & 30 deletions addons/knobs/src/KnobStore.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,7 @@
import Types, {
TextTypeKnob,
NumberTypeKnob,
ColorTypeKnob,
BooleanTypeKnob,
ObjectTypeKnob,
SelectTypeKnob,
RadiosTypeKnob,
ArrayTypeKnob,
DateTypeKnob,
ButtonTypeOnClickProp,
FileTypeKnob,
OptionsTypeKnob,
} from './components/types';
import { Knob } from './type-defs';

type Callback = () => any;

type KnobPlus<T extends keyof typeof Types, K> = K & { type: T; groupId?: string };

export type Knob =
| KnobPlus<'text', Pick<TextTypeKnob, 'value'>>
| KnobPlus<'boolean', Pick<BooleanTypeKnob, 'value'>>
| KnobPlus<'number', Pick<NumberTypeKnob, 'value' | 'range' | 'min' | 'max' | 'step'>>
| KnobPlus<'color', Pick<ColorTypeKnob, 'value'>>
| KnobPlus<'object', Pick<ObjectTypeKnob<any>, 'value'>>
| KnobPlus<'select', Pick<SelectTypeKnob, 'value' | 'options'> & { selectV2: true }>
| KnobPlus<'radios', Pick<RadiosTypeKnob, 'value' | 'options'>>
| KnobPlus<'array', Pick<ArrayTypeKnob, 'value' | 'separator'>>
| KnobPlus<'date', Pick<DateTypeKnob, 'value'>>
| KnobPlus<'files', Pick<FileTypeKnob, 'value' | 'accept'>>
| KnobPlus<'button', { value?: unknown; callback: ButtonTypeOnClickProp; hideLabel: true }>
| KnobPlus<'options', Pick<OptionsTypeKnob<any>, 'options' | 'value' | 'optionsObj'>>;

export type KnobStoreKnob = Knob & {
name: string;
label: string;
Expand Down
17 changes: 6 additions & 11 deletions addons/knobs/src/components/Panel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { PureComponent, Fragment, ComponentType } from 'react';
import React, { PureComponent, Fragment } from 'react';
import PropTypes from 'prop-types';
import qs from 'qs';
import { document } from 'global';
Expand All @@ -16,7 +16,7 @@ import {
} from '@storybook/components';
import { RESET, SET, CHANGE, SET_OPTIONS, CLICK } from '../shared';

import Types from './types';
import { getKnobControl } from './types';
import PropForm from './PropForm';
import { KnobStoreKnob } from '../KnobStore';

Expand Down Expand Up @@ -58,11 +58,6 @@ interface KnobPanelOptions {
timestamps?: boolean;
}

type KnobControlType = ComponentType<any> & {
serialize: (v: any) => any;
deserialize: (v: any) => any;
};

export default class KnobPanel extends PureComponent<KnobPanelProps> {
static propTypes = {
active: PropTypes.bool.isRequired,
Expand Down Expand Up @@ -133,9 +128,9 @@ export default class KnobPanel extends PureComponent<KnobPanelProps> {

// If the knob value present in url
if (urlValue !== undefined) {
const value = (Types[knob.type] as KnobControlType).deserialize(urlValue);
const value = getKnobControl(knob.type).deserialize(urlValue);
knob.value = value;
queryParams[`knob-${name}`] = (Types[knob.type] as KnobControlType).serialize(value);
queryParams[`knob-${name}`] = getKnobControl(knob.type).serialize(value);

api.emit(CHANGE, knob);
}
Expand All @@ -161,7 +156,7 @@ export default class KnobPanel extends PureComponent<KnobPanelProps> {
const { knobs } = this.state;

Object.entries(knobs).forEach(([name, knob]) => {
query[`knob-${name}`] = (Types[knob.type] as KnobControlType).serialize(knob.value);
query[`knob-${name}`] = getKnobControl(knob.type).serialize(knob.value);
});

copy(`${location.origin + location.pathname}?${qs.stringify(query, { encode: false })}`);
Expand Down Expand Up @@ -193,7 +188,7 @@ export default class KnobPanel extends PureComponent<KnobPanelProps> {

Object.keys(newKnobs).forEach(n => {
const knob = newKnobs[n];
queryParams[`knob-${n}`] = (Types[knob.type] as KnobControlType).serialize(knob.value);
queryParams[`knob-${n}`] = getKnobControl(knob.type).serialize(knob.value);
});

api.setQueryParams(queryParams);
Expand Down
4 changes: 2 additions & 2 deletions addons/knobs/src/components/PropForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { Component, WeakValidationMap, ComponentType, Requireable } from
import PropTypes from 'prop-types';

import { Form } from '@storybook/components';
import TypeMap from './types';
import { getKnobControl } from './types';
import { KnobStoreKnob } from '../KnobStore';

interface PropFormProps {
Expand Down Expand Up @@ -48,7 +48,7 @@ export default class PropForm extends Component<PropFormProps> {
<Form>
{knobs.map(knob => {
const changeHandler = this.makeChangeHandler(knob.name, knob.type);
const InputType: ComponentType<any> = TypeMap[knob.type] || InvalidType;
const InputType: ComponentType<any> = getKnobControl(knob.type) || InvalidType;

return (
<Form.Field key={knob.name} label={!knob.hideLabel && `${knob.label || knob.name}`}>
Expand Down
15 changes: 14 additions & 1 deletion addons/knobs/src/components/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ComponentType } from 'react';

import TextType from './Text';
import NumberType from './Number';
import ColorType from './Color';
Expand All @@ -11,7 +13,7 @@ import ButtonType from './Button';
import FilesType from './Files';
import OptionsType from './Options';

export default {
const KnobControls = {
text: TextType,
number: NumberType,
color: ColorType,
Expand All @@ -25,6 +27,17 @@ export default {
files: FilesType,
options: OptionsType,
};
export default KnobControls;

export type KnobType = keyof typeof KnobControls;

export type KnobControlType = ComponentType<any> & {
serialize: (v: any) => any;
deserialize: (v: any) => any;
};

// Note: this is a utility function that helps in resolving types more orderly
export const getKnobControl = (type: KnobType): KnobControlType => KnobControls[type];

export { TextTypeKnob } from './Text';
export { NumberTypeKnob, NumberTypeKnobOptions } from './Number';
Expand Down
11 changes: 6 additions & 5 deletions addons/knobs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import addons, { makeDecorator } from '@storybook/addons';

import { SET_OPTIONS } from './shared';
import { manager, registerKnobs } from './registerKnobs';
import { Knob, KnobType } from './type-defs';
import {
NumberTypeKnobOptions,
ButtonTypeOnClickProp,
Expand All @@ -12,8 +13,8 @@ import {
OptionsKnobOptions,
} from './components/types';

export function knob(name: string, optionsParam: any) {
return manager.knob(name, optionsParam);
export function knob<T extends KnobType>(name: string, options: Knob<T>) {
return manager.knob(name, options);
}

export function text(name: string, value: string, groupId?: string) {
Expand Down Expand Up @@ -57,7 +58,7 @@ export function color(name: string, value: string, groupId?: string) {
return manager.knob(name, { type: 'color', value, groupId });
}

export function object<T>(name: string, value: T, groupId?: string) {
export function object<T>(name: string, value: T, groupId?: string): T {
return manager.knob(name, { type: 'object', value, groupId });
}

Expand Down Expand Up @@ -99,10 +100,10 @@ export function files(name: string, accept: string, value: string[] = [], groupI
export function optionsKnob<T>(
name: string,
valuesObj: OptionsTypeOptionsProp<T>,
value: string,
value: T,
optionsObj: OptionsKnobOptions,
groupId?: string
) {
): T {
return manager.knob(name, { type: 'options', options: valuesObj, value, optionsObj, groupId });
}

Expand Down
45 changes: 45 additions & 0 deletions addons/knobs/src/type-defs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
TextTypeKnob,
NumberTypeKnob,
ColorTypeKnob,
BooleanTypeKnob,
ObjectTypeKnob,
SelectTypeKnob,
RadiosTypeKnob,
ArrayTypeKnob,
DateTypeKnob,
ButtonTypeOnClickProp,
FileTypeKnob,
OptionsTypeKnob,
KnobType,
} from './components/types';

type KnobPlus<T extends KnobType, K> = K & { type: T; groupId?: string };

export type Knob<T extends KnobType = any> = T extends 'text'
? KnobPlus<T, Pick<TextTypeKnob, 'value'>>
: T extends 'boolean'
? KnobPlus<T, Pick<BooleanTypeKnob, 'value'>>
: T extends 'number'
? KnobPlus<T, Pick<NumberTypeKnob, 'value' | 'range' | 'min' | 'max' | 'step'>>
: T extends 'color'
? KnobPlus<T, Pick<ColorTypeKnob, 'value'>>
: T extends 'object'
? KnobPlus<T, Pick<ObjectTypeKnob<any>, 'value'>>
: T extends 'select'
? KnobPlus<T, Pick<SelectTypeKnob, 'value' | 'options'> & { selectV2: true }>
: T extends 'radios'
? KnobPlus<T, Pick<RadiosTypeKnob, 'value' | 'options'>>
: T extends 'array'
? KnobPlus<T, Pick<ArrayTypeKnob, 'value' | 'separator'>>
: T extends 'date'
? KnobPlus<T, Pick<DateTypeKnob, 'value'>>
: T extends 'files'
? KnobPlus<T, Pick<FileTypeKnob, 'value' | 'accept'>>
: T extends 'button'
? KnobPlus<T, { value?: never; callback: ButtonTypeOnClickProp; hideLabel: true }>
: T extends 'options'
? KnobPlus<T, Pick<OptionsTypeKnob<any>, 'options' | 'value' | 'optionsObj'>>
: never;

export { KnobType };

0 comments on commit a28e8a1

Please sign in to comment.