Skip to content

Commit

Permalink
Migrate @storybook/addon-knobs to typescript (#7180)
Browse files Browse the repository at this point in the history
Migrate @storybook/addon-knobs to typescript
  • Loading branch information
shilman authored Jul 8, 2019
2 parents 8d82c6a + 7d1fe63 commit 41602a0
Show file tree
Hide file tree
Showing 55 changed files with 1,454 additions and 969 deletions.
1 change: 0 additions & 1 deletion addons/knobs/angular.js

This file was deleted.

1 change: 1 addition & 0 deletions addons/knobs/angular.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './dist/deprecated';
1 change: 0 additions & 1 deletion addons/knobs/html.js

This file was deleted.

1 change: 1 addition & 0 deletions addons/knobs/html.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './dist/deprecated';
1 change: 0 additions & 1 deletion addons/knobs/marko.js

This file was deleted.

1 change: 1 addition & 0 deletions addons/knobs/marko.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './dist/deprecated';
1 change: 0 additions & 1 deletion addons/knobs/mithril.js

This file was deleted.

1 change: 1 addition & 0 deletions addons/knobs/mithril.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './dist/deprecated';
8 changes: 7 additions & 1 deletion addons/knobs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
},
"license": "MIT",
"main": "dist/index.js",
"jsnext:main": "src/index.js",
"types": "dist/index.d.ts",
"scripts": {
"prepare": "node ../../scripts/prepare.js"
},
Expand All @@ -44,5 +44,11 @@
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@types/escape-html": "0.0.20",
"@types/react-color": "^3.0.1",
"@types/react-lifecycles-compat": "^3.0.1",
"@types/react-select": "^2.0.19"
}
}
1 change: 0 additions & 1 deletion addons/knobs/polymer.js

This file was deleted.

1 change: 1 addition & 0 deletions addons/knobs/polymer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './dist/deprecated';
1 change: 0 additions & 1 deletion addons/knobs/react.js

This file was deleted.

1 change: 1 addition & 0 deletions addons/knobs/react.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './dist/deprecated';
56 changes: 36 additions & 20 deletions addons/knobs/src/KnobManager.js → addons/knobs/src/KnobManager.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
/* eslint no-underscore-dangle: 0 */

import { navigator } from 'global';
import escape from 'escape-html';

import { getQueryParams } from '@storybook/client-api';
// eslint-disable-next-line import/no-extraneous-dependencies
import { Channel } from '@storybook/channels';

import KnobStore from './KnobStore';
import KnobStore, { Knob } from './KnobStore';
import { SET } from './shared';

import { deserializers } from './converters';

const knobValuesFromUrl = Object.entries(getQueryParams()).reduce((acc, [k, v]) => {
if (k.includes('knob-')) {
return { ...acc, [k.replace('knob-', '')]: v };
}
return acc;
}, {});
const knobValuesFromUrl: Record<string, string> = Object.entries(getQueryParams()).reduce(
(acc, [k, v]) => {
if (k.includes('knob-')) {
return { ...acc, [k.replace('knob-', '')]: v };
}
return acc;
},
{}
);

// This is used by _mayCallChannel to determine how long to wait to before triggering a panel update
const PANEL_UPDATE_INTERVAL = 400;

const escapeStrings = obj => {
function escapeStrings(obj: { [key: string]: string }): { [key: string]: string };
function escapeStrings(obj: (string | string[])[]): (string | string[])[];
function escapeStrings(obj: string): string;
function escapeStrings(obj: any): any {
if (typeof obj === 'string') {
return escape(obj);
}
Expand All @@ -31,31 +39,39 @@ const escapeStrings = obj => {
const didChange = newArray.some((newValue, key) => newValue !== obj[key]);
return didChange ? newArray : obj;
}
return Object.entries(obj).reduce((acc, [key, oldValue]) => {
return Object.entries<{ [key: string]: string }>(obj).reduce((acc, [key, oldValue]) => {
const newValue = escapeStrings(oldValue);
return newValue === oldValue ? acc : { ...acc, [key]: newValue };
}, obj);
};
}

interface KnobManagerOptions {
escapeHTML?: boolean;
disableDebounce?: boolean;
}

export default class KnobManager {
constructor() {
this.knobStore = new KnobStore();
this.options = {};
}
knobStore = new KnobStore();

channel: Channel;

options: KnobManagerOptions = {};

calling: boolean;

setChannel(channel) {
setChannel(channel: Channel) {
this.channel = channel;
}

setOptions(options) {
setOptions(options: KnobManagerOptions) {
this.options = options;
}

getKnobValue({ value }) {
getKnobValue({ value }: Knob) {
return this.options.escapeHTML ? escapeStrings(value) : value;
}

knob(name, options) {
knob(name: string, options: Knob) {
this._mayCallChannel();

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

const knobInfo = {
const knobInfo: Knob & { name: string; label: string; defaultValue?: any } = {
...options,
name: knobName,
label: name,
Expand Down
56 changes: 0 additions & 56 deletions addons/knobs/src/KnobStore.js

This file was deleted.

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

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;
used?: boolean;
defaultValue?: any;
hideLabel?: boolean;
callback?: () => any;
};

const callArg = (fn: Callback) => fn();
const callAll = (fns: Callback[]) => fns.forEach(callArg);

export default class KnobStore {
store: Record<string, KnobStoreKnob> = {};

callbacks: Callback[] = [];

timer: number;

has(key: string) {
return this.store[key] !== undefined;
}

set(key: string, value: KnobStoreKnob) {
this.store[key] = {
...value,
used: true,
groupId: value.groupId,
};

// debounce the execution of the callbacks for 50 milliseconds
if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(callAll, 50, this.callbacks);
}

get(key: string) {
const knob = this.store[key];
if (knob) {
knob.used = true;
}
return knob;
}

getAll() {
return this.store;
}

reset() {
this.store = {};
}

markAllUnused() {
Object.keys(this.store).forEach(knobName => {
this.store[knobName].used = false;
});
}

subscribe(cb: Callback) {
this.callbacks.push(cb);
}

unsubscribe(cb: Callback) {
const index = this.callbacks.indexOf(cb);
this.callbacks.splice(index, 1);
}
}
Loading

0 comments on commit 41602a0

Please sign in to comment.