diff --git a/package.json b/package.json index 481695ef..1f98b5c3 100644 --- a/package.json +++ b/package.json @@ -48,11 +48,14 @@ "@fortawesome/react-fontawesome": "0.1.3", "@types/enzyme": "^3.1.15", "@types/lodash": "4.x.x", + "@types/react": "16.x.x", + "@types/react-dom": "16.x.x", "@types/react-input-autosize": "2.0.x", "@types/react-textarea-autosize": "4.3.x", "@types/styled-components": "4.0.x", "@types/styled-system": "3.0.x", "lodash": "4.x.x", + "react-dom": "^16.7.0-alpha.2", "react-contextmenu": "2.10.x", "react-input-autosize": "2.2.x", "react-textarea-autosize": "7.1.0-1", @@ -65,14 +68,11 @@ "@fortawesome/free-solid-svg-icons": "5.5.x", "@sambego/storybook-state": "1.x.x", "@stoplight/scripts": "1.2.3", - "@types/react": "16.x.x", - "@types/react-dom": "16.x.x", "enzyme": "^3.7.0", "enzyme-adapter-react-16": "^1.7.0", "enzyme-to-json": "^3.3.4", "jest-enzyme": "^7.0.1", - "react": "16.x.x", - "react-dom": "16.x.x", + "react": "^16.7.0-alpha.2", "storybook-chromatic": "1.2.x", "typescript": "3.1.6" }, diff --git a/src/Checkbox.tsx b/src/Checkbox.tsx index 2ceca702..0b469652 100644 --- a/src/Checkbox.tsx +++ b/src/Checkbox.tsx @@ -1,57 +1,63 @@ +import noop = require('lodash/noop'); import * as React from 'react'; import { Box } from './Box'; import { Flex } from './Flex'; import { Icon } from './Icon'; -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 const Checkbox = styled((props: ICheckboxProps) => { - const { id, className, checked, width, height, disabled, onChange } = props; +export const BasicCheckbox = (props: ICheckboxProps) => { + const { id, className, width, height, disabled, onChange = noop } = props; + + const [checked, setValue] = React.useState(props.checked || false); + const isChecked = props.hasOwnProperty('checked') ? props.checked : checked; + + const handleChange = (event: React.ChangeEvent) => { + setValue(event.target.checked); + onChange(event.target.checked); + }; return ( - - - - - {checked && } - - - + + + + {isChecked && } + + ); -})``; +}; + +export const Checkbox = styled(BasicCheckbox as any)``; diff --git a/src/Input.tsx b/src/Input.tsx index 81349b74..d2e7e440 100644 --- a/src/Input.tsx +++ b/src/Input.tsx @@ -1,3 +1,4 @@ +import noop = require('lodash/noop'); import * as React from 'react'; import AutosizeInput from 'react-input-autosize'; @@ -7,16 +8,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 const BasicInput = (props: IInputProps) => { + const { autosize, className, onChange = noop, ...rest } = props; + + const [value, setValue] = React.useState(props.value || ''); + const internalValue = props.hasOwnProperty('value') ? props.value : value; + + const handleChange = (event: React.ChangeEvent) => { + setValue(event.target.value); + onChange(event.target.value); + }; + + if (autosize) { + return ( + + ); + } + + return React.createElement('input', { className, ...rest, value: internalValue, onChange: handleChange }); +}; -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 +60,7 @@ export const Input = styled(Text as any).attrs({ ); Input.defaultProps = { + as: BasicInput, px: 'md', py: 'sm', border: 'xs', diff --git a/src/Textarea.tsx b/src/Textarea.tsx index 909a44bf..1b835241 100644 --- a/src/Textarea.tsx +++ b/src/Textarea.tsx @@ -1,3 +1,5 @@ +import noop = require('lodash/noop'); +import * as React from 'react'; import AutosizeTextarea from 'react-textarea-autosize'; import { ITextProps, Text } from './Text'; @@ -7,12 +9,32 @@ export interface ITextareaProps extends ITextProps { autosize?: boolean; minRows?: number; maxRows?: number; - as?: any; + + value?: string; + onChange?: (value: string) => void; } -export const Textarea = styled(Text as any).attrs({ - as: ({ autosize }: ITextareaProps) => (autosize ? AutosizeTextarea : 'textarea'), -})( +export const BasicTextArea = (props: ITextareaProps) => { + const { autosize, minRows, maxRows, onChange = noop, ...rest } = props; + + const [value, setValue] = React.useState(props.value || ''); + const internalValue = props.hasOwnProperty('value') ? props.value : value; + + const handleChange = (event: React.ChangeEvent) => { + setValue(event.target.value); + onChange(event.target.value); + }; + + if (autosize) { + return ( + + ); + } + + return React.createElement('textarea', { ...rest, value: internalValue, onChange: handleChange }); +}; + +export const Textarea = styled(Text as any)( { // @ts-ignore ':focus': { @@ -34,6 +56,7 @@ export const Textarea = styled(Text as any).attrs({ ); Textarea.defaultProps = { + as: BasicTextArea, px: 'md', py: 'sm', border: 'xs', diff --git a/src/Toggle.tsx b/src/Toggle.tsx index ac7763c3..44dcf702 100644 --- a/src/Toggle.tsx +++ b/src/Toggle.tsx @@ -1,63 +1,70 @@ +import noop = require('lodash/noop'); import * as React from 'react'; import { Box } from './Box'; import { Flex } from './Flex'; import { Icon } from './Icon'; -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 const BasicToggle = (props: IToggleProps) => { + const { id, className, width, height, disabled, onChange = noop } = props; + + const [checked, setValue] = React.useState(props.checked || false); + const isChecked = props.hasOwnProperty('checked') ? props.checked : checked; + + const handleChange = (event: React.ChangeEvent) => { + setValue(event.target.checked); + onChange(event.target.checked); + }; return ( - - - - + + + - - - - + /> + + ); -})``; +}; + +export const Toggle = styled(BasicToggle as any)``; diff --git a/stories/Checkbox.tsx b/stories/Checkbox.tsx index 6cf6c99a..0764de28 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 set', () => ) + .addDecorator(StateDecorator(store)) + .add('controlled', () => ( + { + store.set({ checked }); + }} + /> + )); diff --git a/stories/Input.tsx b/stories/Input.tsx index 31b2dd0d..c92d4d51 100644 --- a/stories/Input.tsx +++ b/stories/Input.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; // @ts-ignore import { StateDecorator, Store } from '@sambego/storybook-state'; import { withKnobs } from '@storybook/addon-knobs'; -import { boolean, select } from '@storybook/addon-knobs/react'; +import { boolean, select, text } from '@storybook/addon-knobs/react'; import { storiesOf } from '@storybook/react'; import { omitBy } from 'lodash'; @@ -21,6 +21,7 @@ export const inputKnobs = (tabName = 'Input'): any => { { disabled: boolean('disabled', false, tabName), type: select('type', InlineInputType, 'text', tabName), + placeholder: text('placeholder', 'placeholder', tabName), }, val => !val ); @@ -31,6 +32,7 @@ export const autosizeInputKnobs = (tabName = 'Input'): any => { { disabled: boolean('disabled', false, tabName), type: select('type', AutosizeInputType, 'text', tabName), + placeholder: text('placeholder', 'placeholder', tabName), }, val => !val ); @@ -38,23 +40,16 @@ export const autosizeInputKnobs = (tabName = 'Input'): any => { storiesOf('Input', module) .addDecorator(withKnobs) + .add('uncontrolled', () => ) + .add('autosize', () => ) + .add('controlled set', () => ) .addDecorator(StateDecorator(store)) - .add('with defaults', () => ( + .add('controlled store', () => ( store.set({ value: e.target.value })} - /> - )) - .add('autosize', () => ( - store.set({ value: e.target.value })} + onChange={(value: any) => store.set({ value })} /> )); diff --git a/stories/Textarea.tsx b/stories/Textarea.tsx index 8909febc..d859aad5 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,16 @@ export const textareaAutosizeKnobs = (tabName = 'Textarea'): any => { storiesOf('Textarea', module) .addDecorator(withKnobs) - .add('with defaults', () => ( -