From 29a5f2b09c43e845ea40a390fed7aa91fae762ea Mon Sep 17 00:00:00 2001 From: Hossein Mehrabi Date: Mon, 27 Feb 2023 13:27:01 +0330 Subject: [PATCH 1/8] feat: implement Checkbox component --- .../components/CSSBaseline/CSSBaseline.tsx | 2 + .../components/Checkbox/Checkbox.classes.ts | 15 +++ .../components/Checkbox/Checkbox.stories.tsx | 28 ++++++ .../components/Checkbox/Checkbox.styles.ts | 59 +++++++++++ .../src/components/Checkbox/Checkbox.tsx | 97 +++++++++++++++++++ .../src/components/Checkbox/index.ts | 1 + .../CheckboxIndeterminateIcon.tsx | 24 +++++ .../Icons/CheckboxIndeterminate/index.ts | 1 + packages/lsd-react/src/index.ts | 1 + packages/lsd-react/src/utils/useInput.ts | 23 +++-- 10 files changed, 241 insertions(+), 10 deletions(-) create mode 100644 packages/lsd-react/src/components/Checkbox/Checkbox.classes.ts create mode 100644 packages/lsd-react/src/components/Checkbox/Checkbox.stories.tsx create mode 100644 packages/lsd-react/src/components/Checkbox/Checkbox.styles.ts create mode 100644 packages/lsd-react/src/components/Checkbox/Checkbox.tsx create mode 100644 packages/lsd-react/src/components/Checkbox/index.ts create mode 100644 packages/lsd-react/src/components/Icons/CheckboxIndeterminate/CheckboxIndeterminateIcon.tsx create mode 100644 packages/lsd-react/src/components/Icons/CheckboxIndeterminate/index.ts diff --git a/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx b/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx index f36c9e3..c5f38f5 100644 --- a/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx +++ b/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx @@ -7,6 +7,7 @@ import { ButtonStyles } from '../Button/Button.styles' import { CardStyles } from '../Card/Card.styles' import { CardBodyStyles } from '../CardBody/CardBody.styles' import { CardHeaderStyles } from '../CardHeader/CardHeader.styles' +import { CheckboxStyles } from '../Checkbox/Checkbox.styles' import { CollapseStyles } from '../Collapse/Collapse.styles' import { CollapseHeaderStyles } from '../CollapseHeader/CollapseHeader.styles' import { DropdownStyles } from '../Dropdown/Dropdown.styles' @@ -40,6 +41,7 @@ const componentStyles: Array | SerializedStyles> = CardBodyStyles, TagStyles, TextFieldStyles, + CheckboxStyles, AutocompleteStyles, QuoteStyles, CollapseStyles, diff --git a/packages/lsd-react/src/components/Checkbox/Checkbox.classes.ts b/packages/lsd-react/src/components/Checkbox/Checkbox.classes.ts new file mode 100644 index 0000000..8d224d5 --- /dev/null +++ b/packages/lsd-react/src/components/Checkbox/Checkbox.classes.ts @@ -0,0 +1,15 @@ +export const checkboxClasses = { + root: `lsd-checkbox`, + + input: `lsd-checkbox__input`, + icon: `lsd-checkbox__icon`, + label: `lsd-checkbox__label`, + + focused: `lsd-checkbox--focused`, + disabled: `lsd-checkbox--disabled`, + indeterminate: 'lsd-checkbox--indeterminate', + + large: `lsd-checkbox--large`, + medium: `lsd-checkbox--medium`, + small: 'lsd-checkbox--small', +} diff --git a/packages/lsd-react/src/components/Checkbox/Checkbox.stories.tsx b/packages/lsd-react/src/components/Checkbox/Checkbox.stories.tsx new file mode 100644 index 0000000..0bed9b3 --- /dev/null +++ b/packages/lsd-react/src/components/Checkbox/Checkbox.stories.tsx @@ -0,0 +1,28 @@ +import { Meta, Story } from '@storybook/react' +import { Checkbox, CheckboxProps } from './Checkbox' + +export default { + title: 'Checkbox', + component: Checkbox, + argTypes: { + size: { + type: { + name: 'enum', + value: ['small', 'medium', 'large'], + }, + defaultValue: 'large', + }, + }, +} as Meta + +export const Root: Story = (args) => ( + Checkbox +) + +Root.args = { + size: 'large', + disabled: false, + indeterminate: false, + checked: undefined, + onChange: undefined, +} diff --git a/packages/lsd-react/src/components/Checkbox/Checkbox.styles.ts b/packages/lsd-react/src/components/Checkbox/Checkbox.styles.ts new file mode 100644 index 0000000..bde6ed5 --- /dev/null +++ b/packages/lsd-react/src/components/Checkbox/Checkbox.styles.ts @@ -0,0 +1,59 @@ +import { css } from '@emotion/react' +import { checkboxClasses } from './Checkbox.classes' + +export const CheckboxStyles = css` + .${checkboxClasses.root} { + position: relative; + display: flex; + flex-direction: row; + align-items: center; + } + + .${checkboxClasses.input} { + opacity: 0; + position: absolute; + left: 0; + top: 0; + padding: 0; + margin: 0; + width: 100%; + height: 100%; + } + + .${checkboxClasses.root}:not(.${checkboxClasses.disabled}) { + &:hover, + &.${checkboxClasses.focused} { + text-decoration: underline; + } + + .${checkboxClasses.input} { + cursor: pointer; + } + } + + .${checkboxClasses.disabled} { + opacity: 0.34; + } + + .${checkboxClasses.label} { + margin-left: 18px; + } + + .${checkboxClasses.large} { + .${checkboxClasses.label} { + margin-left: 18px; + } + } + + .${checkboxClasses.medium} { + .${checkboxClasses.label} { + margin-left: 14px; + } + } + + .${checkboxClasses.small} { + .${checkboxClasses.label} { + margin-left: 12px; + } + } +` diff --git a/packages/lsd-react/src/components/Checkbox/Checkbox.tsx b/packages/lsd-react/src/components/Checkbox/Checkbox.tsx new file mode 100644 index 0000000..fca6fc7 --- /dev/null +++ b/packages/lsd-react/src/components/Checkbox/Checkbox.tsx @@ -0,0 +1,97 @@ +import clsx from 'clsx' +import React, { useEffect, useRef, useState } from 'react' +import { useInput } from '../../utils/useInput' +import { CheckboxFilledIcon, CheckboxIcon } from '../Icons' +import { CheckboxIndeterminateIcon } from '../Icons/CheckboxIndeterminate' +import { Typography } from '../Typography' +import { checkboxClasses } from './Checkbox.classes' + +export type CheckboxProps = Omit< + React.LabelHTMLAttributes, + 'onChange' | 'value' | 'color' +> & + Pick< + React.InputHTMLAttributes, + 'onChange' | 'checked' | 'defaultChecked' + > & { + disabled?: boolean + indeterminate?: boolean + size?: 'small' | 'medium' | 'large' + inputProps?: React.InputHTMLAttributes + } + +export const Checkbox: React.FC & { + classes: typeof checkboxClasses +} = ({ + size = 'large', + onChange, + checked, + defaultChecked, + disabled = false, + indeterminate = false, + inputProps = {}, + children, + ...props +}) => { + const ref = useRef(null) + const [focused, setFocused] = useState(false) + const input = useInput({ + value: checked, + defaultValue: defaultChecked ?? false, + onChange, + ref, + }) + + useEffect(() => { + if (!ref.current) return + + const onFocus = () => setFocused(true) + const onBlur = () => setFocused(false) + + ref.current.addEventListener('focus', onFocus) + ref.current.addEventListener('blur', onBlur) + + return () => { + ref.current?.removeEventListener('focus', onFocus) + ref.current?.removeEventListener('blur', onBlur) + } + }, [ref.current]) + + return ( + + + {indeterminate ? ( + + ) : input.value ? ( + + ) : ( + + )} + {children} + + ) +} + +Checkbox.classes = checkboxClasses diff --git a/packages/lsd-react/src/components/Checkbox/index.ts b/packages/lsd-react/src/components/Checkbox/index.ts new file mode 100644 index 0000000..0dab115 --- /dev/null +++ b/packages/lsd-react/src/components/Checkbox/index.ts @@ -0,0 +1 @@ +export * from './Checkbox' diff --git a/packages/lsd-react/src/components/Icons/CheckboxIndeterminate/CheckboxIndeterminateIcon.tsx b/packages/lsd-react/src/components/Icons/CheckboxIndeterminate/CheckboxIndeterminateIcon.tsx new file mode 100644 index 0000000..dad4ef2 --- /dev/null +++ b/packages/lsd-react/src/components/Icons/CheckboxIndeterminate/CheckboxIndeterminateIcon.tsx @@ -0,0 +1,24 @@ +import { LsdIcon } from '../LsdIcon' + +export const CheckboxIndeterminateIcon = LsdIcon( + (props) => ( + + + + ), + { + filled: true, + }, +) diff --git a/packages/lsd-react/src/components/Icons/CheckboxIndeterminate/index.ts b/packages/lsd-react/src/components/Icons/CheckboxIndeterminate/index.ts new file mode 100644 index 0000000..1775ffb --- /dev/null +++ b/packages/lsd-react/src/components/Icons/CheckboxIndeterminate/index.ts @@ -0,0 +1 @@ +export * from './CheckboxIndeterminateIcon' diff --git a/packages/lsd-react/src/index.ts b/packages/lsd-react/src/index.ts index 94a2dc2..36c389e 100644 --- a/packages/lsd-react/src/index.ts +++ b/packages/lsd-react/src/index.ts @@ -5,6 +5,7 @@ export * from './components/Button' export * from './components/Card' export * from './components/CardBody' export * from './components/CardHeader' +export * from './components/Checkbox' export * from './components/Collapse' export * from './components/CollapseHeader' export * from './components/Dropdown' diff --git a/packages/lsd-react/src/utils/useInput.ts b/packages/lsd-react/src/utils/useInput.ts index 9a8925e..8b2fd41 100644 --- a/packages/lsd-react/src/utils/useInput.ts +++ b/packages/lsd-react/src/utils/useInput.ts @@ -1,22 +1,23 @@ import React, { useEffect, useState } from 'react' export type InputValueType = - React.InputHTMLAttributes['value'] + | React.InputHTMLAttributes['value'] + | boolean export type InputOnChangeType = React.InputHTMLAttributes['onChange'] -export type InputProps = { - value?: InputValueType - defaultValue?: InputValueType +export type InputProps = { + value?: T + defaultValue: T onChange?: InputOnChangeType ref?: React.RefObject } -export const useInput = (props: InputProps) => { - const [value, setValue] = useState( - props.value ?? props.defaultValue ?? '', - ) +export const useInput = ( + props: InputProps, +) => { + const [value, setValue] = useState(props.value ?? props.defaultValue) const uncontrolled = typeof props.value === 'undefined' const filled = @@ -27,7 +28,9 @@ export const useInput = (props: InputProps) => { : value.toString().length > 0 const onChange: InputOnChangeType = (event) => { - if (uncontrolled) return setValue(event.target.value) + const type = event.target.type + const value = event.target[type === 'checkbox' ? 'checked' : 'value'] + if (uncontrolled) return setValue(value as T) props.onChange && props.onChange(event) } @@ -46,7 +49,7 @@ export const useInput = (props: InputProps) => { } useEffect(() => { - !uncontrolled && setValue(props.value) + !uncontrolled && setValue(props.value as T) }, [uncontrolled, props.value]) return { From 907dc4b5d0fb3d5db24a4bdfda5248666cacff2f Mon Sep 17 00:00:00 2001 From: Hossein Mehrabi Date: Tue, 28 Feb 2023 12:23:33 +0330 Subject: [PATCH 2/8] fix: fix Checkbox input's disabled state --- packages/lsd-react/src/components/Checkbox/Checkbox.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/lsd-react/src/components/Checkbox/Checkbox.tsx b/packages/lsd-react/src/components/Checkbox/Checkbox.tsx index fca6fc7..c827f3d 100644 --- a/packages/lsd-react/src/components/Checkbox/Checkbox.tsx +++ b/packages/lsd-react/src/components/Checkbox/Checkbox.tsx @@ -76,6 +76,7 @@ export const Checkbox: React.FC & { Date: Wed, 1 Mar 2023 14:02:09 +0330 Subject: [PATCH 3/8] feat: add name prop to Checkbox component --- packages/lsd-react/src/components/Checkbox/Checkbox.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/lsd-react/src/components/Checkbox/Checkbox.tsx b/packages/lsd-react/src/components/Checkbox/Checkbox.tsx index c827f3d..43c649c 100644 --- a/packages/lsd-react/src/components/Checkbox/Checkbox.tsx +++ b/packages/lsd-react/src/components/Checkbox/Checkbox.tsx @@ -12,7 +12,7 @@ export type CheckboxProps = Omit< > & Pick< React.InputHTMLAttributes, - 'onChange' | 'checked' | 'defaultChecked' + 'name' | 'onChange' | 'checked' | 'defaultChecked' > & { disabled?: boolean indeterminate?: boolean @@ -23,6 +23,7 @@ export type CheckboxProps = Omit< export const Checkbox: React.FC & { classes: typeof checkboxClasses } = ({ + name, size = 'large', onChange, checked, @@ -75,6 +76,7 @@ export const Checkbox: React.FC & { > Date: Tue, 28 Feb 2023 01:38:19 +0900 Subject: [PATCH 4/8] feat: implement radio button --- .../components/CSSBaseline/CSSBaseline.tsx | 2 + .../RadioButtonFilledIcon.tsx | 26 +++++++ .../Icons/RadioButtonFilledIcon/index.ts | 1 + .../Icons/RadioButtonIcon/RadioButtonIcon.tsx | 22 ++++++ .../components/Icons/RadioButtonIcon/index.ts | 1 + .../lsd-react/src/components/Icons/index.ts | 2 + .../RadioButton/RadioButton.classes.ts | 12 +++ .../RadioButton/RadioButton.stories.tsx | 27 +++++++ .../RadioButton/RadioButton.styles.ts | 58 +++++++++++++++ .../components/RadioButton/RadioButton.tsx | 74 +++++++++++++++++++ .../src/components/RadioButton/index.ts | 1 + packages/lsd-react/src/index.ts | 1 + 12 files changed, 227 insertions(+) create mode 100644 packages/lsd-react/src/components/Icons/RadioButtonFilledIcon/RadioButtonFilledIcon.tsx create mode 100644 packages/lsd-react/src/components/Icons/RadioButtonFilledIcon/index.ts create mode 100644 packages/lsd-react/src/components/Icons/RadioButtonIcon/RadioButtonIcon.tsx create mode 100644 packages/lsd-react/src/components/Icons/RadioButtonIcon/index.ts create mode 100644 packages/lsd-react/src/components/RadioButton/RadioButton.classes.ts create mode 100644 packages/lsd-react/src/components/RadioButton/RadioButton.stories.tsx create mode 100644 packages/lsd-react/src/components/RadioButton/RadioButton.styles.ts create mode 100644 packages/lsd-react/src/components/RadioButton/RadioButton.tsx create mode 100644 packages/lsd-react/src/components/RadioButton/index.ts diff --git a/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx b/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx index c5f38f5..91c0bf0 100644 --- a/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx +++ b/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx @@ -16,6 +16,7 @@ import { IconButtonStyles } from '../IconButton/IconButton.styles' import { LsdIconStyles } from '../Icons/LsdIcon/LsdIcon.styles' import { ListBoxStyles } from '../ListBox/ListBox.styles' import { QuoteStyles } from '../Quote/Quote.styles' +import { RadioButtonStyles } from '../RadioButton/RadioButton.styles' import { TabItemStyles } from '../TabItem/TabItem.styles' import { TabsStyles } from '../Tabs/Tabs.styles' import { TagStyles } from '../Tag/Tag.styles' @@ -46,6 +47,7 @@ const componentStyles: Array | SerializedStyles> = QuoteStyles, CollapseStyles, CollapseHeaderStyles, + RadioButtonStyles, ] export const CSSBaseline: React.FC<{ theme?: Theme }> = ({ diff --git a/packages/lsd-react/src/components/Icons/RadioButtonFilledIcon/RadioButtonFilledIcon.tsx b/packages/lsd-react/src/components/Icons/RadioButtonFilledIcon/RadioButtonFilledIcon.tsx new file mode 100644 index 0000000..9c78c51 --- /dev/null +++ b/packages/lsd-react/src/components/Icons/RadioButtonFilledIcon/RadioButtonFilledIcon.tsx @@ -0,0 +1,26 @@ +import { LsdIcon } from '../LsdIcon' + +export const RadioButtonFilledIcon = LsdIcon( + (props) => ( + + + + + ), + { + filled: true, + }, +) diff --git a/packages/lsd-react/src/components/Icons/RadioButtonFilledIcon/index.ts b/packages/lsd-react/src/components/Icons/RadioButtonFilledIcon/index.ts new file mode 100644 index 0000000..62143be --- /dev/null +++ b/packages/lsd-react/src/components/Icons/RadioButtonFilledIcon/index.ts @@ -0,0 +1 @@ +export * from './RadioButtonFilledIcon' diff --git a/packages/lsd-react/src/components/Icons/RadioButtonIcon/RadioButtonIcon.tsx b/packages/lsd-react/src/components/Icons/RadioButtonIcon/RadioButtonIcon.tsx new file mode 100644 index 0000000..b349332 --- /dev/null +++ b/packages/lsd-react/src/components/Icons/RadioButtonIcon/RadioButtonIcon.tsx @@ -0,0 +1,22 @@ +import { LsdIcon } from '../LsdIcon' + +export const RadioButtonIcon = LsdIcon( + (props) => ( + + + + ), + { + filled: true, + }, +) diff --git a/packages/lsd-react/src/components/Icons/RadioButtonIcon/index.ts b/packages/lsd-react/src/components/Icons/RadioButtonIcon/index.ts new file mode 100644 index 0000000..03275c3 --- /dev/null +++ b/packages/lsd-react/src/components/Icons/RadioButtonIcon/index.ts @@ -0,0 +1 @@ +export * from './RadioButtonIcon' diff --git a/packages/lsd-react/src/components/Icons/index.ts b/packages/lsd-react/src/components/Icons/index.ts index 40f1f7f..40d3d8f 100644 --- a/packages/lsd-react/src/components/Icons/index.ts +++ b/packages/lsd-react/src/components/Icons/index.ts @@ -17,3 +17,5 @@ export * from './NavigateNextIcon' export * from './NewPageIcon' export * from './SearchIcon' export * from './PickIcon' +export * from './RadioButtonIcon' +export * from './RadioButtonFilledIcon' diff --git a/packages/lsd-react/src/components/RadioButton/RadioButton.classes.ts b/packages/lsd-react/src/components/RadioButton/RadioButton.classes.ts new file mode 100644 index 0000000..6c88247 --- /dev/null +++ b/packages/lsd-react/src/components/RadioButton/RadioButton.classes.ts @@ -0,0 +1,12 @@ +export const radioButtonClasses = { + root: `lsd-radio-button`, + + input: `lsd-radio-button__input`, + label: `lsd-radio-button__label`, + + disabled: `lsd-radio-button--disabled`, + + large: `lsd-radio-button--large`, + medium: `lsd-radio-button--medium`, + small: 'lsd-radio-button--small', +} diff --git a/packages/lsd-react/src/components/RadioButton/RadioButton.stories.tsx b/packages/lsd-react/src/components/RadioButton/RadioButton.stories.tsx new file mode 100644 index 0000000..c27a36c --- /dev/null +++ b/packages/lsd-react/src/components/RadioButton/RadioButton.stories.tsx @@ -0,0 +1,27 @@ +import { Meta, Story } from '@storybook/react' +import { RadioButton, RadioButtonProps } from './RadioButton' + +export default { + title: 'RadioButton', + component: RadioButton, + argTypes: { + size: { + type: { + name: 'enum', + value: ['small', 'medium', 'large'], + }, + defaultValue: 'large', + }, + }, +} as Meta + +export const Root: Story = (args) => ( + RadioButton label +) + +Root.args = { + size: 'large', + disabled: false, + checked: undefined, + onChange: undefined, +} diff --git a/packages/lsd-react/src/components/RadioButton/RadioButton.styles.ts b/packages/lsd-react/src/components/RadioButton/RadioButton.styles.ts new file mode 100644 index 0000000..dc13696 --- /dev/null +++ b/packages/lsd-react/src/components/RadioButton/RadioButton.styles.ts @@ -0,0 +1,58 @@ +import { css } from '@emotion/react' +import { radioButtonClasses } from './RadioButton.classes' + +export const RadioButtonStyles = css` + .${radioButtonClasses.root} { + position: relative; + display: flex; + flex-direction: row; + align-items: center; + } + + .${radioButtonClasses.input} { + opacity: 0; + position: absolute; + left: 0; + top: 0; + padding: 0; + margin: 0; + width: 100%; + height: 100%; + } + + .${radioButtonClasses.root}:not(.${radioButtonClasses.disabled}) { + &:hover { + text-decoration: underline; + } + + .${radioButtonClasses.input} { + cursor: pointer; + } + } + + .${radioButtonClasses.disabled} { + opacity: 0.34; + } + + .${radioButtonClasses.label} { + margin-left: 18px; + } + + .${radioButtonClasses.large} { + .${radioButtonClasses.label} { + margin-left: 18px; + } + } + + .${radioButtonClasses.medium} { + .${radioButtonClasses.label} { + margin-left: 14px; + } + } + + .${radioButtonClasses.small} { + .${radioButtonClasses.label} { + margin-left: 12px; + } + } +` diff --git a/packages/lsd-react/src/components/RadioButton/RadioButton.tsx b/packages/lsd-react/src/components/RadioButton/RadioButton.tsx new file mode 100644 index 0000000..3e50b9b --- /dev/null +++ b/packages/lsd-react/src/components/RadioButton/RadioButton.tsx @@ -0,0 +1,74 @@ +import clsx from 'clsx' +import React, { useRef } from 'react' +import { useInput } from '../../utils/useInput' +import { RadioButtonFilledIcon, RadioButtonIcon } from '../Icons' +import { Typography } from '../Typography' +import { radioButtonClasses } from './RadioButton.classes' + +export type RadioButtonProps = Omit< + React.LabelHTMLAttributes, + 'onChange' | 'value' | 'color' +> & + Pick< + React.InputHTMLAttributes, + 'onChange' | 'checked' | 'defaultChecked' + > & { + disabled?: boolean + size?: 'small' | 'medium' | 'large' + inputProps?: React.InputHTMLAttributes + } + +export const RadioButton: React.FC & { + classes: typeof radioButtonClasses +} = ({ + size = 'large', + onChange, + checked, + defaultChecked, + disabled = false, + inputProps = {}, + children, + ...props +}) => { + const ref = useRef(null) + const input = useInput({ + value: checked, + defaultValue: defaultChecked ?? false, + onChange, + ref, + }) + + return ( + + + {input.value ? ( + + ) : ( + + )} + {children} + + ) +} + +RadioButton.classes = radioButtonClasses diff --git a/packages/lsd-react/src/components/RadioButton/index.ts b/packages/lsd-react/src/components/RadioButton/index.ts new file mode 100644 index 0000000..37d8240 --- /dev/null +++ b/packages/lsd-react/src/components/RadioButton/index.ts @@ -0,0 +1 @@ +export * from './RadioButton' diff --git a/packages/lsd-react/src/index.ts b/packages/lsd-react/src/index.ts index 36c389e..42bccf2 100644 --- a/packages/lsd-react/src/index.ts +++ b/packages/lsd-react/src/index.ts @@ -18,3 +18,4 @@ export * from './components/TabItem' export * from './components/Tabs' export * from './components/Tag' export * from './components/Theme' +export * from './components/RadioButton' From 929dc713e5150fc2c5472651903750a8d7abaeed Mon Sep 17 00:00:00 2001 From: jinhojang6 Date: Fri, 3 Mar 2023 02:55:29 +0900 Subject: [PATCH 5/8] feat: implement radio button group --- .../components/CSSBaseline/CSSBaseline.tsx | 2 + .../components/RadioButton/RadioButton.tsx | 23 +++++++-- .../RadioButtonGroup.classes.ts | 4 ++ .../RadioButtonGroup.context.ts | 18 +++++++ .../RadioButtonGroup.stories.tsx | 51 +++++++++++++++++++ .../RadioButtonGroup.styles.ts | 15 ++++++ .../RadioButtonGroup/RadioButtonGroup.tsx | 49 ++++++++++++++++++ .../src/components/RadioButtonGroup/index.ts | 1 + packages/lsd-react/src/index.ts | 1 + packages/lsd-react/src/utils/useInput.ts | 5 +- 10 files changed, 164 insertions(+), 5 deletions(-) create mode 100644 packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.classes.ts create mode 100644 packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.context.ts create mode 100644 packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.stories.tsx create mode 100644 packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.styles.ts create mode 100644 packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.tsx create mode 100644 packages/lsd-react/src/components/RadioButtonGroup/index.ts diff --git a/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx b/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx index 91c0bf0..2e3118b 100644 --- a/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx +++ b/packages/lsd-react/src/components/CSSBaseline/CSSBaseline.tsx @@ -17,6 +17,7 @@ import { LsdIconStyles } from '../Icons/LsdIcon/LsdIcon.styles' import { ListBoxStyles } from '../ListBox/ListBox.styles' import { QuoteStyles } from '../Quote/Quote.styles' import { RadioButtonStyles } from '../RadioButton/RadioButton.styles' +import { RadioButtonGroupStyles } from '../RadioButtonGroup/RadioButtonGroup.styles' import { TabItemStyles } from '../TabItem/TabItem.styles' import { TabsStyles } from '../Tabs/Tabs.styles' import { TagStyles } from '../Tag/Tag.styles' @@ -48,6 +49,7 @@ const componentStyles: Array | SerializedStyles> = CollapseStyles, CollapseHeaderStyles, RadioButtonStyles, + RadioButtonGroupStyles, ] export const CSSBaseline: React.FC<{ theme?: Theme }> = ({ diff --git a/packages/lsd-react/src/components/RadioButton/RadioButton.tsx b/packages/lsd-react/src/components/RadioButton/RadioButton.tsx index 3e50b9b..90ececf 100644 --- a/packages/lsd-react/src/components/RadioButton/RadioButton.tsx +++ b/packages/lsd-react/src/components/RadioButton/RadioButton.tsx @@ -2,6 +2,7 @@ import clsx from 'clsx' import React, { useRef } from 'react' import { useInput } from '../../utils/useInput' import { RadioButtonFilledIcon, RadioButtonIcon } from '../Icons' +import { useRadioButtonGroupContext } from '../RadioButtonGroup/RadioButtonGroup.context' import { Typography } from '../Typography' import { radioButtonClasses } from './RadioButton.classes' @@ -21,9 +22,9 @@ export type RadioButtonProps = Omit< export const RadioButton: React.FC & { classes: typeof radioButtonClasses } = ({ - size = 'large', + size: _size = 'large', onChange, - checked, + checked: _checked, defaultChecked, disabled = false, inputProps = {}, @@ -31,13 +32,27 @@ export const RadioButton: React.FC & { ...props }) => { const ref = useRef(null) + + const radioButtonGroup = useRadioButtonGroupContext() + const size = radioButtonGroup?.size ?? _size + const selected = radioButtonGroup + ? radioButtonGroup.activeRadioButton != null && + radioButtonGroup.activeRadioButton === inputProps?.value + : _checked + const input = useInput({ - value: checked, + value: selected, defaultValue: defaultChecked ?? false, onChange, ref, }) + const handleChange = (event: React.ChangeEvent) => { + onChange && onChange(event) + input.onChange(event) + inputProps.value && radioButtonGroup?.setActiveRadioButton(inputProps.value) + } + return ( & { ref={ref} type="radio" checked={input.value} - onChange={input.onChange} + onChange={handleChange} defaultChecked={defaultChecked} className={clsx(inputProps.className, radioButtonClasses.input)} {...inputProps} diff --git a/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.classes.ts b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.classes.ts new file mode 100644 index 0000000..f5f4aac --- /dev/null +++ b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.classes.ts @@ -0,0 +1,4 @@ +export const radioButtonGroupClasses = { + root: `lsd-radio-button-group`, + label: `lsd-radio-button-group__label`, +} diff --git a/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.context.ts b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.context.ts new file mode 100644 index 0000000..28d6be6 --- /dev/null +++ b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.context.ts @@ -0,0 +1,18 @@ +import React from 'react' +import { + ActiveRadioButtonType, + RadioButtonGroupProps, +} from './RadioButtonGroup' + +export type RadioButtonGroupContextType = { + activeRadioButton?: ActiveRadioButtonType | null + setActiveRadioButton: (value: ActiveRadioButtonType) => void + + size?: RadioButtonGroupProps['size'] +} + +export const RadioButtonGroupContext = + React.createContext(null as any) + +export const useRadioButtonGroupContext = () => + React.useContext(RadioButtonGroupContext) diff --git a/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.stories.tsx b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.stories.tsx new file mode 100644 index 0000000..158286e --- /dev/null +++ b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.stories.tsx @@ -0,0 +1,51 @@ +import { Meta, Story } from '@storybook/react' +import { RadioButton } from '../RadioButton' +import { RadioButtonGroup, RadioButtonGroupProps } from './RadioButtonGroup' + +export default { + title: 'RadioButtonGroup', + component: RadioButtonGroup, + argTypes: { + size: { + type: { + name: 'enum', + value: ['small', 'medium', 'large'], + }, + defaultValue: 'large', + }, + }, +} as Meta + +export const Root: Story = (args) => ( + + + RadioButton label + + + RadioButton label + + + RadioButton label + + + RadioButton label + + + RadioButton label + + + RadioButton label + + + RadioButton label + + + RadioButton label + + +) + +Root.args = { + size: 'large', + label: 'Label', +} diff --git a/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.styles.ts b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.styles.ts new file mode 100644 index 0000000..b7d9bf1 --- /dev/null +++ b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.styles.ts @@ -0,0 +1,15 @@ +import { css } from '@emotion/react' +import { radioButtonGroupClasses } from './RadioButtonGroup.classes' + +export const RadioButtonGroupStyles = css` + .${radioButtonGroupClasses.root} { + display: flex; + flex-direction: column; + gap: 6px; + width: fit-content; + } + + .${radioButtonGroupClasses.label} { + margin-bottom: 6px; + } +` diff --git a/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.tsx b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.tsx new file mode 100644 index 0000000..e63a533 --- /dev/null +++ b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.tsx @@ -0,0 +1,49 @@ +import clsx from 'clsx' +import React, { useEffect, useRef, useState } from 'react' +import { RadioButtonGroupContext } from './RadioButtonGroup.context' +import { radioButtonGroupClasses } from './RadioButtonGroup.classes' +import { Typography } from '../Typography' + +export type ActiveRadioButtonType = string | number | readonly string[] + +export type RadioButtonGroupProps = React.HTMLAttributes & { + activeRadioButton?: ActiveRadioButtonType | null + size?: 'small' | 'medium' | 'large' + label?: string +} + +export const RadioButtonGroup: React.FC & { + classes: typeof radioButtonGroupClasses +} = ({ size = 'large', label, activeRadioButton, children, ...props }) => { + const ref = useRef(null) + const [value, setValue] = useState(activeRadioButton) + + const setActiveRadioButton = (radioButton: ActiveRadioButtonType) => { + setValue(radioButton) + } + + useEffect(() => setValue(activeRadioButton), [activeRadioButton]) + + return ( + +
+ + {label && label} + + {children} +
+
+ ) +} + +RadioButtonGroup.classes = radioButtonGroupClasses diff --git a/packages/lsd-react/src/components/RadioButtonGroup/index.ts b/packages/lsd-react/src/components/RadioButtonGroup/index.ts new file mode 100644 index 0000000..a9f5a07 --- /dev/null +++ b/packages/lsd-react/src/components/RadioButtonGroup/index.ts @@ -0,0 +1 @@ +export * from './RadioButtonGroup' diff --git a/packages/lsd-react/src/index.ts b/packages/lsd-react/src/index.ts index 42bccf2..1c4c165 100644 --- a/packages/lsd-react/src/index.ts +++ b/packages/lsd-react/src/index.ts @@ -19,3 +19,4 @@ export * from './components/Tabs' export * from './components/Tag' export * from './components/Theme' export * from './components/RadioButton' +export * from './components/RadioButtonGroup' diff --git a/packages/lsd-react/src/utils/useInput.ts b/packages/lsd-react/src/utils/useInput.ts index 8b2fd41..0e8f1d0 100644 --- a/packages/lsd-react/src/utils/useInput.ts +++ b/packages/lsd-react/src/utils/useInput.ts @@ -29,7 +29,10 @@ export const useInput = ( const onChange: InputOnChangeType = (event) => { const type = event.target.type - const value = event.target[type === 'checkbox' ? 'checked' : 'value'] + const value = + event.target[ + type === 'checkbox' || type === 'radio' ? 'checked' : 'value' + ] if (uncontrolled) return setValue(value as T) props.onChange && props.onChange(event) } From a73302ffd5ac55f5da6336507d4c33d69a8a2dbc Mon Sep 17 00:00:00 2001 From: jinhojang6 Date: Tue, 7 Mar 2023 02:24:07 +0900 Subject: [PATCH 6/8] refactor: refactor radio button and radio button group --- .../components/RadioButton/RadioButton.tsx | 12 ++++++------ .../RadioButtonGroup.context.ts | 4 ++-- .../RadioButtonGroup.stories.tsx | 16 ++++++++-------- .../RadioButtonGroup/RadioButtonGroup.tsx | 19 +++++++++++-------- 4 files changed, 27 insertions(+), 24 deletions(-) diff --git a/packages/lsd-react/src/components/RadioButton/RadioButton.tsx b/packages/lsd-react/src/components/RadioButton/RadioButton.tsx index 90ececf..a547e68 100644 --- a/packages/lsd-react/src/components/RadioButton/RadioButton.tsx +++ b/packages/lsd-react/src/components/RadioButton/RadioButton.tsx @@ -16,6 +16,8 @@ export type RadioButtonProps = Omit< > & { disabled?: boolean size?: 'small' | 'medium' | 'large' + name?: string + value?: string inputProps?: React.InputHTMLAttributes } @@ -32,12 +34,10 @@ export const RadioButton: React.FC & { ...props }) => { const ref = useRef(null) - const radioButtonGroup = useRadioButtonGroupContext() const size = radioButtonGroup?.size ?? _size const selected = radioButtonGroup - ? radioButtonGroup.activeRadioButton != null && - radioButtonGroup.activeRadioButton === inputProps?.value + ? radioButtonGroup.value === inputProps?.value : _checked const input = useInput({ @@ -48,9 +48,9 @@ export const RadioButton: React.FC & { }) const handleChange = (event: React.ChangeEvent) => { - onChange && onChange(event) - input.onChange(event) - inputProps.value && radioButtonGroup?.setActiveRadioButton(inputProps.value) + radioButtonGroup + ? radioButtonGroup.setActiveValue(event.target.value) + : input.onChange(event) } return ( diff --git a/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.context.ts b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.context.ts index 28d6be6..311c7e5 100644 --- a/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.context.ts +++ b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.context.ts @@ -5,8 +5,8 @@ import { } from './RadioButtonGroup' export type RadioButtonGroupContextType = { - activeRadioButton?: ActiveRadioButtonType | null - setActiveRadioButton: (value: ActiveRadioButtonType) => void + value?: ActiveRadioButtonType | null + setActiveValue: (value: ActiveRadioButtonType) => void size?: RadioButtonGroupProps['size'] } diff --git a/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.stories.tsx b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.stories.tsx index 158286e..2225f95 100644 --- a/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.stories.tsx +++ b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.stories.tsx @@ -18,28 +18,28 @@ export default { export const Root: Story = (args) => ( - + RadioButton label - + RadioButton label - + RadioButton label - + RadioButton label - + RadioButton label - + RadioButton label - + RadioButton label - + RadioButton label diff --git a/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.tsx b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.tsx index e63a533..505f2e2 100644 --- a/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.tsx +++ b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.tsx @@ -4,29 +4,32 @@ import { RadioButtonGroupContext } from './RadioButtonGroup.context' import { radioButtonGroupClasses } from './RadioButtonGroup.classes' import { Typography } from '../Typography' -export type ActiveRadioButtonType = string | number | readonly string[] +export type ActiveRadioButtonType = string | readonly string[] export type RadioButtonGroupProps = React.HTMLAttributes & { - activeRadioButton?: ActiveRadioButtonType | null + value?: ActiveRadioButtonType | null + name?: ActiveRadioButtonType | null + onChange?: (value: ActiveRadioButtonType) => void size?: 'small' | 'medium' | 'large' label?: string } export const RadioButtonGroup: React.FC & { classes: typeof radioButtonGroupClasses -} = ({ size = 'large', label, activeRadioButton, children, ...props }) => { +} = ({ size = 'large', label, value, name, onChange, children, ...props }) => { const ref = useRef(null) - const [value, setValue] = useState(activeRadioButton) + const [activeValue, setActiveValue] = useState(value) - const setActiveRadioButton = (radioButton: ActiveRadioButtonType) => { - setValue(radioButton) + const setActiveRadioButton = (value: ActiveRadioButtonType) => { + if (onChange) onChange(value) + else setActiveValue(value) } - useEffect(() => setValue(activeRadioButton), [activeRadioButton]) + useEffect(() => setActiveValue(value), [value]) return (
Date: Tue, 7 Mar 2023 20:57:06 +0900 Subject: [PATCH 7/8] refactor: pass down value and name directly --- .../RadioButton/RadioButton.stories.tsx | 1 + .../components/RadioButton/RadioButton.tsx | 11 ++++-- .../RadioButtonGroup.context.ts | 3 +- .../RadioButtonGroup.stories.tsx | 34 +++++-------------- .../RadioButtonGroup/RadioButtonGroup.tsx | 6 ++-- 5 files changed, 23 insertions(+), 32 deletions(-) diff --git a/packages/lsd-react/src/components/RadioButton/RadioButton.stories.tsx b/packages/lsd-react/src/components/RadioButton/RadioButton.stories.tsx index c27a36c..2a9d446 100644 --- a/packages/lsd-react/src/components/RadioButton/RadioButton.stories.tsx +++ b/packages/lsd-react/src/components/RadioButton/RadioButton.stories.tsx @@ -24,4 +24,5 @@ Root.args = { disabled: false, checked: undefined, onChange: undefined, + value: '1', } diff --git a/packages/lsd-react/src/components/RadioButton/RadioButton.tsx b/packages/lsd-react/src/components/RadioButton/RadioButton.tsx index a547e68..ed49ac2 100644 --- a/packages/lsd-react/src/components/RadioButton/RadioButton.tsx +++ b/packages/lsd-react/src/components/RadioButton/RadioButton.tsx @@ -17,7 +17,7 @@ export type RadioButtonProps = Omit< disabled?: boolean size?: 'small' | 'medium' | 'large' name?: string - value?: string + value: string inputProps?: React.InputHTMLAttributes } @@ -29,15 +29,18 @@ export const RadioButton: React.FC & { checked: _checked, defaultChecked, disabled = false, + value, inputProps = {}, children, ...props }) => { const ref = useRef(null) + const radioButtonGroup = useRadioButtonGroupContext() const size = radioButtonGroup?.size ?? _size + const name = radioButtonGroup?.name ?? '' const selected = radioButtonGroup - ? radioButtonGroup.value === inputProps?.value + ? radioButtonGroup.value === value : _checked const input = useInput({ @@ -49,7 +52,7 @@ export const RadioButton: React.FC & { const handleChange = (event: React.ChangeEvent) => { radioButtonGroup - ? radioButtonGroup.setActiveValue(event.target.value) + ? radioButtonGroup.setActiveRadioButton(event.target.value) : input.onChange(event) } @@ -69,6 +72,8 @@ export const RadioButton: React.FC & { > void + name?: string | null + setActiveRadioButton: (value: ActiveRadioButtonType) => void size?: RadioButtonGroupProps['size'] } diff --git a/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.stories.tsx b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.stories.tsx index 2225f95..4bde163 100644 --- a/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.stories.tsx +++ b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.stories.tsx @@ -17,31 +17,15 @@ export default { } as Meta export const Root: Story = (args) => ( - - - RadioButton label - - - RadioButton label - - - RadioButton label - - - RadioButton label - - - RadioButton label - - - RadioButton label - - - RadioButton label - - - RadioButton label - + + RadioButton label + RadioButton label + RadioButton label + RadioButton label + RadioButton label + RadioButton label + RadioButton label + RadioButton label ) diff --git a/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.tsx b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.tsx index 505f2e2..57913e3 100644 --- a/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.tsx +++ b/packages/lsd-react/src/components/RadioButtonGroup/RadioButtonGroup.tsx @@ -7,8 +7,8 @@ import { Typography } from '../Typography' export type ActiveRadioButtonType = string | readonly string[] export type RadioButtonGroupProps = React.HTMLAttributes & { - value?: ActiveRadioButtonType | null - name?: ActiveRadioButtonType | null + value: ActiveRadioButtonType | null + name?: string | null onChange?: (value: ActiveRadioButtonType) => void size?: 'small' | 'medium' | 'large' label?: string @@ -29,7 +29,7 @@ export const RadioButtonGroup: React.FC & { return (
Date: Fri, 10 Mar 2023 00:06:42 +0900 Subject: [PATCH 8/8] refactor: use _name in case of radio button group is not being used --- packages/lsd-react/src/components/RadioButton/RadioButton.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/lsd-react/src/components/RadioButton/RadioButton.tsx b/packages/lsd-react/src/components/RadioButton/RadioButton.tsx index ed49ac2..ca87974 100644 --- a/packages/lsd-react/src/components/RadioButton/RadioButton.tsx +++ b/packages/lsd-react/src/components/RadioButton/RadioButton.tsx @@ -30,6 +30,7 @@ export const RadioButton: React.FC & { defaultChecked, disabled = false, value, + name: _name, inputProps = {}, children, ...props @@ -38,7 +39,7 @@ export const RadioButton: React.FC & { const radioButtonGroup = useRadioButtonGroupContext() const size = radioButtonGroup?.size ?? _size - const name = radioButtonGroup?.name ?? '' + const name = radioButtonGroup?.name ?? _name ?? '' const selected = radioButtonGroup ? radioButtonGroup.value === value : _checked