From fc65863fe859db3b926c7d14a2d504a534429da3 Mon Sep 17 00:00:00 2001 From: casserni Date: Wed, 14 Nov 2018 13:35:03 -0600 Subject: [PATCH 1/8] feat(checkbox): allow checkbox to be either controlled or uncontrolled --- src/Checkbox.tsx | 49 +++++++++++++++++++++++++++++++------------- stories/Checkbox.tsx | 22 +++++++++++++++++--- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/Checkbox.tsx b/src/Checkbox.tsx index 2ceca702..db3fd256 100644 --- a/src/Checkbox.tsx +++ b/src/Checkbox.tsx @@ -7,27 +7,46 @@ import { Input } from './Input'; import { styled } from './utils'; export interface ICheckboxProps { - checked?: boolean; - disabled?: boolean; id?: string; className?: string; + checked?: boolean; + disabled?: boolean; width?: string; height?: string; - onChange?: React.ChangeEventHandler; + onChange?: (checked: boolean) => void; +} + +export interface ICheckboxState { + checked?: boolean; } -export const Checkbox = styled((props: ICheckboxProps) => { - const { id, className, checked, width, height, disabled, onChange } = props; +export class BasicCheckbox extends React.Component { + constructor(props: ICheckboxProps) { + super(props); + this.state = { checked: this.props.checked }; + } - return ( - - + private onChange = (event: React.ChangeEvent) => { + this.setState({ checked: event.target.checked }); + + if (this.props.onChange) { + this.props.onChange(event.target.checked); + } + }; + + public render() { + const { checked: stateChecked } = this.state; + const { id, className, width, height, disabled, checked: propsChecked } = this.props; + + const checked = this.props.hasOwnProperty('checked') ? propsChecked : stateChecked; + + return ( + @@ -52,6 +71,8 @@ export const Checkbox = styled((props: ICheckboxProps) => { {checked && } - - ); -})``; + ); + } +} + +export const Checkbox = styled(BasicCheckbox as any)``; diff --git a/stories/Checkbox.tsx b/stories/Checkbox.tsx index 6cf6c99a..624bc2d8 100644 --- a/stories/Checkbox.tsx +++ b/stories/Checkbox.tsx @@ -1,4 +1,5 @@ -import { action } from '@storybook/addon-actions'; +// @ts-ignore +import { StateDecorator, Store } from '@sambego/storybook-state'; import { withKnobs } from '@storybook/addon-knobs'; import { boolean } from '@storybook/addon-knobs/react'; import { storiesOf } from '@storybook/react'; @@ -6,13 +7,28 @@ import * as React from 'react'; import { Checkbox } from '../src/Checkbox'; +const store = new Store({ + checked: false, +}); + export const checkboxKnobs = (tabName = 'Checkbox') => { return { - checked: boolean('checked', false, tabName), disabled: boolean('disabled', false, tabName), }; }; storiesOf('Checkbox', module) .addDecorator(withKnobs) - .add('with defaults', () => ); + .add('uncontrolled', () => ) + .add('controlled checked', () => ) + .addDecorator(StateDecorator(store)) + .add('controlled', () => ( + { + store.set({ checked }); + }} + /> + )); From 46d06473e454eb8fc691bf94e3b6b8f9e79d8db4 Mon Sep 17 00:00:00 2001 From: casserni Date: Wed, 14 Nov 2018 13:40:08 -0600 Subject: [PATCH 2/8] feat(toggle): allow toggle to be either controlled or uncontrolled --- src/Toggle.tsx | 48 +++++++++++++++++++++++++++++++++------------- stories/Toggle.tsx | 22 ++++++++++++++++++--- 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/Toggle.tsx b/src/Toggle.tsx index ac7763c3..43fc992a 100644 --- a/src/Toggle.tsx +++ b/src/Toggle.tsx @@ -7,27 +7,47 @@ import { Input } from './Input'; import { styled } from './utils'; export interface IToggleProps { - checked: boolean; - disabled?: boolean; id?: string; className?: string; + checked?: boolean; + disabled?: boolean; width?: string; height?: string; - onChange?: React.ChangeEventHandler; + onChange?: (checked: boolean) => void; } -export const Toggle = styled((props: IToggleProps) => { - const { id, className, checked, width, height, disabled, onChange } = props; +export interface IToggleState { + checked?: boolean; +} + +export class BasicToggle extends React.Component { + constructor(props: IToggleProps) { + super(props); + this.state = { checked: this.props.checked }; + } + + private onChange = (event: React.ChangeEvent) => { + this.setState({ checked: event.target.checked }); - return ( - - + if (this.props.onChange) { + this.props.onChange(event.target.checked); + } + }; + + public render() { + const { checked: stateChecked } = this.state; + + const { id, className, width, height, disabled, checked: propsChecked } = this.props; + const checked = this.props.hasOwnProperty('checked') ? propsChecked : stateChecked; + + return ( + @@ -58,6 +78,8 @@ export const Toggle = styled((props: IToggleProps) => { /> - - ); -})``; + ); + } +} + +export const Toggle = styled(BasicToggle as any)``; diff --git a/stories/Toggle.tsx b/stories/Toggle.tsx index 0ba256ba..360693d5 100644 --- a/stories/Toggle.tsx +++ b/stories/Toggle.tsx @@ -1,4 +1,5 @@ -import { action } from '@storybook/addon-actions'; +// @ts-ignore +import { StateDecorator, Store } from '@sambego/storybook-state'; import { withKnobs } from '@storybook/addon-knobs'; import { boolean } from '@storybook/addon-knobs/react'; import { storiesOf } from '@storybook/react'; @@ -6,13 +7,28 @@ import * as React from 'react'; import { Toggle } from '../src/Toggle'; +const store = new Store({ + checked: false, +}); + export const toggleKnobs = (tabName = 'Toggle') => { return { - checked: boolean('checked', false, tabName), disabled: boolean('disabled', false, tabName), }; }; storiesOf('Toggle', module) .addDecorator(withKnobs) - .add('with defaults', () => ); + .add('uncontrolled', () => ) + .add('controlled checked', () => ) + .addDecorator(StateDecorator(store)) + .add('controlled', () => ( + { + store.set({ checked }); + }} + /> + )); From 83751e202666e98b5a5e73c0aba863b2baa18e66 Mon Sep 17 00:00:00 2001 From: casserni Date: Wed, 14 Nov 2018 14:16:02 -0600 Subject: [PATCH 3/8] feat(input): allow Input to be either controlled or uncontrolled --- src/Input.tsx | 40 +++++++++++++++++++++++++++++++++------- stories/Input.tsx | 5 +++-- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/Input.tsx b/src/Input.tsx index 81349b74..a8e8345f 100644 --- a/src/Input.tsx +++ b/src/Input.tsx @@ -7,16 +7,41 @@ import { styled } from './utils'; export interface IInputProps extends ITextProps { type?: string; autosize?: boolean; - as?: any; + value?: string | number; + onChange?: (value: string | number) => void; } -const StyledAutosizeInput = ({ autosize, className, ...rest }: IInputProps) => ( - -); +export interface IInputState { + value?: string | number; +} + +export class BasicInput extends React.Component { + constructor(props: IInputProps) { + super(props); + this.state = { value: this.props.value }; + } + + private onChange = (event: React.ChangeEvent) => { + this.setState({ value: event.target.value }); + + if (this.props.onChange) { + this.props.onChange(event.target.value); + } + }; + + public render() { + const { autosize, className, ...rest } = this.props; + const value = (this.props.hasOwnProperty('value') ? this.props.value : this.state.value) || ''; + + if (autosize) { + return ; + } + + return React.createElement('input', { className, ...rest, value, onChange: this.onChange }); + } +} -export const Input = styled(Text as any).attrs({ - as: ({ autosize }: IInputProps) => (autosize ? StyledAutosizeInput : 'input'), -})( +export const Input = styled(Text as any)( { // @ts-ignore ':focus': { @@ -34,6 +59,7 @@ export const Input = styled(Text as any).attrs({ ); Input.defaultProps = { + as: BasicInput, px: 'md', py: 'sm', border: 'xs', diff --git a/stories/Input.tsx b/stories/Input.tsx index 31b2dd0d..9c3b6407 100644 --- a/stories/Input.tsx +++ b/stories/Input.tsx @@ -38,6 +38,7 @@ export const autosizeInputKnobs = (tabName = 'Input'): any => { storiesOf('Input', module) .addDecorator(withKnobs) + .add('uncontrolled', () => ) .addDecorator(StateDecorator(store)) .add('with defaults', () => ( store.set({ value: e.target.value })} + onChange={(value: any) => store.set({ value })} /> )) .add('autosize', () => ( @@ -55,6 +56,6 @@ storiesOf('Input', module) {...boxKnobs()} autosize value={store.get('value')} - onChange={(e: any) => store.set({ value: e.target.value })} + onChange={(value: any) => store.set({ value })} /> )); From af97f18db43fa553359fe7fe8e6b7900002ff2c8 Mon Sep 17 00:00:00 2001 From: casserni Date: Wed, 14 Nov 2018 14:26:53 -0600 Subject: [PATCH 4/8] feat(textarea): allow textarea to be either controlled or uncontrolled --- src/Textarea.tsx | 40 ++++++++++++++++++++++++++++++++++++---- stories/Textarea.tsx | 25 +++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/src/Textarea.tsx b/src/Textarea.tsx index 909a44bf..3898aabf 100644 --- a/src/Textarea.tsx +++ b/src/Textarea.tsx @@ -1,3 +1,4 @@ +import * as React from 'react'; import AutosizeTextarea from 'react-textarea-autosize'; import { ITextProps, Text } from './Text'; @@ -7,12 +8,42 @@ export interface ITextareaProps extends ITextProps { autosize?: boolean; minRows?: number; maxRows?: number; - as?: any; + + value?: string; + onChange?: (value: string) => void; +} + +export interface ITextareaState { + value?: string; +} + +export class BasicTextArea extends React.Component { + constructor(props: ITextareaProps) { + super(props); + this.state = { value: this.props.value }; + } + + private onChange = (event: React.ChangeEvent) => { + this.setState({ value: event.target.value }); + + if (this.props.onChange) { + this.props.onChange(event.target.value); + } + }; + + public render() { + const { autosize, minRows, maxRows, ...rest } = this.props; + const value = (this.props.hasOwnProperty('value') ? this.props.value : this.state.value) || ''; + + if (autosize) { + return ; + } + + return React.createElement('textarea', { ...rest, value, onChange: this.onChange }); + } } -export const Textarea = styled(Text as any).attrs({ - as: ({ autosize }: ITextareaProps) => (autosize ? AutosizeTextarea : 'textarea'), -})( +export const Textarea = styled(Text as any)( { // @ts-ignore ':focus': { @@ -34,6 +65,7 @@ export const Textarea = styled(Text as any).attrs({ ); Textarea.defaultProps = { + as: BasicTextArea, px: 'md', py: 'sm', border: 'xs', diff --git a/stories/Textarea.tsx b/stories/Textarea.tsx index 8909febc..13a4a09a 100644 --- a/stories/Textarea.tsx +++ b/stories/Textarea.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; +// @ts-ignore +import { StateDecorator, Store } from '@sambego/storybook-state'; import { withKnobs } from '@storybook/addon-knobs'; import { boolean, number } from '@storybook/addon-knobs/react'; import { storiesOf } from '@storybook/react'; @@ -9,6 +11,10 @@ import { Textarea } from '../src/Textarea'; import { boxKnobs } from './Box'; import { textKnobs } from './Text'; +const store = new Store({ + value: 'TextArea Text', +}); + export const textareaKnobs = (tabName = 'Textarea'): any => { return omitBy( { @@ -49,9 +55,24 @@ export const textareaAutosizeKnobs = (tabName = 'Textarea'): any => { storiesOf('Textarea', module) .addDecorator(withKnobs) + .add('uncontrolled', () =>