Skip to content

Commit

Permalink
feat: Switchのchildrenを必須にし、label要素として紐づけることでa11yを改善する
Browse files Browse the repository at this point in the history
  • Loading branch information
AtsushiM committed Aug 29, 2024
1 parent 62911b2 commit b3c14cd
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 18 deletions.
23 changes: 18 additions & 5 deletions packages/smarthr-ui/src/components/Switch/Switch.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,37 @@ export const Default: StoryFn = () => (
<Section>
<Stack gap={0.5} align="flex-start">
<Heading type="blockTitle">標準</Heading>
<Switch onChange={action('clicked')} />
<Switch onChange={action('clicked')}>ラベル</Switch>
</Stack>
</Section>
<Section>
<Stack gap={0.5} align="flex-start">
<Heading type="blockTitle">ラベルをVisuallyHidden</Heading>
<Switch dangerouslyLabelHidden={true} onChange={action('clicked')}>
非表示ラベル
</Switch>
</Stack>
</Section>
<Section>
<Stack gap={0.5} align="flex-start">
<Heading type="blockTitle">デフォルト値変更</Heading>
<Switch defaultChecked={true} onChange={action('clicked')} />
<Switch defaultChecked={true} onChange={action('clicked')}>
ラベル
</Switch>
</Stack>
</Section>
<Section>
<Stack gap={0.5} align="flex-start">
<Heading type="blockTitle">disabled</Heading>
<Cluster>
<Switch disabled onChange={action('clicked')} />
<Switch disabled defaultChecked={true} onChange={action('clicked')} />
<Switch disabled onChange={action('clicked')}>
ラベル1
</Switch>
<Switch disabled defaultChecked={true} onChange={action('clicked')}>
ラベル2
</Switch>
</Cluster>
</Stack>
</Section>
<p>※ 実際に使う場合には必ずラベルを指定してください。</p>
</Stack>
)
51 changes: 38 additions & 13 deletions packages/smarthr-ui/src/components/Switch/Switch.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import React, { InputHTMLAttributes, forwardRef, useMemo } from 'react'
import { tv } from 'tailwind-variants'

import { useId } from '../../hooks/useId'
import { FaCheckIcon } from '../Icon'
import { Cluster } from '../Layout'
import { Text } from '../Text'
import { VisuallyHiddenText } from '../VisuallyHiddenText'

const switchStyle = tv({
slots: {
Expand Down Expand Up @@ -42,17 +46,38 @@ const switchStyle = tv({
},
})

type Props = InputHTMLAttributes<HTMLInputElement>
type Props = InputHTMLAttributes<HTMLInputElement> &
PropsWithChildren<{
/** ラベルを視覚的に隠すかどうか */
dangerouslyLabelHidden?: boolean
}>

export const Switch = forwardRef<HTMLInputElement, Props>(({ className, ...props }, ref) => {
const { wrapper, input, icon, iconWrapper } = useMemo(() => switchStyle(), [])
return (
<span className={wrapper({ className })}>
{/* eslint-disable-next-line smarthr/a11y-input-has-name-attribute */}
<input {...props} type="checkbox" role="switch" className={input()} ref={ref} />
<span className={iconWrapper()}>
<FaCheckIcon className={icon()} size="XXS" />
</span>
</span>
)
})
export const Switch = forwardRef<HTMLInputElement, Props>(
({ children, dangerouslyLabelHidden, className, id, ...props }, ref) => {
const { wrapper, input, icon, iconWrapper } = useMemo(() => switchStyle(), [])
const ActualLabel = dangerouslyLabelHidden ? VisuallyHiddenText : Text
const inputId = useId(id)

return (
<Cluster align="center">
<ActualLabel as="label" htmlFor={inputId}>
{children}
</ActualLabel>
<span className={wrapper({ className })}>
{/* eslint-disable-next-line smarthr/a11y-input-has-name-attribute */}
<input
{...props}
type="checkbox"
role="switch"
id={inputId}
className={input()}
ref={ref}
/>
<span className={iconWrapper()}>
<FaCheckIcon className={icon()} size="XXS" />
</span>
</span>
</Cluster>
)
},
)

0 comments on commit b3c14cd

Please sign in to comment.