Addon-knobs: migrate components

This commit is contained in:
Emilio Martinez 2019-06-24 18:12:10 -07:00
parent dfbf5d9cb5
commit 754ec02628
13 changed files with 554 additions and 257 deletions

View File

@ -1,25 +1,61 @@
import PropTypes from 'prop-types';
import React from 'react';
import React, { Component, WeakValidationMap } from 'react';
import { Form } from '@storybook/components';
function formatArray(value, separator) {
type ArrayTypeKnobValue = string[];
interface ArrayTypeProps {
knob: {
name: string;
value: ArrayTypeKnobValue;
separator: string;
};
onChange: (value: ArrayTypeKnobValue) => ArrayTypeKnobValue;
}
function formatArray(value: string, separator: string) {
if (value === '') {
return [];
}
return value.split(separator);
}
class ArrayType extends React.Component {
shouldComponentUpdate(nextProps) {
export default class ArrayType extends Component<ArrayTypeProps> {
static defaultProps: Partial<ArrayTypeProps> = {
knob: {} as any,
onChange: (value: ArrayTypeKnobValue) => value,
};
static propTypes: WeakValidationMap<ArrayTypeProps> = {
// TODO: remove `any` once DefinitelyTyped/DefinitelyTyped#31280 has been resolved
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.array,
separator: PropTypes.string,
}) as any,
onChange: PropTypes.func,
};
static serialize = (value: ArrayTypeKnobValue) => value;
static deserialize = (value: ArrayTypeKnobValue) => {
if (Array.isArray(value)) return value;
return Object.keys(value)
.sort()
.reduce((array, key) => [...array, value[key]], []);
};
shouldComponentUpdate(nextProps: Readonly<ArrayTypeProps>) {
const { knob } = this.props;
return nextProps.knob.value !== knob.value;
}
handleChange = e => {
handleChange = (e: Event) => {
const { knob, onChange } = this.props;
const { value } = e.target;
const { value } = e.target as HTMLTextAreaElement;
const newVal = formatArray(value, knob.separator);
onChange(newVal);
@ -40,28 +76,3 @@ class ArrayType extends React.Component {
);
}
}
ArrayType.defaultProps = {
knob: {},
onChange: value => value,
};
ArrayType.propTypes = {
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.array,
separator: PropTypes.string,
}),
onChange: PropTypes.func,
};
ArrayType.serialize = value => value;
ArrayType.deserialize = value => {
if (Array.isArray(value)) return value;
return Object.keys(value)
.sort()
.reduce((array, key) => [...array, value[key]], []);
};
export default ArrayType;

View File

@ -1,8 +1,19 @@
import PropTypes from 'prop-types';
import React from 'react';
import React, { FunctionComponent } from 'react';
import { styled } from '@storybook/theming';
type BooleanTypeKnobValue = boolean;
interface BooleanTypeProps {
knob: {
name: string;
value: BooleanTypeKnobValue;
separator: string;
};
onChange: (value: BooleanTypeKnobValue) => BooleanTypeKnobValue;
}
const Input = styled.input({
display: 'table-cell',
boxSizing: 'border-box',
@ -14,7 +25,13 @@ const Input = styled.input({
color: '#555',
});
const BooleanType = ({ knob, onChange }) => (
const serialize = (value: BooleanTypeKnobValue): string | null => (value ? String(value) : null);
const deserialize = (value: string | null) => value === 'true';
const BooleanType: FunctionComponent<BooleanTypeProps> & {
serialize: typeof serialize;
deserialize: typeof deserialize;
} = ({ knob, onChange }) => (
<Input
id={knob.name}
name={knob.name}
@ -25,19 +42,20 @@ const BooleanType = ({ knob, onChange }) => (
);
BooleanType.defaultProps = {
knob: {},
knob: {} as any,
onChange: value => value,
};
BooleanType.propTypes = {
// TODO: remove `any` once DefinitelyTyped/DefinitelyTyped#31280 has been resolved
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.bool,
}),
}) as any,
onChange: PropTypes.func,
};
BooleanType.serialize = value => (value ? String(value) : null);
BooleanType.deserialize = value => value === 'true';
BooleanType.serialize = serialize;
BooleanType.deserialize = deserialize;
export default BooleanType;

View File

@ -1,22 +1,42 @@
import PropTypes from 'prop-types';
import React from 'react';
import React, { FunctionComponent, Validator } from 'react';
import { Form } from '@storybook/components';
const ButtonType = ({ knob, onClick }) => (
interface ButtonTypeKnobProp {
name: string;
}
interface ButtonTypeProps {
knob: ButtonTypeKnobProp;
onClick: (knob: ButtonTypeKnobProp) => any;
}
const serialize = (): undefined => undefined;
const deserialize = (): undefined => undefined;
const ButtonType: FunctionComponent<ButtonTypeProps> & {
serialize: typeof serialize;
deserialize: typeof deserialize;
} = ({ knob, onClick }) => (
<Form.Button type="button" name={knob.name} onClick={() => onClick(knob)}>
{knob.name}
</Form.Button>
);
ButtonType.defaultProps = {
knob: {} as any,
};
ButtonType.propTypes = {
// TODO: remove `any` once DefinitelyTyped/DefinitelyTyped#31280 has been resolved
knob: PropTypes.shape({
name: PropTypes.string,
}).isRequired,
}).isRequired as Validator<any>,
onClick: PropTypes.func.isRequired,
};
ButtonType.serialize = () => undefined;
ButtonType.deserialize = () => undefined;
ButtonType.serialize = serialize;
ButtonType.deserialize = deserialize;
export default ButtonType;

View File

@ -1,8 +1,33 @@
import React, { Component } from 'react';
import React, { Component, ChangeEvent, WeakValidationMap } from 'react';
import PropTypes from 'prop-types';
import { styled } from '@storybook/theming';
const CheckboxesWrapper = styled.div(({ isInline }) =>
type CheckboxesTypeKnobValue = string[];
interface CheckboxesWrapperProps {
isInline: boolean;
}
interface CheckboxesTypeKnobProp {
name: string;
value: CheckboxesTypeKnobValue;
defaultValue: CheckboxesTypeKnobValue;
options: {
[key: string]: string;
};
}
interface CheckboxesTypeProps {
knob: CheckboxesTypeKnobProp;
isInline: boolean;
onChange: (value: CheckboxesTypeKnobValue) => CheckboxesTypeKnobValue;
}
interface CheckboxesTypeState {
values: CheckboxesTypeKnobValue;
}
const CheckboxesWrapper = styled.div(({ isInline }: CheckboxesWrapperProps) =>
isInline
? {
display: 'flex',
@ -27,8 +52,29 @@ const CheckboxLabel = styled.label({
display: 'inline-block',
});
class CheckboxesType extends Component {
constructor(props) {
export default class CheckboxesType extends Component<CheckboxesTypeProps, CheckboxesTypeState> {
static defaultProps: CheckboxesTypeProps = {
knob: {} as any,
onChange: value => value,
isInline: false,
};
static propTypes: WeakValidationMap<CheckboxesTypeProps> = {
// TODO: remove `any` once DefinitelyTyped/DefinitelyTyped#31280 has been resolved
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.array,
options: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
}) as any,
onChange: PropTypes.func,
isInline: PropTypes.bool,
};
static serialize = (value: CheckboxesTypeKnobValue) => value;
static deserialize = (value: CheckboxesTypeKnobValue) => value;
constructor(props: CheckboxesTypeProps) {
super(props);
const { knob } = props;
@ -37,9 +83,9 @@ class CheckboxesType extends Component {
};
}
handleChange = e => {
handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const { onChange } = this.props;
const currentValue = e.target.value;
const currentValue = (e.target as HTMLInputElement).value;
const { values } = this.state;
if (values.includes(currentValue)) {
@ -53,10 +99,10 @@ class CheckboxesType extends Component {
onChange(values);
};
renderCheckboxList = ({ options }) =>
renderCheckboxList = ({ options }: CheckboxesTypeKnobProp) =>
Object.keys(options).map(key => this.renderCheckbox(key, options[key]));
renderCheckbox = (label, value) => {
renderCheckbox = (label: string, value: string) => {
const { knob } = this.props;
const { name } = knob;
const id = `${name}-${value}`;
@ -87,24 +133,3 @@ class CheckboxesType extends Component {
);
}
}
CheckboxesType.defaultProps = {
knob: {},
onChange: value => value,
isInline: false,
};
CheckboxesType.propTypes = {
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.array,
options: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
}),
onChange: PropTypes.func,
isInline: PropTypes.bool,
};
CheckboxesType.serialize = value => value;
CheckboxesType.deserialize = value => value;
export default CheckboxesType;

View File

@ -1,12 +1,45 @@
// @ts-ignore
import { document } from 'global';
import PropTypes from 'prop-types';
import React from 'react';
import React, { Component, WeakValidationMap } from 'react';
// @ts-ignore
import { SketchPicker } from 'react-color';
import { styled } from '@storybook/theming';
import { Form } from '@storybook/components';
type ColorTypeKnobValue = string;
interface ColorTypeProps {
knob: {
name: string;
value: ColorTypeKnobValue;
};
onChange: (value: ColorTypeKnobValue) => ColorTypeKnobValue;
}
interface ColorTypeState {
displayColorPicker: boolean;
}
interface ColorButtonProps {
name: string;
type: string;
size: string;
active: boolean;
onClick: () => any;
}
// TODO: These types should come from @types/react-color once installed
interface ColorResult {
rgb: {
a?: number;
b: number;
g: number;
r: number;
};
}
const { Button } = Form;
const Swatch = styled.div(({ theme }) => ({
@ -20,25 +53,45 @@ const Swatch = styled.div(({ theme }) => ({
borderRadius: '1rem',
}));
const ColorButton = styled(Button)(({ active }) => ({
const ColorButton = styled(Button)(({ active }: ColorButtonProps) => ({
zIndex: active ? 3 : 'unset',
}));
const Popover = styled.div({
position: 'absolute',
zIndex: '2',
zIndex: 2,
});
class ColorType extends React.Component {
state = {
export default class ColorType extends Component<ColorTypeProps, ColorTypeState> {
static propTypes: WeakValidationMap<ColorTypeProps> = {
// TODO: remove `any` once DefinitelyTyped/DefinitelyTyped#31280 has been resolved
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.string,
}) as any,
onChange: PropTypes.func,
};
static defaultProps: ColorTypeProps = {
knob: {} as any,
onChange: value => value,
};
static serialize = (value: ColorTypeKnobValue) => value;
static deserialize = (value: ColorTypeKnobValue) => value;
state: ColorTypeState = {
displayColorPicker: false,
};
popover: HTMLDivElement;
componentDidMount() {
document.addEventListener('mousedown', this.handleWindowMouseDown);
}
shouldComponentUpdate(nextProps, nextState) {
shouldComponentUpdate(nextProps: ColorTypeProps, nextState: ColorTypeState) {
const { knob } = this.props;
const { displayColorPicker } = this.state;
@ -51,9 +104,9 @@ class ColorType extends React.Component {
document.removeEventListener('mousedown', this.handleWindowMouseDown);
}
handleWindowMouseDown = e => {
handleWindowMouseDown = (e: MouseEvent) => {
const { displayColorPicker } = this.state;
if (!displayColorPicker || this.popover.contains(e.target)) {
if (!displayColorPicker || this.popover.contains(e.target as HTMLElement)) {
return;
}
@ -70,7 +123,7 @@ class ColorType extends React.Component {
});
};
handleChange = color => {
handleChange = (color: ColorResult) => {
const { onChange } = this.props;
onChange(`rgba(${color.rgb.r},${color.rgb.g},${color.rgb.b},${color.rgb.a})`);
@ -105,20 +158,3 @@ class ColorType extends React.Component {
);
}
}
ColorType.propTypes = {
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.string,
}),
onChange: PropTypes.func,
};
ColorType.defaultProps = {
knob: {},
onChange: value => value,
};
ColorType.serialize = value => value;
ColorType.deserialize = value => value;
export default ColorType;

View File

@ -1,8 +1,22 @@
import React, { Component } from 'react';
import React, { Component, ChangeEvent, WeakValidationMap } from 'react';
import PropTypes from 'prop-types';
import { styled } from '@storybook/theming';
import { Form } from '@storybook/components';
type DateTypeKnobValue = number;
interface DateTypeProps {
knob: {
name: string;
value: DateTypeKnobValue;
};
onChange: (value: DateTypeKnobValue) => DateTypeKnobValue;
}
interface DateTypeState {
valid: boolean | undefined;
}
const FlexSpaced = styled.div({
flex: 1,
display: 'flex',
@ -15,29 +29,54 @@ const FlexSpaced = styled.div({
});
const FlexInput = styled(Form.Input)({ flex: 1 });
const formatDate = date => {
const formatDate = (date: Date) => {
const year = `000${date.getFullYear()}`.slice(-4);
const month = `0${date.getMonth() + 1}`.slice(-2);
const day = `0${date.getDate()}`.slice(-2);
return `${year}-${month}-${day}`;
};
const formatTime = date => {
const formatTime = (date: Date) => {
const hours = `0${date.getHours()}`.slice(-2);
const minutes = `0${date.getMinutes()}`.slice(-2);
return `${hours}:${minutes}`;
};
class DateType extends Component {
export default class DateType extends Component<DateTypeProps, DateTypeState> {
static defaultProps: DateTypeProps = {
knob: {} as any,
onChange: value => value,
};
static propTypes: WeakValidationMap<DateTypeProps> = {
// TODO: remove `any` once DefinitelyTyped/DefinitelyTyped#31280 has been resolved
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.number,
}) as any,
onChange: PropTypes.func,
};
static serialize = (value: DateTypeKnobValue) =>
new Date(value).getTime() || new Date().getTime();
static deserialize = (value: DateTypeKnobValue) =>
new Date(value).getTime() || new Date().getTime();
static getDerivedStateFromProps() {
return { valid: true };
}
state = {
state: DateTypeState = {
valid: undefined,
};
dateInput: HTMLInputElement;
timeInput: HTMLInputElement;
componentDidUpdate() {
const { knob } = this.props;
const { valid } = this.state;
@ -49,7 +88,7 @@ class DateType extends Component {
}
}
onDateChange = e => {
onDateChange = (e: ChangeEvent<HTMLInputElement>) => {
const { knob, onChange } = this.props;
const { state } = this;
@ -70,7 +109,7 @@ class DateType extends Component {
}
};
onTimeChange = e => {
onTimeChange = (e: ChangeEvent<HTMLInputElement>) => {
const { knob, onChange } = this.props;
const { state } = this;
@ -100,7 +139,7 @@ class DateType extends Component {
<FlexInput
type="date"
max="9999-12-31" // I do this because of a rendering bug in chrome
ref={el => {
ref={(el: HTMLInputElement) => {
this.dateInput = el;
}}
id={`${name}date`}
@ -111,7 +150,7 @@ class DateType extends Component {
type="time"
id={`${name}time`}
name={`${name}time`}
ref={el => {
ref={(el: HTMLInputElement) => {
this.timeInput = el;
}}
onChange={this.onTimeChange}
@ -121,21 +160,3 @@ class DateType extends Component {
) : null;
}
}
DateType.defaultProps = {
knob: {},
onChange: value => value,
};
DateType.propTypes = {
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.number,
}),
onChange: PropTypes.func,
};
DateType.serialize = value => new Date(value).getTime() || new Date().getTime();
DateType.deserialize = value => new Date(value).getTime() || new Date().getTime();
export default DateType;

View File

@ -1,46 +1,67 @@
// @ts-ignore
import { FileReader } from 'global';
import PropTypes from 'prop-types';
import React from 'react';
import React, { ChangeEvent, FunctionComponent } from 'react';
import { styled } from '@storybook/theming';
import { Form } from '@storybook/components';
type DateTypeKnobValue = string[];
interface FilesTypeProps {
knob: {
name: string;
accept: string;
value: DateTypeKnobValue;
};
onChange: (value: DateTypeKnobValue) => DateTypeKnobValue;
}
const FileInput = styled(Form.Input)({
paddingTop: 4,
});
function fileReaderPromise(file) {
return new Promise(resolve => {
function fileReaderPromise(file: File) {
return new Promise<string>(resolve => {
const fileReader = new FileReader();
fileReader.onload = e => resolve(e.currentTarget.result);
fileReader.onload = (e: Event) => resolve((e.currentTarget as FileReader).result);
fileReader.readAsDataURL(file);
});
}
const FilesType = ({ knob, onChange }) => (
const serialize = (): undefined => undefined;
const deserialize = (): undefined => undefined;
const FilesType: FunctionComponent<FilesTypeProps> & {
serialize: typeof serialize;
deserialize: typeof deserialize;
} = ({ knob, onChange }) => (
<FileInput
type="file"
name={knob.name}
multiple
onChange={e => Promise.all(Array.from(e.target.files).map(fileReaderPromise)).then(onChange)}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
Promise.all(Array.from(e.target.files).map(fileReaderPromise)).then(onChange)
}
accept={knob.accept}
size="flex"
/>
);
FilesType.defaultProps = {
knob: {},
knob: {} as any,
onChange: value => value,
};
FilesType.propTypes = {
// TODO: remove `any` once DefinitelyTyped/DefinitelyTyped#31280 has been resolved
knob: PropTypes.shape({
name: PropTypes.string,
}),
}) as any,
onChange: PropTypes.func,
};
FilesType.serialize = () => undefined;
FilesType.deserialize = () => undefined;
FilesType.serialize = serialize;
FilesType.deserialize = deserialize;
export default FilesType;

View File

@ -1,45 +1,78 @@
import PropTypes from 'prop-types';
import React from 'react';
import React, { Component, ChangeEvent } from 'react';
import { styled } from '@storybook/theming';
import { Form } from '@storybook/components';
const base = {
boxSizing: 'border-box',
height: '25px',
outline: 'none',
border: '1px solid #f7f4f4',
borderRadius: 2,
fontSize: 11,
padding: '5px',
color: '#444',
};
type NumberTypeKnobValue = number;
interface NumberTypeProps {
knob: {
name: string;
value: number;
range?: boolean;
min?: number;
max?: number;
step?: number;
};
onChange: (value: NumberTypeKnobValue) => NumberTypeKnobValue;
}
const RangeInput = styled.input(
{
boxSizing: 'border-box',
height: '25px',
outline: 'none',
border: '1px solid #f7f4f4',
borderRadius: 2,
fontSize: 11,
padding: '5px',
color: '#444',
},
{
display: 'table-cell',
flexGrow: 1,
}
);
const RangeInput = styled.input(base, {
display: 'table-cell',
flexGrow: 1,
});
const RangeLabel = styled.span({
paddingLeft: 5,
paddingRight: 5,
fontSize: 12,
whiteSpace: 'nowrap',
});
const RangeWrapper = styled.div({
display: 'flex',
alignItems: 'center',
width: '100%',
});
class NumberType extends React.Component {
shouldComponentUpdate(nextProps) {
export default class NumberType extends Component<NumberTypeProps> {
static propTypes = {
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
range: PropTypes.bool,
min: PropTypes.number,
max: PropTypes.number,
step: PropTypes.number,
}).isRequired,
onChange: PropTypes.func.isRequired,
};
static serialize = (value: NumberTypeKnobValue | null | undefined) =>
value === null || value === undefined ? '' : String(value);
static deserialize = (value: string) => (value === '' ? null : parseFloat(value));
shouldComponentUpdate(nextProps: NumberTypeProps) {
const { knob } = this.props;
return nextProps.knob.value !== knob.value;
}
handleChange = event => {
handleChange = (event: ChangeEvent<HTMLInputElement>) => {
const { onChange } = this.props;
const { value } = event.target;
@ -83,20 +116,3 @@ class NumberType extends React.Component {
);
}
}
NumberType.propTypes = {
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
range: PropTypes.bool,
min: PropTypes.number,
max: PropTypes.number,
step: PropTypes.number,
}).isRequired,
onChange: PropTypes.func.isRequired,
};
NumberType.serialize = value => (value === null || value === undefined ? '' : String(value));
NumberType.deserialize = value => (value === '' ? null : parseFloat(value));
export default NumberType;

View File

@ -1,17 +1,41 @@
import React, { Component } from 'react';
import React, { Component, ChangeEvent } from 'react';
import PropTypes from 'prop-types';
import deepEqual from 'fast-deep-equal';
// @ts-ignore
import { polyfill } from 'react-lifecycles-compat';
import { Form } from '@storybook/components';
class ObjectType extends Component {
state = {
value: {},
failed: false,
json: '',
interface ObjectTypeProps<T> {
knob: {
name: string;
value: T;
};
onChange: (value: T) => T;
}
interface ObjectTypeState<T> {
value: string;
failed: boolean;
json?: T;
}
class ObjectType<T> extends Component<ObjectTypeProps<T>> {
static propTypes = {
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
}).isRequired,
onChange: PropTypes.func.isRequired,
};
static getDerivedStateFromProps(props, state) {
static serialize: { <T>(object: T): string } = object => JSON.stringify(object);
static deserialize: { <T>(value: string): T } = value => (value ? JSON.parse(value) : {});
static getDerivedStateFromProps<T>(
props: ObjectTypeProps<T>,
state: ObjectTypeState<T>
): ObjectTypeState<T> {
if (!deepEqual(props.knob.value, state.json)) {
try {
return {
@ -26,7 +50,13 @@ class ObjectType extends Component {
return null;
}
handleChange = e => {
state: ObjectTypeState<T> = {
value: '',
failed: false,
json: {} as any,
};
handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
const { value } = e.target;
const { json: stateJson } = this.state;
const { knob, onChange } = this.props;
@ -65,17 +95,6 @@ class ObjectType extends Component {
}
}
ObjectType.propTypes = {
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
}).isRequired,
onChange: PropTypes.func.isRequired,
};
ObjectType.serialize = object => JSON.stringify(object);
ObjectType.deserialize = value => (value ? JSON.parse(value) : {});
polyfill(ObjectType);
export default ObjectType;

View File

@ -1,5 +1,6 @@
import React from 'react';
import React, { FunctionComponent } from 'react';
import PropTypes from 'prop-types';
// @ts-ignore
import ReactSelect from 'react-select';
import { styled } from '@storybook/theming';
@ -8,13 +9,67 @@ import CheckboxesType from './Checkboxes';
// TODO: Apply the Storybook theme to react-select
const OptionsSelect = styled(ReactSelect)({
export type OptionsKnobOptionsDisplay =
| 'radio'
| 'inline-radio'
| 'check'
| 'inline-check'
| 'select'
| 'multi-select';
export interface OptionsKnobOptions {
display?: OptionsKnobOptionsDisplay;
}
interface OptionsTypeProps<T> {
knob: {
name: string;
value: T;
defaultValue: T;
options: {
[key: string]: T;
};
optionsObj: OptionsKnobOptions;
};
display: OptionsKnobOptionsDisplay;
onChange: (value: T) => T;
}
const OptionsSelect: React.ComponentType<ReactSelectProps> = styled(ReactSelect)({
width: '100%',
maxWidth: '300px',
color: 'black',
});
const OptionsType = props => {
// TODO: These types should come from @types/react-select once installed.
type ReactSelectValueType<OptionType = { label: string; value: string }> =
| OptionType
| OptionsType<OptionType>
| null
| undefined;
type ReactSelectOnChangeFn<OptionType = { label: string; value: string }> = (
value: ReactSelectValueType<OptionType>
) => void;
interface ReactSelectProps {
value: OptionsSelectValueItem | OptionsSelectValueItem[];
options: any;
isMulti: boolean;
onChange: ReactSelectOnChangeFn;
}
interface OptionsSelectValueItem {
value: any;
label: string;
}
const serialize: { <T>(value: T): T } = value => value;
const deserialize: { <T>(value: T): T } = value => value;
const OptionsType: FunctionComponent<OptionsTypeProps<any>> & {
serialize: typeof serialize;
deserialize: typeof deserialize;
} = props => {
const { knob, onChange } = props;
const { display } = knob.optionsObj;
@ -29,19 +84,19 @@ const OptionsType = props => {
}
if (display === 'select' || display === 'multi-select') {
const options = Object.keys(knob.options).map(key => ({
const options: OptionsSelectValueItem[] = Object.keys(knob.options).map(key => ({
value: knob.options[key],
label: key,
}));
const isMulti = display === 'multi-select';
const optionsIndex = options.findIndex(i => i.value === knob.value);
let defaultValue = options[optionsIndex];
let handleChange = e => onChange(e.value);
let defaultValue: typeof options | typeof options[0] = options[optionsIndex];
let handleChange: ReactSelectOnChangeFn = (e: OptionsSelectValueItem) => onChange(e.value);
if (isMulti) {
defaultValue = options.filter(i => knob.value.includes(i.value));
handleChange = values => onChange(values.map(item => item.value));
handleChange = (values: OptionsSelectValueItem[]) => onChange(values.map(item => item.value));
}
return (
@ -53,33 +108,35 @@ const OptionsType = props => {
/>
);
}
return null;
};
OptionsType.defaultProps = {
knob: {},
knob: {} as any,
display: 'select',
onChange: value => value,
};
OptionsType.propTypes = {
// TODO: remove `any` once DefinitelyTyped/DefinitelyTyped#31280 has been resolved
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.array, PropTypes.string]),
options: PropTypes.object,
}),
display: PropTypes.oneOf([
'check',
'inline-check',
}) as any,
display: PropTypes.oneOf<OptionsKnobOptionsDisplay>([
'radio',
'inline-radio',
'check',
'inline-check',
'select',
'multi-select',
]),
onChange: PropTypes.func,
};
OptionsType.serialize = value => value;
OptionsType.deserialize = value => value;
OptionsType.serialize = serialize;
OptionsType.deserialize = deserialize;
export default OptionsType;

View File

@ -1,8 +1,29 @@
import React, { Component } from 'react';
import React, { Component, WeakValidationMap } from 'react';
import PropTypes from 'prop-types';
import { styled } from '@storybook/theming';
const RadiosWrapper = styled.div(({ isInline }) =>
type RadiosTypeKnobValue = string;
interface RadiosTypeKnobProp {
name: string;
value: RadiosTypeKnobValue;
defaultValue: RadiosTypeKnobValue;
options: {
[key: string]: RadiosTypeKnobValue;
};
}
interface RadiosTypeProps {
knob: RadiosTypeKnobProp;
isInline: boolean;
onChange: (value: RadiosTypeKnobValue) => RadiosTypeKnobValue;
}
interface RadiosWrapperProps {
isInline: boolean;
}
const RadiosWrapper = styled.div(({ isInline }: RadiosWrapperProps) =>
isInline
? {
display: 'flex',
@ -21,15 +42,36 @@ const RadioLabel = styled.label({
display: 'inline-block',
});
class RadiosType extends Component {
renderRadioButtonList({ options }) {
class RadiosType extends Component<RadiosTypeProps> {
static defaultProps: RadiosTypeProps = {
knob: {} as any,
onChange: value => value,
isInline: false,
};
static propTypes: WeakValidationMap<RadiosTypeProps> = {
// TODO: remove `any` once DefinitelyTyped/DefinitelyTyped#31280 has been resolved
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.string,
options: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
}) as any,
onChange: PropTypes.func,
isInline: PropTypes.bool,
};
static serialize = (value: RadiosTypeKnobValue) => value;
static deserialize = (value: RadiosTypeKnobValue) => value;
renderRadioButtonList({ options }: RadiosTypeKnobProp) {
if (Array.isArray(options)) {
return options.map(val => this.renderRadioButton(val, val));
}
return Object.keys(options).map(key => this.renderRadioButton(key, options[key]));
}
renderRadioButton(label, value) {
renderRadioButton(label: string, value: RadiosTypeKnobValue) {
const opts = { label, value };
const { onChange, knob } = this.props;
const { name } = knob;
@ -57,23 +99,4 @@ class RadiosType extends Component {
}
}
RadiosType.defaultProps = {
knob: {},
onChange: value => value,
isInline: false,
};
RadiosType.propTypes = {
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.string,
options: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
}),
onChange: PropTypes.func,
isInline: PropTypes.bool,
};
RadiosType.serialize = value => value;
RadiosType.deserialize = value => value;
export default RadiosType;

View File

@ -1,9 +1,28 @@
import React from 'react';
import React, { FunctionComponent, ChangeEvent } from 'react';
import PropTypes from 'prop-types';
import { Form } from '@storybook/components';
const SelectType = ({ knob, onChange }) => {
type SelectTypeKnobValue = string;
interface SelectTypeProps {
knob: {
name: string;
value: SelectTypeKnobValue;
options: {
[key: string]: SelectTypeKnobValue;
};
};
onChange: (value: SelectTypeKnobValue) => SelectTypeKnobValue;
}
const serialize = (value: SelectTypeKnobValue) => value;
const deserialize = (value: SelectTypeKnobValue) => value;
const SelectType: FunctionComponent<SelectTypeProps> & {
serialize: typeof serialize;
deserialize: typeof deserialize;
} = ({ knob, onChange }) => {
const { options } = knob;
const entries = Array.isArray(options)
? options.reduce((acc, k) => Object.assign(acc, { [k]: k }), {})
@ -15,7 +34,7 @@ const SelectType = ({ knob, onChange }) => {
<Form.Select
value={selectedKey}
name={knob.name}
onChange={e => {
onChange={(e: ChangeEvent<HTMLSelectElement>) => {
onChange(entries[e.target.value]);
}}
size="flex"
@ -30,20 +49,21 @@ const SelectType = ({ knob, onChange }) => {
};
SelectType.defaultProps = {
knob: {},
knob: {} as any,
onChange: value => value,
};
SelectType.propTypes = {
// TODO: remove `any` once DefinitelyTyped/DefinitelyTyped#31280 has been resolved
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.any,
options: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
}),
}) as any,
onChange: PropTypes.func,
};
SelectType.serialize = value => value;
SelectType.deserialize = value => value;
SelectType.serialize = serialize;
SelectType.deserialize = deserialize;
export default SelectType;

View File

@ -1,16 +1,44 @@
import PropTypes from 'prop-types';
import React from 'react';
import React, { Component, ChangeEvent, WeakValidationMap } from 'react';
import { Form } from '@storybook/components';
class TextType extends React.Component {
shouldComponentUpdate(nextProps) {
type TextTypeKnobValue = string;
interface TextTypeProps {
knob: {
name: string;
value: TextTypeKnobValue;
};
onChange: (value: TextTypeKnobValue) => TextTypeKnobValue;
}
export default class TextType extends Component<TextTypeProps> {
static defaultProps: TextTypeProps = {
knob: {} as any,
onChange: value => value,
};
static propTypes: WeakValidationMap<TextTypeProps> = {
// TODO: remove `any` once DefinitelyTyped/DefinitelyTyped#31280 has been resolved
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.string,
}) as any,
onChange: PropTypes.func,
};
static serialize = (value: TextTypeKnobValue) => value;
static deserialize = (value: TextTypeKnobValue) => value;
shouldComponentUpdate(nextProps: TextTypeProps) {
const { knob } = this.props;
return nextProps.knob.value !== knob.value;
}
handleChange = event => {
handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
const { onChange } = this.props;
const { value } = event.target;
@ -31,21 +59,3 @@ class TextType extends React.Component {
);
}
}
TextType.defaultProps = {
knob: {},
onChange: value => value,
};
TextType.propTypes = {
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.string,
}),
onChange: PropTypes.func,
};
TextType.serialize = value => value;
TextType.deserialize = value => value;
export default TextType;