feat: implement radio button group

This commit is contained in:
jinhojang6 2023-03-03 02:55:29 +09:00
parent 3dc8369b65
commit 929dc713e5
10 changed files with 164 additions and 5 deletions

View File

@ -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<ReturnType<typeof withTheme> | SerializedStyles> =
CollapseStyles,
CollapseHeaderStyles,
RadioButtonStyles,
RadioButtonGroupStyles,
]
export const CSSBaseline: React.FC<{ theme?: Theme }> = ({

View File

@ -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<RadioButtonProps> & {
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<RadioButtonProps> & {
...props
}) => {
const ref = useRef<HTMLInputElement>(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<HTMLInputElement>) => {
onChange && onChange(event)
input.onChange(event)
inputProps.value && radioButtonGroup?.setActiveRadioButton(inputProps.value)
}
return (
<Typography
color="primary"
@ -56,7 +71,7 @@ export const RadioButton: React.FC<RadioButtonProps> & {
ref={ref}
type="radio"
checked={input.value}
onChange={input.onChange}
onChange={handleChange}
defaultChecked={defaultChecked}
className={clsx(inputProps.className, radioButtonClasses.input)}
{...inputProps}

View File

@ -0,0 +1,4 @@
export const radioButtonGroupClasses = {
root: `lsd-radio-button-group`,
label: `lsd-radio-button-group__label`,
}

View File

@ -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<RadioButtonGroupContextType>(null as any)
export const useRadioButtonGroupContext = () =>
React.useContext(RadioButtonGroupContext)

View File

@ -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<RadioButtonGroupProps> = (args) => (
<RadioButtonGroup {...args}>
<RadioButton inputProps={{ name: 'name', value: 1 }}>
RadioButton label
</RadioButton>
<RadioButton inputProps={{ name: 'name', value: 2 }}>
RadioButton label
</RadioButton>
<RadioButton inputProps={{ name: 'name', value: 3 }}>
RadioButton label
</RadioButton>
<RadioButton inputProps={{ name: 'name', value: 4 }}>
RadioButton label
</RadioButton>
<RadioButton inputProps={{ name: 'name', value: 5 }}>
RadioButton label
</RadioButton>
<RadioButton inputProps={{ name: 'name', value: 6 }}>
RadioButton label
</RadioButton>
<RadioButton inputProps={{ name: 'name', value: 7 }}>
RadioButton label
</RadioButton>
<RadioButton inputProps={{ name: 'name', value: 8 }}>
RadioButton label
</RadioButton>
</RadioButtonGroup>
)
Root.args = {
size: 'large',
label: 'Label',
}

View File

@ -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;
}
`

View File

@ -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<HTMLDivElement> & {
activeRadioButton?: ActiveRadioButtonType | null
size?: 'small' | 'medium' | 'large'
label?: string
}
export const RadioButtonGroup: React.FC<RadioButtonGroupProps> & {
classes: typeof radioButtonGroupClasses
} = ({ size = 'large', label, activeRadioButton, children, ...props }) => {
const ref = useRef<HTMLDivElement>(null)
const [value, setValue] = useState(activeRadioButton)
const setActiveRadioButton = (radioButton: ActiveRadioButtonType) => {
setValue(radioButton)
}
useEffect(() => setValue(activeRadioButton), [activeRadioButton])
return (
<RadioButtonGroupContext.Provider
value={{ activeRadioButton: value, setActiveRadioButton, size }}
>
<div
ref={ref}
{...props}
className={clsx(props.className, radioButtonGroupClasses.root)}
>
<Typography
component="span"
variant={size === 'small' ? 'label2' : 'label1'}
className={radioButtonGroupClasses.label}
>
{label && label}
</Typography>
{children}
</div>
</RadioButtonGroupContext.Provider>
)
}
RadioButtonGroup.classes = radioButtonGroupClasses

View File

@ -0,0 +1 @@
export * from './RadioButtonGroup'

View File

@ -19,3 +19,4 @@ export * from './components/Tabs'
export * from './components/Tag'
export * from './components/Theme'
export * from './components/RadioButton'
export * from './components/RadioButtonGroup'

View File

@ -29,7 +29,10 @@ export const useInput = <T extends InputValueType = InputValueType>(
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)
}