Skip to content

Commit

Permalink
chore: FormControl, Fieldsetのロジックを最適化
Browse files Browse the repository at this point in the history
  • Loading branch information
AtsushiM committed Jan 8, 2025
1 parent 00e1a37 commit 675c8ff
Showing 1 changed file with 72 additions and 62 deletions.
134 changes: 72 additions & 62 deletions packages/smarthr-ui/src/components/FormControl/FormControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ const childrenWrapper = tv({
],
})

const SMARTHR_UI_INPUT_SELECTOR = '[data-smarthr-ui-input="true"]'

export const ActualFormControl: React.FC<Props & ElementProps> = ({
title,
titleType = 'blockTitle',
Expand All @@ -126,7 +128,7 @@ export const ActualFormControl: React.FC<Props & ElementProps> = ({
htmlFor,
labelId,
innerMargin,
statusLabelProps = [],
statusLabelProps,
helpMessage,
exampleMessage,
errorMessages,
Expand All @@ -143,7 +145,6 @@ export const ActualFormControl: React.FC<Props & ElementProps> = ({
const managedLabelId = labelId || defaultLabelId
const inputWrapperRef = useRef<HTMLDivElement>(null)
const isRoleGroup = as === 'fieldset'
const statusLabelList = Array.isArray(statusLabelProps) ? statusLabelProps : [statusLabelProps]

const describedbyIds = useMemo(() => {
const temp = []
Expand All @@ -163,6 +164,13 @@ export const ActualFormControl: React.FC<Props & ElementProps> = ({

return temp.join(' ')
}, [helpMessage, exampleMessage, supplementaryMessage, errorMessages, managedHtmlFor])
const statusLabelList = useMemo(() => {
if (!statusLabelProps) {
return []
}

return Array.isArray(statusLabelProps) ? statusLabelProps : [statusLabelProps]
}, [statusLabelProps])
const actualErrorMessages = useMemo(() => {
if (!errorMessages) {
return []
Expand All @@ -184,68 +192,63 @@ export const ActualFormControl: React.FC<Props & ElementProps> = ({
}, [className, dangerouslyTitleHidden, innerMargin, isRoleGroup])

useEffect(() => {
if (isRoleGroup) {
return
}

const inputWrapper = inputWrapperRef?.current

if (inputWrapper) {
// HINT: 対象idを持つ要素が既に存在する場合、何もしない
if (document.getElementById(managedHtmlFor)) {
return
}

const input = inputWrapper.querySelector('[data-smarthr-ui-input="true"]')

if (input) {
if (!input.getAttribute('id')) {
input.setAttribute('id', managedHtmlFor)
}

const isInputFile = input instanceof HTMLInputElement && input.type === 'file'
const inputLabelledByIds = input.getAttribute('aria-labelledby')
if (isInputFile && inputLabelledByIds) {
// InputFileの場合はlabel要素の可視ラベルをアクセシブルネームに含める
input.setAttribute('aria-labelledby', `${inputLabelledByIds} ${managedLabelId}`)
if (!isRoleGroup) {
const inputWrapper = inputWrapperRef?.current

if (inputWrapper) {
// HINT: 対象idを持つ要素が既に存在する場合、何もしない
if (!document.getElementById(managedHtmlFor)) {
const input = inputWrapper.querySelector(SMARTHR_UI_INPUT_SELECTOR)

if (input) {
if (!input.getAttribute('id')) {
input.setAttribute('id', managedHtmlFor)
}

if (input instanceof HTMLInputElement && input.type === 'file') {
const attrName = 'aria-labelledby'
const inputLabelledByIds = input.getAttribute(attrName)

if (inputLabelledByIds) {
// InputFileの場合はlabel要素の可視ラベルをアクセシブルネームに含める
input.setAttribute(attrName, `${inputLabelledByIds} ${managedLabelId}`)
}
}
}
}
}
}
}, [managedHtmlFor, isRoleGroup, managedLabelId])
useEffect(() => {
const inputWrapper = inputWrapperRef?.current

if (inputWrapper) {
// HINT: 対象idを持つ要素が既に存在する場合、何もしない
if (!describedbyIds || inputWrapper.querySelector(`[aria-describedby="${describedbyIds}"]`)) {
return
}
if (describedbyIds) {
const attrName = 'aria-describedby'
const inputWrapper = inputWrapperRef?.current

const input = inputWrapper.querySelector('[data-smarthr-ui-input="true"]')
if (inputWrapper && !inputWrapper.querySelector(`[${attrName}="${describedbyIds}"]`)) {
const input = inputWrapper.querySelector(SMARTHR_UI_INPUT_SELECTOR)

if (input && !input.getAttribute('aria-describedby')) {
input.setAttribute('aria-describedby', describedbyIds)
if (input && !input.getAttribute(attrName)) {
input.setAttribute(attrName, describedbyIds)
}
}
}
}, [describedbyIds, isRoleGroup])
}, [describedbyIds])
useEffect(() => {
if (!autoBindErrorInput) {
return
}
if (autoBindErrorInput) {
const inputWrapper = inputWrapperRef?.current

const inputWrapper = inputWrapperRef?.current
if (inputWrapper) {
const input = inputWrapper.querySelector(SMARTHR_UI_INPUT_SELECTOR)

if (inputWrapper) {
const input = inputWrapper.querySelector('[data-smarthr-ui-input="true"]')
if (input) {
const attrName = 'aria-invalid'

if (!input) {
return
}

if (actualErrorMessages.length > 0) {
input.setAttribute('aria-invalid', 'true')
} else {
input.removeAttribute('aria-invalid')
if (actualErrorMessages.length > 0) {
input.setAttribute(attrName, 'true')
} else {
input.removeAttribute(attrName)
}
}
}
}
}, [actualErrorMessages.length, autoBindErrorInput])
Expand Down Expand Up @@ -324,21 +327,28 @@ const TitleCluster = React.memo<
)
const clusterAttrs = isRoleGroup
? { 'aria-hidden': 'true' }
: { htmlFor: managedHtmlFor, as: 'label' }
: {
htmlFor: managedHtmlFor,
id: managedLabelId,
as: 'label' as React.ComponentProps<typeof Cluster>['as'],
}

return (
<>
{isRoleGroup && <VisuallyHiddenText as="legend">{body}</VisuallyHiddenText>}
<Cluster justify="space-between">
{isRoleGroup && (
<VisuallyHiddenText id={managedLabelId} as="legend">
{body}
</VisuallyHiddenText>
)}
<Cluster
justify="space-between"
// HINT: legendが存在する場合、Stackの余白が狂ってしまう&常にこのClusterはUI上先頭になるため、margin-topを0固定する
className="[&&&]:shr-mt-0"
// HINT: dangerouslyTitleHiddenの場合、Stackの余白計算を正常にするためのhidden
hidden={dangerouslyTitleHidden || undefined}
>
{/* eslint-disable-next-line smarthr/best-practice-for-layouts */}
<Cluster
{...clusterAttrs}
align="center"
id={managedLabelId}
className={labelStyle}
// Stack 対象にしないための hidden
hidden={dangerouslyTitleHidden || undefined}
>
<Cluster {...clusterAttrs} align="center" className={labelStyle}>
{body}
</Cluster>
{subActionArea && <div className="shr-grow">{subActionArea}</div>}
Expand Down

0 comments on commit 675c8ff

Please sign in to comment.