From 9ea6d3d087d0e2b306a6c43d51355051c94fb2ef Mon Sep 17 00:00:00 2001 From: AtsushiM Date: Wed, 1 Jan 2025 09:28:18 +0900 Subject: [PATCH] =?UTF-8?q?chore:=20FormControl=E3=81=AE=E3=83=AD=E3=82=B8?= =?UTF-8?q?=E3=83=83=E3=82=AF=E3=82=92=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF?= =?UTF-8?q?=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/FormControl/FormControl.tsx | 131 +++++++++--------- packages/smarthr-ui/src/index.test.ts | 4 +- .../smarthr-ui/src/themes/createSpacing.ts | 17 ++- packages/smarthr-ui/src/types/Gap.ts | 3 +- packages/smarthr-ui/src/types/index.ts | 2 +- 5 files changed, 84 insertions(+), 73 deletions(-) diff --git a/packages/smarthr-ui/src/components/FormControl/FormControl.tsx b/packages/smarthr-ui/src/components/FormControl/FormControl.tsx index 6d31d87556..08db239d38 100644 --- a/packages/smarthr-ui/src/components/FormControl/FormControl.tsx +++ b/packages/smarthr-ui/src/components/FormControl/FormControl.tsx @@ -18,7 +18,7 @@ import { StatusLabel } from '../StatusLabel' import { Text, TextProps } from '../Text' import { visuallyHiddenText } from '../VisuallyHiddenText/VisuallyHiddenText' -import type { Gap } from '../../types' +import type { PositiveGap } from '../../types' type StatusLabelProps = ComponentProps @@ -38,7 +38,7 @@ type Props = PropsWithChildren<{ /** label 要素に適用する `id` 値 */ labelId?: string /** タイトル群と子要素の間の間隔調整用(基本的には不要) */ - innerMargin?: Gap + innerMargin?: PositiveGap /** タイトルの隣に表示する `StatusLabel` の Props の配列 */ statusLabelProps?: StatusLabelProps | StatusLabelProps[] /** タイトルの下に表示するヘルプメッセージ */ @@ -81,7 +81,7 @@ const formGroup = tv({ }, }) -const MARGIN_MAPPER: { [key in Gap]: number } = { +const MARGIN_MAPPER: { [key in PositiveGap]: number } = { 0: 0, 0.25: 0.25, 0.5: 0.5, @@ -105,7 +105,7 @@ const MARGIN_MAPPER: { [key in Gap]: number } = { XXL: 3.5, X3L: 4, } -const MARGIN_MAPPER_KEYS = Object.keys(MARGIN_MAPPER) +const MARGIN_MAPPER_KEYS = Object.keys(MARGIN_MAPPER) as PositiveGap[] const bodyWrapper = tv({ base: ['shr-clear-both'], @@ -116,7 +116,7 @@ const bodyWrapper = tv({ return prev }, - {} as { [key in Gap]: string }, + {} as { [key in PositiveGap]: string }, ), }, compoundVariants: [ @@ -139,7 +139,7 @@ const childrenWrapper = tv({ return prev }, - {} as { [key in Gap]: string }, + {} as { [key in PositiveGap]: string }, ), isRoleGroup: { true: '', @@ -160,6 +160,8 @@ const childrenWrapper = tv({ ], }) +const SMARTHR_UI_INPUT_SELECTOR = '[data-smarthr-ui-input="true"]' + export const ActualFormControl: React.FC = ({ title, titleType = 'blockTitle', @@ -186,8 +188,11 @@ export const ActualFormControl: React.FC = ({ const managedLabelId = labelId || defaultLabelId const inputWrapperRef = useRef(null) const isRoleGroup = as === 'fieldset' - const statusLabelList = Array.isArray(statusLabelProps) ? statusLabelProps : [statusLabelProps] + const statusLabelList = useMemo( + () => (Array.isArray(statusLabelProps) ? statusLabelProps : [statusLabelProps]), + [statusLabelProps], + ) const describedbyIds = useMemo(() => { const temp = [] @@ -235,73 +240,70 @@ export const ActualFormControl: React.FC = ({ }, [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) + } + + const isInputFile = input instanceof HTMLInputElement && input.type === 'file' + + if (isInputFile) { + const attributeName = 'aria-labelledby' + const inputLabelledByIds = input.getAttribute(attributeName) + + if (inputLabelledByIds) { + // InputFileの場合はlabel要素の可視ラベルをアクセシブルネームに含める + input.setAttribute(attributeName, `${inputLabelledByIds} ${managedLabelId}`) + } + } + } } } } }, [managedHtmlFor, isRoleGroup, managedLabelId]) useEffect(() => { - const inputWrapper = inputWrapperRef?.current + if (describedbyIds) { + const inputWrapper = inputWrapperRef?.current - if (inputWrapper) { // HINT: 対象idを持つ要素が既に存在する場合、何もしない - if (!describedbyIds || inputWrapper.querySelector(`[aria-describedby="${describedbyIds}"]`)) { - return - } - - const input = inputWrapper.querySelector('[data-smarthr-ui-input="true"]') + if (inputWrapper && !inputWrapper.querySelector(`[aria-describedby="${describedbyIds}"]`)) { + const input = inputWrapper.querySelector(SMARTHR_UI_INPUT_SELECTOR) + const attributeName = 'aria-describedby' - if (input && !input.getAttribute('aria-describedby')) { - input.setAttribute('aria-describedby', describedbyIds) + if (input && !input.getAttribute(attributeName)) { + input.setAttribute(attributeName, describedbyIds) + } } } }, [describedbyIds, isRoleGroup]) useEffect(() => { - if (!autoBindErrorInput) { - return - } - - const inputWrapper = inputWrapperRef?.current - - if (inputWrapper) { - const input = inputWrapper.querySelector('[data-smarthr-ui-input="true"]') - - if (!input) { - return - } - - if (actualErrorMessages.length > 0) { - input.setAttribute('aria-invalid', 'true') - } else { - input.removeAttribute('aria-invalid') + if (autoBindErrorInput) { + const inputWrapper = inputWrapperRef?.current + + if (inputWrapper) { + const input = inputWrapper.querySelector(SMARTHR_UI_INPUT_SELECTOR) + + if (input) { + const attributeName = 'aria-invalid' + if (actualErrorMessages.length > 0) { + input.setAttribute(attributeName, 'true') + } else { + input.removeAttribute(attributeName) + } + } } } }, [actualErrorMessages.length, autoBindErrorInput]) - const Component = as || 'div' + const Component = as return ( (({ errorMessages, managedHtmlFor, errorListStyle, errorIconStyle }) => { - if (errorMessages.length === 0) { - return null - } - - return ( +}>(({ errorMessages, managedHtmlFor, errorListStyle, errorIconStyle }) => + errorMessages.length > 0 ? ( - ) -}) + ) : null, +) const SupplementaryMessageText = React.memo< Pick & { managedHtmlFor: string } diff --git a/packages/smarthr-ui/src/index.test.ts b/packages/smarthr-ui/src/index.test.ts index 5a63bb1f92..8ce0ce3c0e 100644 --- a/packages/smarthr-ui/src/index.test.ts +++ b/packages/smarthr-ui/src/index.test.ts @@ -35,11 +35,11 @@ describe('index', () => { if (innerComponents.length === 0) { return } - const exportedCompoenntsFromInnerDir = await getExportedDirectoryComponents( + const exportedComponentsFromInnerDir = await getExportedDirectoryComponents( indexPath, `./components/${dirName}`, ) - expect(exportedCompoenntsFromInnerDir.sort()).toEqual( + expect(exportedComponentsFromInnerDir.sort()).toEqual( expect.arrayContaining(innerComponents.sort()), ) }) diff --git a/packages/smarthr-ui/src/themes/createSpacing.ts b/packages/smarthr-ui/src/themes/createSpacing.ts index 53b76ca842..a9800f45f1 100644 --- a/packages/smarthr-ui/src/themes/createSpacing.ts +++ b/packages/smarthr-ui/src/themes/createSpacing.ts @@ -19,11 +19,24 @@ export type CreatedSpacingTheme = { export type CreatedSpacingByCharTheme = (size: CharRelativeSize) => string +const positivePrimitiveTokens = [0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 2.5, 3, 3.5, 4, 8] as const export const primitiveTokens = [ - 0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 2.5, 3, 3.5, 4, 8, -0.25, -0.5, -0.75, -1, -1.25, -1.5, -2, - -2.5, -3, -3.5, -4, -8, + ...positivePrimitiveTokens, + -0.25, + -0.5, + -0.75, + -1, + -1.25, + -1.5, + -2, + -2.5, + -3, + -3.5, + -4, + -8, ] as const +export type PositiveCharRelativeSize = (typeof positivePrimitiveTokens)[number] export type CharRelativeSize = (typeof primitiveTokens)[number] export type AbstractSize = keyof CreatedSpacingTheme diff --git a/packages/smarthr-ui/src/types/Gap.ts b/packages/smarthr-ui/src/types/Gap.ts index 20b13e0665..1c87705248 100644 --- a/packages/smarthr-ui/src/types/Gap.ts +++ b/packages/smarthr-ui/src/types/Gap.ts @@ -1,5 +1,6 @@ -import { AbstractSize, CharRelativeSize } from '../themes/createSpacing' +import { AbstractSize, CharRelativeSize, PositiveCharRelativeSize } from '../themes/createSpacing' +export type PositiveGap = PositiveCharRelativeSize | AbstractSize export type Gap = CharRelativeSize | AbstractSize export type SeparateGap = { row: Gap diff --git a/packages/smarthr-ui/src/types/index.ts b/packages/smarthr-ui/src/types/index.ts index 34b138e3c0..7ab2695c64 100644 --- a/packages/smarthr-ui/src/types/index.ts +++ b/packages/smarthr-ui/src/types/index.ts @@ -1,4 +1,4 @@ -export type { Gap, SeparateGap } from './Gap' +export type { Gap, PositiveGap, SeparateGap } from './Gap' export type { DecoratorsType, DecoratorType } from './Decorator' export type { ResponseMessageType } from './ResponseMessage' export type { LocaleMap } from './Locale'