From 929dc713e5150fc2c5472651903750a8d7abaeed Mon Sep 17 00:00:00 2001 From: jinhojang6 Date: Fri, 3 Mar 2023 02:55:29 +0900 Subject: [PATCH] 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) }