mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-06 07:21:16 +08:00
Addon-knobs: migrate components
This commit is contained in:
parent
dfbf5d9cb5
commit
754ec02628
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user