mirror of https://github.com/acid-info/lsd.git
feat: implement radio button group
This commit is contained in:
parent
3dc8369b65
commit
929dc713e5
|
@ -17,6 +17,7 @@ import { LsdIconStyles } from '../Icons/LsdIcon/LsdIcon.styles'
|
||||||
import { ListBoxStyles } from '../ListBox/ListBox.styles'
|
import { ListBoxStyles } from '../ListBox/ListBox.styles'
|
||||||
import { QuoteStyles } from '../Quote/Quote.styles'
|
import { QuoteStyles } from '../Quote/Quote.styles'
|
||||||
import { RadioButtonStyles } from '../RadioButton/RadioButton.styles'
|
import { RadioButtonStyles } from '../RadioButton/RadioButton.styles'
|
||||||
|
import { RadioButtonGroupStyles } from '../RadioButtonGroup/RadioButtonGroup.styles'
|
||||||
import { TabItemStyles } from '../TabItem/TabItem.styles'
|
import { TabItemStyles } from '../TabItem/TabItem.styles'
|
||||||
import { TabsStyles } from '../Tabs/Tabs.styles'
|
import { TabsStyles } from '../Tabs/Tabs.styles'
|
||||||
import { TagStyles } from '../Tag/Tag.styles'
|
import { TagStyles } from '../Tag/Tag.styles'
|
||||||
|
@ -48,6 +49,7 @@ const componentStyles: Array<ReturnType<typeof withTheme> | SerializedStyles> =
|
||||||
CollapseStyles,
|
CollapseStyles,
|
||||||
CollapseHeaderStyles,
|
CollapseHeaderStyles,
|
||||||
RadioButtonStyles,
|
RadioButtonStyles,
|
||||||
|
RadioButtonGroupStyles,
|
||||||
]
|
]
|
||||||
|
|
||||||
export const CSSBaseline: React.FC<{ theme?: Theme }> = ({
|
export const CSSBaseline: React.FC<{ theme?: Theme }> = ({
|
||||||
|
|
|
@ -2,6 +2,7 @@ import clsx from 'clsx'
|
||||||
import React, { useRef } from 'react'
|
import React, { useRef } from 'react'
|
||||||
import { useInput } from '../../utils/useInput'
|
import { useInput } from '../../utils/useInput'
|
||||||
import { RadioButtonFilledIcon, RadioButtonIcon } from '../Icons'
|
import { RadioButtonFilledIcon, RadioButtonIcon } from '../Icons'
|
||||||
|
import { useRadioButtonGroupContext } from '../RadioButtonGroup/RadioButtonGroup.context'
|
||||||
import { Typography } from '../Typography'
|
import { Typography } from '../Typography'
|
||||||
import { radioButtonClasses } from './RadioButton.classes'
|
import { radioButtonClasses } from './RadioButton.classes'
|
||||||
|
|
||||||
|
@ -21,9 +22,9 @@ export type RadioButtonProps = Omit<
|
||||||
export const RadioButton: React.FC<RadioButtonProps> & {
|
export const RadioButton: React.FC<RadioButtonProps> & {
|
||||||
classes: typeof radioButtonClasses
|
classes: typeof radioButtonClasses
|
||||||
} = ({
|
} = ({
|
||||||
size = 'large',
|
size: _size = 'large',
|
||||||
onChange,
|
onChange,
|
||||||
checked,
|
checked: _checked,
|
||||||
defaultChecked,
|
defaultChecked,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
inputProps = {},
|
inputProps = {},
|
||||||
|
@ -31,13 +32,27 @@ export const RadioButton: React.FC<RadioButtonProps> & {
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const ref = useRef<HTMLInputElement>(null)
|
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({
|
const input = useInput({
|
||||||
value: checked,
|
value: selected,
|
||||||
defaultValue: defaultChecked ?? false,
|
defaultValue: defaultChecked ?? false,
|
||||||
onChange,
|
onChange,
|
||||||
ref,
|
ref,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
onChange && onChange(event)
|
||||||
|
input.onChange(event)
|
||||||
|
inputProps.value && radioButtonGroup?.setActiveRadioButton(inputProps.value)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Typography
|
<Typography
|
||||||
color="primary"
|
color="primary"
|
||||||
|
@ -56,7 +71,7 @@ export const RadioButton: React.FC<RadioButtonProps> & {
|
||||||
ref={ref}
|
ref={ref}
|
||||||
type="radio"
|
type="radio"
|
||||||
checked={input.value}
|
checked={input.value}
|
||||||
onChange={input.onChange}
|
onChange={handleChange}
|
||||||
defaultChecked={defaultChecked}
|
defaultChecked={defaultChecked}
|
||||||
className={clsx(inputProps.className, radioButtonClasses.input)}
|
className={clsx(inputProps.className, radioButtonClasses.input)}
|
||||||
{...inputProps}
|
{...inputProps}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
export const radioButtonGroupClasses = {
|
||||||
|
root: `lsd-radio-button-group`,
|
||||||
|
label: `lsd-radio-button-group__label`,
|
||||||
|
}
|
|
@ -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)
|
|
@ -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',
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
`
|
|
@ -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
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './RadioButtonGroup'
|
|
@ -19,3 +19,4 @@ export * from './components/Tabs'
|
||||||
export * from './components/Tag'
|
export * from './components/Tag'
|
||||||
export * from './components/Theme'
|
export * from './components/Theme'
|
||||||
export * from './components/RadioButton'
|
export * from './components/RadioButton'
|
||||||
|
export * from './components/RadioButtonGroup'
|
||||||
|
|
|
@ -29,7 +29,10 @@ export const useInput = <T extends InputValueType = InputValueType>(
|
||||||
|
|
||||||
const onChange: InputOnChangeType = (event) => {
|
const onChange: InputOnChangeType = (event) => {
|
||||||
const type = event.target.type
|
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)
|
if (uncontrolled) return setValue(value as T)
|
||||||
props.onChange && props.onChange(event)
|
props.onChange && props.onChange(event)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue