smarthr-ui 97.0.0 → 98.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/AppHeader/components/common/AppLauncherFeatures.cjs +1 -1
- package/lib/components/AppHeader/components/common/AppLauncherFeatures.js +1 -1
- package/lib/components/AppHeader/components/common/AppLauncherSortDropdown.cjs +1 -1
- package/lib/components/AppHeader/components/common/AppLauncherSortDropdown.js +1 -1
- package/lib/components/AppHeader/components/desktop/AppLauncher.cjs +5 -5
- package/lib/components/AppHeader/components/desktop/AppLauncher.cjs.map +1 -1
- package/lib/components/AppHeader/components/desktop/AppLauncher.js +5 -5
- package/lib/components/AppHeader/components/desktop/AppLauncher.js.map +1 -1
- package/lib/components/AppHeader/components/desktop/DesktopHeader.cjs +1 -1
- package/lib/components/AppHeader/components/desktop/DesktopHeader.js +1 -1
- package/lib/components/AppHeader/components/desktop/ReleaseNotesDropdown.cjs +1 -1
- package/lib/components/AppHeader/components/desktop/ReleaseNotesDropdown.js +1 -1
- package/lib/components/AppHeader/components/desktop/UserInfo.cjs +1 -1
- package/lib/components/AppHeader/components/desktop/UserInfo.js +1 -1
- package/lib/components/AppHeader/components/mobile/AppLauncher.cjs +1 -1
- package/lib/components/AppHeader/components/mobile/AppLauncher.js +1 -1
- package/lib/components/AppHeader/components/mobile/AppLauncherFilterDropdown.cjs +1 -1
- package/lib/components/AppHeader/components/mobile/AppLauncherFilterDropdown.js +1 -1
- package/lib/components/AppHeader/components/mobile/Help.cjs +1 -1
- package/lib/components/AppHeader/components/mobile/Help.js +1 -1
- package/lib/components/AppHeader/components/mobile/LanguageSelector.cjs +1 -1
- package/lib/components/AppHeader/components/mobile/LanguageSelector.js +1 -1
- package/lib/components/AppHeader/components/mobile/Menu.cjs +1 -1
- package/lib/components/AppHeader/components/mobile/Menu.js +1 -1
- package/lib/components/AppHeader/components/mobile/MenuAccordion.cjs +1 -1
- package/lib/components/AppHeader/components/mobile/MenuAccordion.js +1 -1
- package/lib/components/AppHeader/components/mobile/MenuDialog.cjs +2 -2
- package/lib/components/AppHeader/components/mobile/MenuDialog.js +2 -2
- package/lib/components/AppHeader/components/mobile/MenuSubHeading.cjs +1 -1
- package/lib/components/AppHeader/components/mobile/MenuSubHeading.js +1 -1
- package/lib/components/AppHeader/components/mobile/ReleaseNote.cjs +1 -1
- package/lib/components/AppHeader/components/mobile/ReleaseNote.js +1 -1
- package/lib/components/AppHeader/components/mobile/UserInfo.cjs +2 -2
- package/lib/components/AppHeader/components/mobile/UserInfo.js +2 -2
- package/lib/components/Browser/Browser.cjs +11 -7
- package/lib/components/Browser/Browser.cjs.map +1 -1
- package/lib/components/Browser/Browser.js +12 -8
- package/lib/components/Browser/Browser.js.map +1 -1
- package/lib/components/Calendar/Calendar.cjs +1 -1
- package/lib/components/Calendar/Calendar.js +1 -1
- package/lib/components/Calendar/CalendarTable.cjs +1 -1
- package/lib/components/Calendar/CalendarTable.js +1 -1
- package/lib/components/Calendar/YearPicker.cjs +1 -1
- package/lib/components/Calendar/YearPicker.js +1 -1
- package/lib/components/Combobox/MultiCombobox/MultiCombobox.cjs +122 -134
- package/lib/components/Combobox/MultiCombobox/MultiCombobox.cjs.map +1 -1
- package/lib/components/Combobox/MultiCombobox/MultiCombobox.js +123 -135
- package/lib/components/Combobox/MultiCombobox/MultiCombobox.js.map +1 -1
- package/lib/components/Combobox/MultiCombobox/MultiSelectedItem.cjs +1 -1
- package/lib/components/Combobox/MultiCombobox/MultiSelectedItem.js +1 -1
- package/lib/components/Combobox/SingleCombobox/SingleCombobox.cjs +96 -46
- package/lib/components/Combobox/SingleCombobox/SingleCombobox.cjs.map +1 -1
- package/lib/components/Combobox/SingleCombobox/SingleCombobox.js +98 -48
- package/lib/components/Combobox/SingleCombobox/SingleCombobox.js.map +1 -1
- package/lib/components/Combobox/useListbox.cjs +1 -1
- package/lib/components/Combobox/useListbox.js +1 -1
- package/lib/components/Combobox/useOptions.cjs +3 -1
- package/lib/components/Combobox/useOptions.cjs.map +1 -1
- package/lib/components/Combobox/useOptions.js +4 -2
- package/lib/components/Combobox/useOptions.js.map +1 -1
- package/lib/components/Dialog/ControlledActionDialog/ActionDialogContentInner.cjs +3 -3
- package/lib/components/Dialog/ControlledActionDialog/ActionDialogContentInner.cjs.map +1 -1
- package/lib/components/Dialog/ControlledActionDialog/ActionDialogContentInner.js +3 -3
- package/lib/components/Dialog/ControlledActionDialog/ActionDialogContentInner.js.map +1 -1
- package/lib/components/Dialog/ControlledFormDialog/FormDialogContentInner.cjs +3 -3
- package/lib/components/Dialog/ControlledFormDialog/FormDialogContentInner.cjs.map +1 -1
- package/lib/components/Dialog/ControlledFormDialog/FormDialogContentInner.js +3 -3
- package/lib/components/Dialog/ControlledFormDialog/FormDialogContentInner.js.map +1 -1
- package/lib/components/Dialog/ControlledMessageDialog/MessageDialogContentInner.cjs +1 -1
- package/lib/components/Dialog/ControlledMessageDialog/MessageDialogContentInner.js +1 -1
- package/lib/components/Dialog/ControlledStepFormDialog/ControlledStepFormDialog.cjs +1 -1
- package/lib/components/Dialog/ControlledStepFormDialog/ControlledStepFormDialog.js +1 -1
- package/lib/components/Dialog/ControlledStepFormDialog/StepFormDialogContentInner.cjs +2 -2
- package/lib/components/Dialog/ControlledStepFormDialog/StepFormDialogContentInner.cjs.map +1 -1
- package/lib/components/Dialog/ControlledStepFormDialog/StepFormDialogContentInner.js +2 -2
- package/lib/components/Dialog/ControlledStepFormDialog/StepFormDialogContentInner.js.map +1 -1
- package/lib/components/Dialog/DialogContentResponseStatusMessage.cjs +3 -8
- package/lib/components/Dialog/DialogContentResponseStatusMessage.cjs.map +1 -1
- package/lib/components/Dialog/DialogContentResponseStatusMessage.d.ts +1 -1
- package/lib/components/Dialog/DialogContentResponseStatusMessage.js +3 -8
- package/lib/components/Dialog/DialogContentResponseStatusMessage.js.map +1 -1
- package/lib/components/Dialog/FocusTrap.cjs +4 -1
- package/lib/components/Dialog/FocusTrap.cjs.map +1 -1
- package/lib/components/Dialog/FocusTrap.js +4 -1
- package/lib/components/Dialog/FocusTrap.js.map +1 -1
- package/lib/components/Dialog/ModelessDialog/ModelessDialog.cjs +1 -1
- package/lib/components/Dialog/ModelessDialog/ModelessDialog.js +1 -1
- package/lib/components/DropZone/DropZone.cjs +1 -1
- package/lib/components/DropZone/DropZone.js +1 -1
- package/lib/components/Dropdown/Dropdown.cjs +36 -34
- package/lib/components/Dropdown/Dropdown.cjs.map +1 -1
- package/lib/components/Dropdown/Dropdown.js +37 -35
- package/lib/components/Dropdown/Dropdown.js.map +1 -1
- package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.cjs +1 -1
- package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.js +1 -1
- package/lib/components/Dropdown/FilterDropdown/FilterDropdown.cjs +3 -5
- package/lib/components/Dropdown/FilterDropdown/FilterDropdown.cjs.map +1 -1
- package/lib/components/Dropdown/FilterDropdown/FilterDropdown.d.ts +1 -1
- package/lib/components/Dropdown/FilterDropdown/FilterDropdown.js +3 -5
- package/lib/components/Dropdown/FilterDropdown/FilterDropdown.js.map +1 -1
- package/lib/components/Dropdown/SortDropdown/useSortDropdown.cjs +1 -1
- package/lib/components/Dropdown/SortDropdown/useSortDropdown.js +1 -1
- package/lib/components/ErrorScreen/AuthErrorScreen.cjs +1 -1
- package/lib/components/ErrorScreen/AuthErrorScreen.js +1 -1
- package/lib/components/ErrorScreen/ForbiddenErrorScreen.cjs +1 -1
- package/lib/components/ErrorScreen/ForbiddenErrorScreen.js +1 -1
- package/lib/components/ErrorScreen/NotFoundErrorScreen.cjs +1 -1
- package/lib/components/ErrorScreen/NotFoundErrorScreen.js +1 -1
- package/lib/components/ErrorScreen/UnauthorizedErrorScreen.cjs +1 -1
- package/lib/components/ErrorScreen/UnauthorizedErrorScreen.js +1 -1
- package/lib/components/FormControl/FormControl.cjs +2 -22
- package/lib/components/FormControl/FormControl.cjs.map +1 -1
- package/lib/components/FormControl/FormControl.js +2 -22
- package/lib/components/FormControl/FormControl.js.map +1 -1
- package/lib/components/Header/AppLauncher/AppLauncher.cjs +1 -1
- package/lib/components/Header/AppLauncher/AppLauncher.js +1 -1
- package/lib/components/Header/LanguageSwitcher/LanguageSwitcher.cjs +1 -1
- package/lib/components/Header/LanguageSwitcher/LanguageSwitcher.js +1 -1
- package/lib/components/Icon/OpenInNewTabIcon.cjs +1 -1
- package/lib/components/Icon/OpenInNewTabIcon.js +1 -1
- package/lib/components/InformationPanel/InformationPanel.cjs +1 -1
- package/lib/components/InformationPanel/InformationPanel.js +1 -1
- package/lib/components/Input/SearchInput/SearchInputIcon.cjs +1 -1
- package/lib/components/Input/SearchInput/SearchInputIcon.js +1 -1
- package/lib/components/InputFile/InputFileMultiplyAppendable.cjs +1 -1
- package/lib/components/InputFile/InputFileMultiplyAppendable.js +1 -1
- package/lib/components/InputFile/InputFileNative.cjs +1 -1
- package/lib/components/InputFile/InputFileNative.js +1 -1
- package/lib/components/Layout/Cluster/Cluster.cjs +0 -1
- package/lib/components/Layout/Cluster/Cluster.cjs.map +1 -1
- package/lib/components/Layout/Cluster/Cluster.js +0 -1
- package/lib/components/Layout/Cluster/Cluster.js.map +1 -1
- package/lib/components/Layout/Container/Container.cjs +1 -5
- package/lib/components/Layout/Container/Container.cjs.map +1 -1
- package/lib/components/Layout/Container/Container.js +1 -5
- package/lib/components/Layout/Container/Container.js.map +1 -1
- package/lib/components/Layout/Stack/Stack.cjs +22 -22
- package/lib/components/Layout/Stack/Stack.cjs.map +1 -1
- package/lib/components/Layout/Stack/Stack.js +22 -22
- package/lib/components/Layout/Stack/Stack.js.map +1 -1
- package/lib/components/Loader/LoaderSpinner.cjs +1 -1
- package/lib/components/Loader/LoaderSpinner.js +1 -1
- package/lib/components/Pagination/Pagination.cjs +1 -1
- package/lib/components/Pagination/Pagination.js +1 -1
- package/lib/components/Pagination/PaginationItemButton.cjs +1 -1
- package/lib/components/Pagination/PaginationItemButton.js +1 -1
- package/lib/components/SideNav/SideNav.cjs +3 -22
- package/lib/components/SideNav/SideNav.cjs.map +1 -1
- package/lib/components/SideNav/SideNav.d.ts +2 -9
- package/lib/components/SideNav/SideNav.js +4 -23
- package/lib/components/SideNav/SideNav.js.map +1 -1
- package/lib/components/SideNav/SideNavContext.cjs +11 -0
- package/lib/components/SideNav/SideNavContext.cjs.map +1 -0
- package/lib/components/SideNav/SideNavContext.d.ts +7 -0
- package/lib/components/SideNav/SideNavContext.js +8 -0
- package/lib/components/SideNav/SideNavContext.js.map +1 -0
- package/lib/components/SideNav/SideNavItemButton.cjs +12 -7
- package/lib/components/SideNav/SideNavItemButton.cjs.map +1 -1
- package/lib/components/SideNav/SideNavItemButton.d.ts +5 -11
- package/lib/components/SideNav/SideNavItemButton.js +12 -7
- package/lib/components/SideNav/SideNavItemButton.js.map +1 -1
- package/lib/components/Table/Th.cjs +2 -2
- package/lib/components/Table/Th.cjs.map +1 -1
- package/lib/components/Table/Th.d.ts +0 -2
- package/lib/components/Table/Th.js +2 -2
- package/lib/components/Table/Th.js.map +1 -1
- package/lib/components/Table/ThCheckbox.cjs +1 -1
- package/lib/components/Table/ThCheckbox.js +1 -1
- package/lib/components/Table/ThSortButton.cjs +5 -6
- package/lib/components/Table/ThSortButton.cjs.map +1 -1
- package/lib/components/Table/ThSortButton.d.ts +1 -6
- package/lib/components/Table/ThSortButton.js +5 -6
- package/lib/components/Table/ThSortButton.js.map +1 -1
- package/lib/components/WarekiPicker/WarekiPicker.cjs +1 -1
- package/lib/components/WarekiPicker/WarekiPicker.js +1 -1
- package/lib/index.cjs +1 -6
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.js +1 -4
- package/lib/index.js.map +1 -1
- package/lib/intl/Localizer.cjs +5 -1
- package/lib/intl/Localizer.cjs.map +1 -1
- package/lib/intl/Localizer.d.ts +1 -1
- package/lib/intl/Localizer.js +5 -1
- package/lib/intl/Localizer.js.map +1 -1
- package/metadata.json +1 -1
- package/package.json +1 -1
- package/smarthr-ui.css +0 -94
- package/lib/hooks/useDecorators.cjs +0 -18
- package/lib/hooks/useDecorators.cjs.map +0 -1
- package/lib/hooks/useDecorators.d.ts +0 -6
- package/lib/hooks/useDecorators.js +0 -16
- package/lib/hooks/useDecorators.js.map +0 -1
- package/lib/hooks/useDevice/DeviceProvider.cjs +0 -37
- package/lib/hooks/useDevice/DeviceProvider.cjs.map +0 -1
- package/lib/hooks/useDevice/DeviceProvider.d.ts +0 -4
- package/lib/hooks/useDevice/DeviceProvider.js +0 -35
- package/lib/hooks/useDevice/DeviceProvider.js.map +0 -1
- package/lib/hooks/useDevice/index.cjs +0 -10
- package/lib/hooks/useDevice/index.cjs.map +0 -1
- package/lib/hooks/useDevice/index.d.ts +0 -2
- package/lib/hooks/useDevice/index.js +0 -3
- package/lib/hooks/useDevice/index.js.map +0 -1
- package/lib/hooks/useDevice/useDevice.cjs +0 -11
- package/lib/hooks/useDevice/useDevice.cjs.map +0 -1
- package/lib/hooks/useDevice/useDevice.d.ts +0 -4
- package/lib/hooks/useDevice/useDevice.js +0 -8
- package/lib/hooks/useDevice/useDevice.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilterDropdown.cjs","sources":["../../../../src/components/Dropdown/FilterDropdown/FilterDropdown.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n type FormEvent,\n type MouseEventHandler,\n type ReactNode,\n isValidElement,\n useMemo,\n} from 'react'\nimport
|
|
1
|
+
{"version":3,"file":"FilterDropdown.cjs","sources":["../../../../src/components/Dropdown/FilterDropdown/FilterDropdown.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n type FormEvent,\n type MouseEventHandler,\n type ReactNode,\n isValidElement,\n useMemo,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { type ResponseStatus, useResponseStatus } from '../../../hooks/useResponseStatus'\nimport { useIntl } from '../../../intl'\nimport { Button, type AbstractProps as ButtonProps } from '../../Button'\nimport { FaCircleCheckIcon, FaFilterIcon, FaRotateLeftIcon } from '../../Icon'\nimport { Cluster, Stack } from '../../Layout'\nimport { ResponseMessage } from '../../ResponseMessage'\nimport { Dropdown } from '../Dropdown'\nimport { DropdownCloser } from '../DropdownCloser'\nimport { DropdownContent } from '../DropdownContent'\nimport { DropdownTrigger } from '../DropdownTrigger'\n\ntype ObjectTriggerType = {\n text?: ReactNode\n /** 引き金となるボタンの大きさ */\n size?: ButtonProps['size']\n /** 引き金となるボタンをアイコンのみとするかどうか */\n onlyIcon?: boolean\n}\ntype AbstractProps = {\n /** 引き金となるボタン */\n trigger?: ReactNode | ObjectTriggerType\n applyText?: ReactNode\n cancelText?: ReactNode\n resetText?: ReactNode\n children: ReactNode\n filtered?:\n | boolean\n | {\n iconAlt?: string\n }\n responseStatus?: ResponseStatus\n onApply: MouseEventHandler<HTMLButtonElement>\n onCancel?: MouseEventHandler<HTMLButtonElement>\n onReset?: MouseEventHandler<HTMLButtonElement>\n onOpen?: () => void\n onClose?: () => void\n}\ntype Props = AbstractProps & Omit<ComponentProps<'button'>, keyof AbstractProps>\n\nconst CONTROL_CLUSTER_GAP: ComponentProps<typeof Cluster>['gap'] = { column: 1, row: 0.5 }\nconst ON_SUBMIT = (e: FormEvent) => {\n e.preventDefault()\n}\n\nconst classNameGenerator = tv({\n slots: {\n iconWrapper: ['smarthr-ui-Icon-extended', 'shr-relative shr-leading-none'],\n filteredIcon: 'shr-absolute shr-bottom-[2px] shr-right-[-4px] shr-h-[0.5em] shr-w-[0.5em]',\n inner: 'shr-p-1.5',\n actionArea: 'shr-border-t-shorthand shr-sticky shr-bottom-0 shr-bg-white shr-px-1.5 shr-py-1',\n resetButtonArea: '-shr-ms-0.5',\n rightButtonArea: 'shr-ms-auto',\n message: 'shr-text-right',\n },\n variants: {\n filtered: {\n true: {\n iconWrapper: 'shr-text-main',\n },\n },\n triggerSize: {\n M: {},\n S: {\n iconWrapper: '-shr-translate-x-0.25',\n },\n },\n },\n})\n\nexport const FilterDropdown: FC<Props> = ({\n trigger: orgTrigger,\n applyText,\n cancelText,\n resetText,\n children,\n filtered,\n responseStatus,\n onApply,\n onCancel,\n onReset,\n onOpen,\n onClose,\n ...rest\n}) => {\n // HINT: ReactNodeとObjectのどちらかを判定\n // typeofはnullの場合もobject判定されてしまうため念の為falsyで判定\n // ReactNodeの一部であるReactElementもobjectとして判定されてしまうためisValidElementで判定\n const trigger: ObjectTriggerType =\n !orgTrigger || typeof orgTrigger !== 'object' || isValidElement(orgTrigger)\n ? {\n text: orgTrigger as ReactNode,\n }\n : (orgTrigger as ObjectTriggerType)\n const { localize } = useIntl()\n\n const decorated = useMemo(\n () => ({\n filteredIconAlt:\n (typeof filtered === 'object' && filtered.iconAlt) ||\n localize({\n id: 'smarthr-ui/FilterDropdown/status',\n defaultText: '適用中',\n }),\n trigger:\n trigger.text ||\n localize({\n id: 'smarthr-ui/FilterDropdown/triggerText',\n defaultText: '絞り込み',\n }),\n applyText:\n applyText ||\n localize({\n id: 'smarthr-ui/FilterDropdown/applyText',\n defaultText: '適用',\n }),\n cancelText:\n cancelText ||\n localize({\n id: 'smarthr-ui/FilterDropdown/cancelText',\n defaultText: 'キャンセル',\n }),\n resetText:\n resetText ||\n localize({\n id: 'smarthr-ui/FilterDropdown/resetText',\n defaultText: '絞り込み条件を解除',\n }),\n }),\n [filtered, trigger.text, applyText, cancelText, resetText, localize],\n )\n\n const calcedResponseStatus = useResponseStatus(responseStatus)\n\n const classNamesMapper = useMemo(() => {\n const {\n iconWrapper,\n filteredIcon,\n inner,\n actionArea,\n resetButtonArea,\n rightButtonArea,\n message,\n } = classNameGenerator()\n\n const commonStyles = {\n filteredIcon: filteredIcon(),\n inner: inner(),\n actionArea: actionArea(),\n resetButtonArea: resetButtonArea(),\n rightButtonArea: rightButtonArea(),\n message: message(),\n }\n\n return {\n filtered: {\n ...commonStyles,\n iconWrapper: iconWrapper({ filtered: true, triggerSize: trigger.size }),\n },\n unfiltered: {\n ...commonStyles,\n iconWrapper: iconWrapper({ filtered: false, triggerSize: trigger.size }),\n },\n }\n }, [trigger.size])\n\n const classNames = classNamesMapper[filtered ? 'filtered' : 'unfiltered']\n\n const { buttonSuffix, buttonContent } = useMemo(() => {\n const FilterIcon = (\n <span className={classNames.iconWrapper}>\n <FaFilterIcon alt={trigger.onlyIcon ? decorated.trigger : undefined} />\n\n {filtered && (\n // HINT: altに揃えたいが、styleが複雑になってしまうためaria-labelを利用している\n <FaCircleCheckIcon\n aria-label={decorated.filteredIconAlt}\n className={classNames.filteredIcon}\n />\n )}\n </span>\n )\n\n if (trigger.onlyIcon) {\n return {\n buttonSuffix: undefined,\n buttonContent: FilterIcon,\n }\n }\n\n return {\n buttonSuffix: FilterIcon,\n buttonContent: decorated.trigger,\n }\n }, [filtered, decorated.trigger, decorated.filteredIconAlt, trigger.onlyIcon, classNames])\n\n return (\n <Dropdown onOpen={onOpen} onClose={onClose}>\n <DropdownTrigger tooltip={{ show: trigger.onlyIcon, message: decorated.trigger }}>\n <Button {...rest} suffix={buttonSuffix} size={trigger.size}>\n {buttonContent}\n </Button>\n </DropdownTrigger>\n <DropdownContent controllable>\n <form onSubmit={ON_SUBMIT}>\n <div className={classNames.inner}>{children}</div>\n <Stack gap={0.5} className={classNames.actionArea}>\n <Cluster gap={1} align=\"center\" justify=\"space-between\">\n {onReset && (\n <div className={classNames.resetButtonArea}>\n <Button\n variant=\"text\"\n size=\"S\"\n prefix={<FaRotateLeftIcon />}\n onClick={onReset}\n disabled={calcedResponseStatus.isProcessing}\n >\n {decorated.resetText}\n </Button>\n </div>\n )}\n\n <Cluster\n gap={CONTROL_CLUSTER_GAP}\n justify=\"flex-end\"\n className={classNames.rightButtonArea}\n >\n <DropdownCloser>\n <Button onClick={onCancel} disabled={calcedResponseStatus.isProcessing}>\n {decorated.cancelText}\n </Button>\n </DropdownCloser>\n <DropdownCloser>\n <Button\n variant=\"primary\"\n onClick={onApply}\n loading={calcedResponseStatus.isProcessing}\n >\n {decorated.applyText}\n </Button>\n </DropdownCloser>\n </Cluster>\n </Cluster>\n {calcedResponseStatus.message && (\n <div className={classNames.message}>\n <ResponseMessage status={calcedResponseStatus.status} role=\"alert\">\n {calcedResponseStatus.message}\n </ResponseMessage>\n </div>\n )}\n </Stack>\n </form>\n </DropdownContent>\n </Dropdown>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA;AACA;;AAEA;AAEA;AACE;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACD;AACD;AACE;AACE;AACE;AACD;AACF;AACD;AACE;AACA;AACE;AACD;AACF;AACF;AACF;AAEM;;;;AAkBL;AAEI;AACI;AACD;;AAEP;AAEA;;AAIM;AACE;AACA;;;AAIF;AACE;AACA;;AAEJ;AAEE;AACE;AACA;;AAEJ;AAEE;AACE;AACA;;AAEJ;AAEE;AACE;AACA;;AAEL;AAIH;AAEA;AACE;AAUA;;;;;;;;;AAUE;AACE;AACA;AACD;AACD;AACE;AACA;AACD;;AAEL;AAEA;;AAGE;;AAMM;AAQN;;AAEI;AACA;;;;AAKF;;;AAGJ;;AA6DF;;"}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
3
|
import { isValidElement, useMemo } from 'react';
|
|
4
|
-
import innerText from '../../../_virtual/index3.js';
|
|
5
4
|
import { tv as ce } from './../../../vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.17_@types_node@20.19.35_typescript@5.9.3__/vendor/tailwind-variants/dist/index.js';
|
|
6
5
|
import { useResponseStatus } from '../../../hooks/useResponseStatus.js';
|
|
7
6
|
import '../../../intl/IntlProvider.js';
|
|
8
7
|
import { useIntl } from '../../../intl/useIntl.js';
|
|
9
|
-
import '
|
|
8
|
+
import '../../../intl/Localizer.js';
|
|
10
9
|
import '../../../_virtual/dayjs.min.js';
|
|
11
10
|
import '../../../intl/TimeFormatter.js';
|
|
12
11
|
import { Button } from '../../Button/Button.js';
|
|
@@ -92,7 +91,6 @@ const FilterDropdown = ({ trigger: orgTrigger, applyText, cancelText, resetText,
|
|
|
92
91
|
defaultText: '絞り込み条件を解除',
|
|
93
92
|
}),
|
|
94
93
|
}), [filtered, trigger.text, applyText, cancelText, resetText, localize]);
|
|
95
|
-
const filteredIconAlt = useMemo(() => innerText(decorated.filteredIconAlt), [decorated.filteredIconAlt]);
|
|
96
94
|
const calcedResponseStatus = useResponseStatus(responseStatus);
|
|
97
95
|
const classNamesMapper = useMemo(() => {
|
|
98
96
|
const { iconWrapper, filteredIcon, inner, actionArea, resetButtonArea, rightButtonArea, message, } = classNameGenerator();
|
|
@@ -119,7 +117,7 @@ const FilterDropdown = ({ trigger: orgTrigger, applyText, cancelText, resetText,
|
|
|
119
117
|
const { buttonSuffix, buttonContent } = useMemo(() => {
|
|
120
118
|
const FilterIcon = (jsxs("span", { className: classNames.iconWrapper, children: [jsx(FaFilterIcon, { alt: trigger.onlyIcon ? decorated.trigger : undefined }), filtered && (
|
|
121
119
|
// HINT: altに揃えたいが、styleが複雑になってしまうためaria-labelを利用している
|
|
122
|
-
jsx(FaCircleCheckIcon, { "aria-label": filteredIconAlt, className: classNames.filteredIcon }))] }));
|
|
120
|
+
jsx(FaCircleCheckIcon, { "aria-label": decorated.filteredIconAlt, className: classNames.filteredIcon }))] }));
|
|
123
121
|
if (trigger.onlyIcon) {
|
|
124
122
|
return {
|
|
125
123
|
buttonSuffix: undefined,
|
|
@@ -130,7 +128,7 @@ const FilterDropdown = ({ trigger: orgTrigger, applyText, cancelText, resetText,
|
|
|
130
128
|
buttonSuffix: FilterIcon,
|
|
131
129
|
buttonContent: decorated.trigger,
|
|
132
130
|
};
|
|
133
|
-
}, [filtered, decorated.trigger, trigger.onlyIcon,
|
|
131
|
+
}, [filtered, decorated.trigger, decorated.filteredIconAlt, trigger.onlyIcon, classNames]);
|
|
134
132
|
return (jsxs(Dropdown, { onOpen: onOpen, onClose: onClose, children: [jsx(DropdownTrigger, { tooltip: { show: trigger.onlyIcon, message: decorated.trigger }, children: jsx(Button, { ...rest, suffix: buttonSuffix, size: trigger.size, children: buttonContent }) }), jsx(DropdownContent, { controllable: true, children: jsxs("form", { onSubmit: ON_SUBMIT, children: [jsx("div", { className: classNames.inner, children: children }), jsxs(Stack, { gap: 0.5, className: classNames.actionArea, children: [jsxs(Cluster, { gap: 1, align: "center", justify: "space-between", children: [onReset && (jsx("div", { className: classNames.resetButtonArea, children: jsx(Button, { variant: "text", size: "S", prefix: jsx(FaRotateLeftIcon, {}), onClick: onReset, disabled: calcedResponseStatus.isProcessing, children: decorated.resetText }) })), jsxs(Cluster, { gap: CONTROL_CLUSTER_GAP, justify: "flex-end", className: classNames.rightButtonArea, children: [jsx(DropdownCloser, { children: jsx(Button, { onClick: onCancel, disabled: calcedResponseStatus.isProcessing, children: decorated.cancelText }) }), jsx(DropdownCloser, { children: jsx(Button, { variant: "primary", onClick: onApply, loading: calcedResponseStatus.isProcessing, children: decorated.applyText }) })] })] }), calcedResponseStatus.message && (jsx("div", { className: classNames.message, children: jsx(ResponseMessage, { status: calcedResponseStatus.status, role: "alert", children: calcedResponseStatus.message }) }))] })] }) })] }));
|
|
135
133
|
};
|
|
136
134
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilterDropdown.js","sources":["../../../../src/components/Dropdown/FilterDropdown/FilterDropdown.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n type FormEvent,\n type MouseEventHandler,\n type ReactNode,\n isValidElement,\n useMemo,\n} from 'react'\nimport
|
|
1
|
+
{"version":3,"file":"FilterDropdown.js","sources":["../../../../src/components/Dropdown/FilterDropdown/FilterDropdown.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n type FormEvent,\n type MouseEventHandler,\n type ReactNode,\n isValidElement,\n useMemo,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { type ResponseStatus, useResponseStatus } from '../../../hooks/useResponseStatus'\nimport { useIntl } from '../../../intl'\nimport { Button, type AbstractProps as ButtonProps } from '../../Button'\nimport { FaCircleCheckIcon, FaFilterIcon, FaRotateLeftIcon } from '../../Icon'\nimport { Cluster, Stack } from '../../Layout'\nimport { ResponseMessage } from '../../ResponseMessage'\nimport { Dropdown } from '../Dropdown'\nimport { DropdownCloser } from '../DropdownCloser'\nimport { DropdownContent } from '../DropdownContent'\nimport { DropdownTrigger } from '../DropdownTrigger'\n\ntype ObjectTriggerType = {\n text?: ReactNode\n /** 引き金となるボタンの大きさ */\n size?: ButtonProps['size']\n /** 引き金となるボタンをアイコンのみとするかどうか */\n onlyIcon?: boolean\n}\ntype AbstractProps = {\n /** 引き金となるボタン */\n trigger?: ReactNode | ObjectTriggerType\n applyText?: ReactNode\n cancelText?: ReactNode\n resetText?: ReactNode\n children: ReactNode\n filtered?:\n | boolean\n | {\n iconAlt?: string\n }\n responseStatus?: ResponseStatus\n onApply: MouseEventHandler<HTMLButtonElement>\n onCancel?: MouseEventHandler<HTMLButtonElement>\n onReset?: MouseEventHandler<HTMLButtonElement>\n onOpen?: () => void\n onClose?: () => void\n}\ntype Props = AbstractProps & Omit<ComponentProps<'button'>, keyof AbstractProps>\n\nconst CONTROL_CLUSTER_GAP: ComponentProps<typeof Cluster>['gap'] = { column: 1, row: 0.5 }\nconst ON_SUBMIT = (e: FormEvent) => {\n e.preventDefault()\n}\n\nconst classNameGenerator = tv({\n slots: {\n iconWrapper: ['smarthr-ui-Icon-extended', 'shr-relative shr-leading-none'],\n filteredIcon: 'shr-absolute shr-bottom-[2px] shr-right-[-4px] shr-h-[0.5em] shr-w-[0.5em]',\n inner: 'shr-p-1.5',\n actionArea: 'shr-border-t-shorthand shr-sticky shr-bottom-0 shr-bg-white shr-px-1.5 shr-py-1',\n resetButtonArea: '-shr-ms-0.5',\n rightButtonArea: 'shr-ms-auto',\n message: 'shr-text-right',\n },\n variants: {\n filtered: {\n true: {\n iconWrapper: 'shr-text-main',\n },\n },\n triggerSize: {\n M: {},\n S: {\n iconWrapper: '-shr-translate-x-0.25',\n },\n },\n },\n})\n\nexport const FilterDropdown: FC<Props> = ({\n trigger: orgTrigger,\n applyText,\n cancelText,\n resetText,\n children,\n filtered,\n responseStatus,\n onApply,\n onCancel,\n onReset,\n onOpen,\n onClose,\n ...rest\n}) => {\n // HINT: ReactNodeとObjectのどちらかを判定\n // typeofはnullの場合もobject判定されてしまうため念の為falsyで判定\n // ReactNodeの一部であるReactElementもobjectとして判定されてしまうためisValidElementで判定\n const trigger: ObjectTriggerType =\n !orgTrigger || typeof orgTrigger !== 'object' || isValidElement(orgTrigger)\n ? {\n text: orgTrigger as ReactNode,\n }\n : (orgTrigger as ObjectTriggerType)\n const { localize } = useIntl()\n\n const decorated = useMemo(\n () => ({\n filteredIconAlt:\n (typeof filtered === 'object' && filtered.iconAlt) ||\n localize({\n id: 'smarthr-ui/FilterDropdown/status',\n defaultText: '適用中',\n }),\n trigger:\n trigger.text ||\n localize({\n id: 'smarthr-ui/FilterDropdown/triggerText',\n defaultText: '絞り込み',\n }),\n applyText:\n applyText ||\n localize({\n id: 'smarthr-ui/FilterDropdown/applyText',\n defaultText: '適用',\n }),\n cancelText:\n cancelText ||\n localize({\n id: 'smarthr-ui/FilterDropdown/cancelText',\n defaultText: 'キャンセル',\n }),\n resetText:\n resetText ||\n localize({\n id: 'smarthr-ui/FilterDropdown/resetText',\n defaultText: '絞り込み条件を解除',\n }),\n }),\n [filtered, trigger.text, applyText, cancelText, resetText, localize],\n )\n\n const calcedResponseStatus = useResponseStatus(responseStatus)\n\n const classNamesMapper = useMemo(() => {\n const {\n iconWrapper,\n filteredIcon,\n inner,\n actionArea,\n resetButtonArea,\n rightButtonArea,\n message,\n } = classNameGenerator()\n\n const commonStyles = {\n filteredIcon: filteredIcon(),\n inner: inner(),\n actionArea: actionArea(),\n resetButtonArea: resetButtonArea(),\n rightButtonArea: rightButtonArea(),\n message: message(),\n }\n\n return {\n filtered: {\n ...commonStyles,\n iconWrapper: iconWrapper({ filtered: true, triggerSize: trigger.size }),\n },\n unfiltered: {\n ...commonStyles,\n iconWrapper: iconWrapper({ filtered: false, triggerSize: trigger.size }),\n },\n }\n }, [trigger.size])\n\n const classNames = classNamesMapper[filtered ? 'filtered' : 'unfiltered']\n\n const { buttonSuffix, buttonContent } = useMemo(() => {\n const FilterIcon = (\n <span className={classNames.iconWrapper}>\n <FaFilterIcon alt={trigger.onlyIcon ? decorated.trigger : undefined} />\n\n {filtered && (\n // HINT: altに揃えたいが、styleが複雑になってしまうためaria-labelを利用している\n <FaCircleCheckIcon\n aria-label={decorated.filteredIconAlt}\n className={classNames.filteredIcon}\n />\n )}\n </span>\n )\n\n if (trigger.onlyIcon) {\n return {\n buttonSuffix: undefined,\n buttonContent: FilterIcon,\n }\n }\n\n return {\n buttonSuffix: FilterIcon,\n buttonContent: decorated.trigger,\n }\n }, [filtered, decorated.trigger, decorated.filteredIconAlt, trigger.onlyIcon, classNames])\n\n return (\n <Dropdown onOpen={onOpen} onClose={onClose}>\n <DropdownTrigger tooltip={{ show: trigger.onlyIcon, message: decorated.trigger }}>\n <Button {...rest} suffix={buttonSuffix} size={trigger.size}>\n {buttonContent}\n </Button>\n </DropdownTrigger>\n <DropdownContent controllable>\n <form onSubmit={ON_SUBMIT}>\n <div className={classNames.inner}>{children}</div>\n <Stack gap={0.5} className={classNames.actionArea}>\n <Cluster gap={1} align=\"center\" justify=\"space-between\">\n {onReset && (\n <div className={classNames.resetButtonArea}>\n <Button\n variant=\"text\"\n size=\"S\"\n prefix={<FaRotateLeftIcon />}\n onClick={onReset}\n disabled={calcedResponseStatus.isProcessing}\n >\n {decorated.resetText}\n </Button>\n </div>\n )}\n\n <Cluster\n gap={CONTROL_CLUSTER_GAP}\n justify=\"flex-end\"\n className={classNames.rightButtonArea}\n >\n <DropdownCloser>\n <Button onClick={onCancel} disabled={calcedResponseStatus.isProcessing}>\n {decorated.cancelText}\n </Button>\n </DropdownCloser>\n <DropdownCloser>\n <Button\n variant=\"primary\"\n onClick={onApply}\n loading={calcedResponseStatus.isProcessing}\n >\n {decorated.applyText}\n </Button>\n </DropdownCloser>\n </Cluster>\n </Cluster>\n {calcedResponseStatus.message && (\n <div className={classNames.message}>\n <ResponseMessage status={calcedResponseStatus.status} role=\"alert\">\n {calcedResponseStatus.message}\n </ResponseMessage>\n </div>\n )}\n </Stack>\n </form>\n </DropdownContent>\n </Dropdown>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA;AACA;;AAEA;AAEA;AACE;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACD;AACD;AACE;AACE;AACE;AACD;AACF;AACD;AACE;AACA;AACE;AACD;AACF;AACF;AACF;AAEM;;;;AAkBL;AAEI;AACI;AACD;;AAEP;AAEA;;AAIM;AACE;AACA;;;AAIF;AACE;AACA;;AAEJ;AAEE;AACE;AACA;;AAEJ;AAEE;AACE;AACA;;AAEJ;AAEE;AACE;AACA;;AAEL;AAIH;AAEA;AACE;AAUA;;;;;;;;;AAUE;AACE;AACA;AACD;AACD;AACE;AACA;AACD;;AAEL;AAEA;;AAGE;;AAMM;AAQN;;AAEI;AACA;;;;AAKF;;;AAGJ;;AA6DF;;"}
|
|
@@ -4,8 +4,8 @@ var React = require('react');
|
|
|
4
4
|
var index = require('./../../../vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.17_@types_node@20.19.35_typescript@5.9.3__/vendor/tailwind-variants/dist/index.cjs');
|
|
5
5
|
require('../../../intl/IntlProvider.cjs');
|
|
6
6
|
var intl_useIntl = require('../../../intl/useIntl.cjs');
|
|
7
|
+
require('../../../intl/Localizer.cjs');
|
|
7
8
|
require('react/jsx-runtime');
|
|
8
|
-
require('react-intl');
|
|
9
9
|
require('../../../_virtual/dayjs.min.cjs');
|
|
10
10
|
require('../../../intl/TimeFormatter.cjs');
|
|
11
11
|
var components_Icon_FaIcon = require('../../Icon/FaIcon.cjs');
|
|
@@ -2,8 +2,8 @@ import { useMemo, useState, useEffect, useCallback } from 'react';
|
|
|
2
2
|
import { tv as ce } from './../../../vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.17_@types_node@20.19.35_typescript@5.9.3__/vendor/tailwind-variants/dist/index.js';
|
|
3
3
|
import '../../../intl/IntlProvider.js';
|
|
4
4
|
import { useIntl } from '../../../intl/useIntl.js';
|
|
5
|
+
import '../../../intl/Localizer.js';
|
|
5
6
|
import 'react/jsx-runtime';
|
|
6
|
-
import 'react-intl';
|
|
7
7
|
import '../../../_virtual/dayjs.min.js';
|
|
8
8
|
import '../../../intl/TimeFormatter.js';
|
|
9
9
|
import { FaArrowUpWideShortIcon, FaArrowDownWideShortIcon } from '../../Icon/FaIcon.js';
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
5
|
require('../../intl/IntlProvider.cjs');
|
|
6
6
|
var intl_useIntl = require('../../intl/useIntl.cjs');
|
|
7
|
-
require('
|
|
7
|
+
require('../../intl/Localizer.cjs');
|
|
8
8
|
require('../../_virtual/dayjs.min.cjs');
|
|
9
9
|
require('../../intl/TimeFormatter.cjs');
|
|
10
10
|
var components_ErrorScreen_ErrorScreen = require('./ErrorScreen.cjs');
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx } from 'react/jsx-runtime';
|
|
3
3
|
import '../../intl/IntlProvider.js';
|
|
4
4
|
import { useIntl } from '../../intl/useIntl.js';
|
|
5
|
-
import '
|
|
5
|
+
import '../../intl/Localizer.js';
|
|
6
6
|
import '../../_virtual/dayjs.min.js';
|
|
7
7
|
import '../../intl/TimeFormatter.js';
|
|
8
8
|
import { ErrorScreen } from './ErrorScreen.js';
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
5
|
require('../../intl/IntlProvider.cjs');
|
|
6
6
|
var intl_useIntl = require('../../intl/useIntl.cjs');
|
|
7
|
-
require('
|
|
7
|
+
require('../../intl/Localizer.cjs');
|
|
8
8
|
require('../../_virtual/dayjs.min.cjs');
|
|
9
9
|
require('../../intl/TimeFormatter.cjs');
|
|
10
10
|
var components_ErrorScreen_ErrorScreen = require('./ErrorScreen.cjs');
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx } from 'react/jsx-runtime';
|
|
3
3
|
import '../../intl/IntlProvider.js';
|
|
4
4
|
import { useIntl } from '../../intl/useIntl.js';
|
|
5
|
-
import '
|
|
5
|
+
import '../../intl/Localizer.js';
|
|
6
6
|
import '../../_virtual/dayjs.min.js';
|
|
7
7
|
import '../../intl/TimeFormatter.js';
|
|
8
8
|
import { ErrorScreen } from './ErrorScreen.js';
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
5
|
require('../../intl/IntlProvider.cjs');
|
|
6
6
|
var intl_useIntl = require('../../intl/useIntl.cjs');
|
|
7
|
-
require('
|
|
7
|
+
require('../../intl/Localizer.cjs');
|
|
8
8
|
require('../../_virtual/dayjs.min.cjs');
|
|
9
9
|
require('../../intl/TimeFormatter.cjs');
|
|
10
10
|
var components_ErrorScreen_ErrorScreen = require('./ErrorScreen.cjs');
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx } from 'react/jsx-runtime';
|
|
3
3
|
import '../../intl/IntlProvider.js';
|
|
4
4
|
import { useIntl } from '../../intl/useIntl.js';
|
|
5
|
-
import '
|
|
5
|
+
import '../../intl/Localizer.js';
|
|
6
6
|
import '../../_virtual/dayjs.min.js';
|
|
7
7
|
import '../../intl/TimeFormatter.js';
|
|
8
8
|
import { ErrorScreen } from './ErrorScreen.js';
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
5
|
require('../../intl/IntlProvider.cjs');
|
|
6
6
|
var intl_useIntl = require('../../intl/useIntl.cjs');
|
|
7
|
-
require('
|
|
7
|
+
require('../../intl/Localizer.cjs');
|
|
8
8
|
require('../../_virtual/dayjs.min.cjs');
|
|
9
9
|
require('../../intl/TimeFormatter.cjs');
|
|
10
10
|
var components_Button_Button = require('../Button/Button.cjs');
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
3
|
import '../../intl/IntlProvider.js';
|
|
4
4
|
import { useIntl } from '../../intl/useIntl.js';
|
|
5
|
-
import '
|
|
5
|
+
import '../../intl/Localizer.js';
|
|
6
6
|
import '../../_virtual/dayjs.min.js';
|
|
7
7
|
import '../../intl/TimeFormatter.js';
|
|
8
8
|
import { Button } from '../Button/Button.js';
|
|
@@ -35,7 +35,6 @@ const classNameGenerator = index.tv({
|
|
|
35
35
|
errorList: ['shr-list-none'],
|
|
36
36
|
errorIcon: ['smarthr-ui-FormControl-errorMessage-Icon', 'shr-text-danger'],
|
|
37
37
|
errorMessage: ['smarthr-ui-FormControl-errorMessage'],
|
|
38
|
-
underLabelStack: ['[&&&]:shr-mt-0'],
|
|
39
38
|
childrenWrapper: [],
|
|
40
39
|
},
|
|
41
40
|
variants: {
|
|
@@ -76,13 +75,6 @@ const classNameGenerator = index.tv({
|
|
|
76
75
|
{
|
|
77
76
|
innerMargin: undefined,
|
|
78
77
|
isFieldset: true,
|
|
79
|
-
class: {
|
|
80
|
-
childrenWrapper: '[:not([hidden])_~_&&&]:shr-mt-1',
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
innerMargin: undefined,
|
|
85
|
-
isFieldset: false,
|
|
86
78
|
class: {
|
|
87
79
|
childrenWrapper: '[:not([hidden])_~_&&&]:shr-mt-0.5',
|
|
88
80
|
},
|
|
@@ -122,7 +114,6 @@ const ActualFormControl = ({ label: orgLabel, subActionArea, innerMargin, status
|
|
|
122
114
|
}
|
|
123
115
|
return Array.isArray(errorMessages) ? errorMessages : [errorMessages];
|
|
124
116
|
}, [errorMessages]);
|
|
125
|
-
const actualInnerMargin = React.useMemo(() => innerMargin ?? 0.5, [innerMargin]);
|
|
126
117
|
const classNames = React.useMemo(() => {
|
|
127
118
|
const generators = classNameGenerator({ innerMargin, isFieldset });
|
|
128
119
|
return {
|
|
@@ -133,7 +124,6 @@ const ActualFormControl = ({ label: orgLabel, subActionArea, innerMargin, status
|
|
|
133
124
|
errorList: generators.errorList(),
|
|
134
125
|
errorIcon: generators.errorIcon(),
|
|
135
126
|
errorMessage: generators.errorMessage(),
|
|
136
|
-
underLabelStack: generators.underLabelStack(),
|
|
137
127
|
childrenWrapper: generators.childrenWrapper(),
|
|
138
128
|
};
|
|
139
129
|
}, [innerMargin, isFieldset, label.unrecommendedHide, className]);
|
|
@@ -217,14 +207,7 @@ const ActualFormControl = ({ label: orgLabel, subActionArea, innerMargin, status
|
|
|
217
207
|
}
|
|
218
208
|
});
|
|
219
209
|
}, [isFieldset, label.text]);
|
|
220
|
-
|
|
221
|
-
// HINT: label.unrecommendedHideの場合、body以下の余白の計算を簡略化するため
|
|
222
|
-
// Stackをネストし、そのStackに対してmargin-top: 0を指定する
|
|
223
|
-
// こうすることでinner Stack以下の要素は擬似的にStackの最初の要素になる
|
|
224
|
-
if (label.unrecommendedHide) {
|
|
225
|
-
body = (jsxRuntime.jsx(components_Layout_Stack_Stack.Stack, { gap: actualInnerMargin, className: classNames.underLabelStack, children: body }));
|
|
226
|
-
}
|
|
227
|
-
return (jsxRuntime.jsxs(components_Layout_Stack_Stack.Stack, { ...rest, as: as, gap: actualInnerMargin, "aria-describedby": isFieldset && describedbyIds ? describedbyIds : undefined, className: classNames.wrapper, children: [jsxRuntime.jsx(LabelCluster, { isFieldset: isFieldset, managedHtmlFor: managedHtmlFor, managedLabelId: managedLabelId, unrecommendedHideLabel: label.unrecommendedHide, labelType: label.styleType, label: label.text, labelIcon: label.icon, statusLabels: actualStatusLabels, subActionArea: subActionArea, labelClassName: classNames.label }), body] }));
|
|
210
|
+
return (jsxRuntime.jsxs(components_Layout_Stack_Stack.Stack, { ...rest, as: as, gap: innerMargin ?? 0.5, "aria-describedby": isFieldset && describedbyIds ? describedbyIds : undefined, className: classNames.wrapper, children: [jsxRuntime.jsx(LabelCluster, { isFieldset: isFieldset, managedHtmlFor: managedHtmlFor, managedLabelId: managedLabelId, unrecommendedHideLabel: label.unrecommendedHide, labelType: label.styleType, label: label.text, labelIcon: label.icon, statusLabels: actualStatusLabels, subActionArea: subActionArea, labelClassName: classNames.label }), jsxRuntime.jsx(HelpMessageParagraph, { helpMessage: helpMessage, managedHtmlFor: managedHtmlFor }), jsxRuntime.jsx(ExampleMessageText, { exampleMessage: exampleMessage, managedHtmlFor: managedHtmlFor }), jsxRuntime.jsx(ErrorMessageList, { errorMessages: actualErrorMessages, managedHtmlFor: managedHtmlFor, classNames: classNames }), jsxRuntime.jsx("div", { className: classNames.childrenWrapper, ref: inputWrapperRef, children: children }), jsxRuntime.jsx(SupplementaryMessageText, { supplementaryMessage: supplementaryMessage, managedHtmlFor: managedHtmlFor })] }));
|
|
228
211
|
};
|
|
229
212
|
const LabelCluster = React.memo(({ isFieldset, managedHtmlFor, managedLabelId, unrecommendedHideLabel, labelType = 'blockTitle', label, labelIcon, subActionArea, labelClassName, statusLabels, }) => {
|
|
230
213
|
const body = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(components_Text_Text.Text, { styleType: labelType, icon: labelIcon, children: label }), jsxRuntime.jsx(StatusLabelCluster, { statusLabels: statusLabels })] }));
|
|
@@ -261,10 +244,7 @@ const LabelCluster = React.memo(({ isFieldset, managedHtmlFor, managedLabelId, u
|
|
|
261
244
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [attrs.visuallyHidden && (jsxRuntime.jsx(components_VisuallyHiddenText_VisuallyHiddenText.VisuallyHiddenText, { ...attrs.visuallyHidden, children:
|
|
262
245
|
// HINT: innerTextでは正しく文字が取得できない場合がある
|
|
263
246
|
// 安全策としてinnerTextが空を取得してきたらbody自体を埋めこみます
|
|
264
|
-
index$1(body) || body })), attrs.label && (jsxRuntime.jsxs(components_Layout_Cluster_Cluster.Cluster, { justify: "space-between",
|
|
265
|
-
// HINT: UI上、常にトップの要素になるため、Stackの計算が狂わないよう、
|
|
266
|
-
// 常にmargin-topを0にする
|
|
267
|
-
className: "[&&&]:shr--mt-0", children: [jsxRuntime.jsx(components_Layout_Cluster_Cluster.Cluster, { ...attrs.label, align: "center", className: labelClassName, children: body }), subActionArea && jsxRuntime.jsx("div", { className: "shr-grow", children: subActionArea })] }))] }));
|
|
247
|
+
index$1(body) || body })), attrs.label && (jsxRuntime.jsxs(components_Layout_Cluster_Cluster.Cluster, { justify: "space-between", children: [jsxRuntime.jsx(components_Layout_Cluster_Cluster.Cluster, { ...attrs.label, align: "center", className: labelClassName, children: body }), subActionArea && jsxRuntime.jsx("div", { className: "shr-grow", children: subActionArea })] }))] }));
|
|
268
248
|
});
|
|
269
249
|
const StatusLabelCluster = React.memo(({ statusLabels }) => statusLabels.length === 0 ? null : (jsxRuntime.jsx(components_Layout_Cluster_Cluster.Cluster, { gap: 0.25, as: "span", children: statusLabels })));
|
|
270
250
|
const HelpMessageParagraph = React.memo(({ helpMessage, managedHtmlFor }) => helpMessage ? (jsxRuntime.jsx("p", { className: "smarthr-ui-FormControl-helpMessage", id: `${managedHtmlFor}_helpMessage`, children: helpMessage })) : null);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormControl.cjs","sources":["../../../src/components/FormControl/FormControl.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type ComponentPropsWithoutRef,\n type ComponentType,\n type FC,\n type FunctionComponentElement,\n type PropsWithChildren,\n type ReactNode,\n memo,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { useId } from 'react'\nimport innerText from 'react-innertext'\nimport { tv } from 'tailwind-variants'\n\nimport { useObjectAttributes } from '../../hooks/useObjectAttributes'\nimport { FaCircleExclamationIcon } from '../Icon'\nimport { Cluster, Stack } from '../Layout'\nimport { Text, type TextProps } from '../Text'\nimport { VisuallyHiddenText, visuallyHiddenTextClassName } from '../VisuallyHiddenText'\n\nimport type { Gap } from '../../types'\nimport type { StatusLabel } from '../StatusLabel'\n\ntype StatusLabelType = FunctionComponentElement<ComponentProps<typeof StatusLabel>>\ntype IconType = ComponentProps<typeof Text>['icon']\n\ntype ObjectLabelType = {\n text: ReactNode\n /** ラベルの表示タイプ */\n styleType?: TextProps['styleType']\n /** ラベル左に設置するアイコン */\n icon?: IconType\n /** ラベルを視覚的に隠すかどうか */\n unrecommendedHide?: boolean\n /** ラベルを紐づける入力要素のID属性と同じ値 */\n htmlFor?: string\n /** ラベルに適用する `id` 値 */\n id?: string\n}\ntype AbstractProps = PropsWithChildren<{\n /** グループのラベル名 */\n label: ReactNode | ObjectLabelType\n /** タイトル右の領域 */\n subActionArea?: ReactNode\n /** タイトル群と子要素の間の間隔調整用(基本的には不要) */\n innerMargin?: Gap\n /** タイトルの隣に表示する `StatusLabel` の配列 */\n statusLabels?: StatusLabelType | StatusLabelType[]\n /** タイトルの下に表示するヘルプメッセージ */\n helpMessage?: ReactNode\n /** タイトルの下に表示する入力例 */\n exampleMessage?: ReactNode\n /** タイトルの下に表示するエラーメッセージ */\n errorMessages?: ReactNode | ReactNode[]\n /** エラーがある場合に自動的に入力要素を error にするかどうか */\n autoBindErrorInput?: boolean\n /** フォームコントロールの下に表示する補足メッセージ */\n supplementaryMessage?: ReactNode\n /** `true` のとき、文字色を `TEXT_DISABLED` にする */\n disabled?: boolean\n as?: string | ComponentType<any>\n}>\ntype Props = AbstractProps &\n Omit<ComponentPropsWithoutRef<'div'>, keyof AbstractProps | 'aria-labelledby'>\n\nconst labelObjectConverter = (label: ReactNode) => ({ text: label })\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: [\n 'smarthr-ui-FormControl',\n 'shr-mx-[unset] shr-border-none shr-p-[unset]',\n 'disabled:shr-text-disabled',\n '[&:disabled_.smarthr-ui-FormControl-label_>_span]:shr-text-disabled',\n '[&:disabled_.smarthr-ui-FormControl-exampleMessage]:shr-text-color-inherit',\n '[&:disabled_.smarthr-ui-FormControl-errorMessage-Icon]:shr-text-color-inherit',\n '[&:disabled_.smarthr-ui-FormControl-supplementaryMessage]:shr-text-color-inherit',\n '[&:disabled_.smarthr-ui-Input]:shr-border-default/50 [&:disabled_.smarthr-ui-Input]:shr-bg-white-darken',\n ],\n label: ['smarthr-ui-FormControl-label'],\n errorList: ['shr-list-none'],\n errorIcon: ['smarthr-ui-FormControl-errorMessage-Icon', 'shr-text-danger'],\n errorMessage: ['smarthr-ui-FormControl-errorMessage'],\n underLabelStack: ['[&&&]:shr-mt-0'],\n childrenWrapper: [],\n },\n variants: {\n innerMargin: {\n 0: {},\n 0.25: {},\n 0.5: {},\n 0.75: {},\n 1: {},\n 1.25: {},\n 1.5: {},\n 2: {},\n 2.5: {},\n 3: {},\n 3.5: {},\n 4: {},\n 8: {},\n X3S: {},\n XXS: {},\n XS: {},\n S: {},\n M: {},\n L: {},\n XL: {},\n XXL: {},\n X3L: {},\n } as { [key in Gap]: string },\n isFieldset: {\n true: {},\n false: {},\n },\n },\n compoundVariants: [\n // TODO: innerMarginが未指定、初期値の場合、かつFieldsetの場合、childrenの上部の余白を広げることで\n // FormControltとの差をわかりやすくしている\n // 微妙な方法ではあるので、必要に応じてinnerMarginではない属性を用意する\n // https://kufuinc.slack.com/archives/CGC58MW01/p1737944965871159?thread_ts=1737541173.404369&cid=CGC58MW01\n {\n innerMargin: undefined,\n isFieldset: true,\n class: {\n childrenWrapper: '[:not([hidden])_~_&&&]:shr-mt-1',\n },\n },\n {\n innerMargin: undefined,\n isFieldset: false,\n class: {\n childrenWrapper: '[:not([hidden])_~_&&&]:shr-mt-0.5',\n },\n },\n ],\n})\n\nconst SMARTHR_UI_INPUT_SELECTOR = '[data-smarthr-ui-input=\"true\"]'\n\nexport const ActualFormControl: FC<Props> = ({\n label: orgLabel,\n subActionArea,\n innerMargin,\n statusLabels,\n helpMessage,\n exampleMessage,\n errorMessages,\n autoBindErrorInput = true,\n supplementaryMessage,\n as = 'div',\n className,\n children,\n ...rest\n}) => {\n const label = useObjectAttributes<ReactNode | ObjectLabelType, ObjectLabelType>(\n orgLabel,\n labelObjectConverter,\n )\n const defaultHtmlFor = useId()\n const defaultLabelId = useId()\n const [childInputId, setChildInputId] = useState<string>('')\n const managedHtmlFor = label.htmlFor || childInputId || defaultHtmlFor\n const managedLabelId = label.id || defaultLabelId\n const inputWrapperRef = useRef<HTMLDivElement>(null)\n const isFieldset = as === 'fieldset'\n\n const describedbyIds = useMemo(() => {\n const temp = []\n\n if (helpMessage) {\n temp.push(`${managedHtmlFor}_helpMessage`)\n }\n if (exampleMessage) {\n temp.push(`${managedHtmlFor}_exampleMessage`)\n }\n if (supplementaryMessage) {\n temp.push(`${managedHtmlFor}_supplementaryMessage`)\n }\n if (errorMessages) {\n temp.push(`${managedHtmlFor}_errorMessages`)\n }\n\n return temp.join(' ')\n }, [helpMessage, exampleMessage, supplementaryMessage, errorMessages, managedHtmlFor])\n\n const actualStatusLabels = useMemo(\n () => (statusLabels ? (Array.isArray(statusLabels) ? statusLabels : [statusLabels]) : []),\n [statusLabels],\n )\n\n const actualErrorMessages = useMemo(() => {\n if (!errorMessages) {\n return []\n }\n\n return Array.isArray(errorMessages) ? errorMessages : [errorMessages]\n }, [errorMessages])\n\n const actualInnerMargin = useMemo(() => innerMargin ?? 0.5, [innerMargin])\n\n const classNames = useMemo(() => {\n const generators = classNameGenerator({ innerMargin, isFieldset })\n\n return {\n wrapper: generators.wrapper({ className }),\n label: generators.label({\n className: label.unrecommendedHide ? visuallyHiddenTextClassName : '',\n }),\n errorList: generators.errorList(),\n errorIcon: generators.errorIcon(),\n errorMessage: generators.errorMessage(),\n underLabelStack: generators.underLabelStack(),\n childrenWrapper: generators.childrenWrapper(),\n }\n }, [innerMargin, isFieldset, label.unrecommendedHide, className])\n\n useEffect(() => {\n if (\n isFieldset ||\n !inputWrapperRef?.current ||\n // HINT: 対象idを持つ要素が既に存在する場合、何もしない\n document.getElementById(managedHtmlFor)\n ) {\n return\n }\n\n const input = inputWrapperRef.current.querySelector(SMARTHR_UI_INPUT_SELECTOR)\n\n if (!input) {\n return\n }\n\n const inputId = input.getAttribute('id')\n\n if (inputId) {\n setChildInputId(inputId)\n } else {\n input.setAttribute('id', managedHtmlFor)\n }\n\n if (input instanceof HTMLInputElement && input.type === 'file') {\n const attrName = 'aria-labelledby'\n const inputLabelledByIds = input.getAttribute(attrName)\n\n if (inputLabelledByIds) {\n // InputFileの場合はlabel要素の可視ラベルをアクセシブルネームに含める\n input.setAttribute(attrName, `${inputLabelledByIds} ${managedLabelId}`)\n }\n }\n }, [managedHtmlFor, isFieldset, managedLabelId])\n\n useEffect(() => {\n if (!describedbyIds || !inputWrapperRef?.current) {\n return\n }\n\n const inputWrapper = inputWrapperRef.current\n const attrName = 'aria-describedby'\n\n if (inputWrapper.querySelector(`[${attrName}=\"${describedbyIds}\"]`)) {\n return\n }\n\n const input = inputWrapper.querySelector(SMARTHR_UI_INPUT_SELECTOR)\n\n if (input) {\n const attribute = input.getAttribute(attrName)\n\n input.setAttribute(attrName, attribute ? `${attribute} ${describedbyIds}` : describedbyIds)\n }\n }, [describedbyIds])\n\n useEffect(() => {\n if (!autoBindErrorInput || !inputWrapperRef?.current) {\n return\n }\n\n const input = inputWrapperRef.current.querySelector(SMARTHR_UI_INPUT_SELECTOR)\n\n if (input) {\n const attrName = 'aria-invalid'\n\n if (actualErrorMessages.length > 0) {\n input.setAttribute(attrName, 'true')\n } else {\n input.removeAttribute(attrName)\n }\n }\n }, [actualErrorMessages.length, autoBindErrorInput])\n\n // HINT: Fieldset内の可視ラベルが無いinputに、legend文言をアクセシブルネームに追加する\n // https://waic.jp/translations/WCAG21/Understanding/label-in-name.html\n useEffect(() => {\n if (!isFieldset || !inputWrapperRef.current) return\n\n const inputs =\n inputWrapperRef.current.querySelectorAll<HTMLInputElement>(SMARTHR_UI_INPUT_SELECTOR)\n\n if (!inputs.length) return\n\n const legendText = innerText(label.text)\n\n if (!legendText) return\n\n inputs.forEach((input: HTMLInputElement) => {\n const accessibleName =\n input.getAttribute('aria-label') ||\n (input.labels?.[0]?.classList.contains('smarthr-ui-VisuallyHiddenText')\n ? input.labels![0].textContent\n : '')\n\n if (\n accessibleName &&\n !accessibleName.includes(legendText) &&\n !legendText.includes(accessibleName)\n ) {\n input.setAttribute('aria-label', `${accessibleName} ${legendText}`)\n }\n })\n }, [isFieldset, label.text])\n\n let body = (\n <>\n <HelpMessageParagraph helpMessage={helpMessage} managedHtmlFor={managedHtmlFor} />\n <ExampleMessageText exampleMessage={exampleMessage} managedHtmlFor={managedHtmlFor} />\n <ErrorMessageList\n errorMessages={actualErrorMessages}\n managedHtmlFor={managedHtmlFor}\n classNames={classNames}\n />\n <div className={classNames.childrenWrapper} ref={inputWrapperRef}>\n {children}\n </div>\n <SupplementaryMessageText\n supplementaryMessage={supplementaryMessage}\n managedHtmlFor={managedHtmlFor}\n />\n </>\n )\n\n // HINT: label.unrecommendedHideの場合、body以下の余白の計算を簡略化するため\n // Stackをネストし、そのStackに対してmargin-top: 0を指定する\n // こうすることでinner Stack以下の要素は擬似的にStackの最初の要素になる\n if (label.unrecommendedHide) {\n body = (\n <Stack gap={actualInnerMargin} className={classNames.underLabelStack}>\n {body}\n </Stack>\n )\n }\n\n return (\n <Stack\n {...rest}\n as={as}\n gap={actualInnerMargin}\n aria-describedby={isFieldset && describedbyIds ? describedbyIds : undefined}\n className={classNames.wrapper}\n >\n <LabelCluster\n isFieldset={isFieldset}\n managedHtmlFor={managedHtmlFor}\n managedLabelId={managedLabelId}\n unrecommendedHideLabel={label.unrecommendedHide}\n labelType={label.styleType}\n label={label.text}\n labelIcon={label.icon}\n statusLabels={actualStatusLabels}\n subActionArea={subActionArea}\n labelClassName={classNames.label}\n />\n {body}\n </Stack>\n )\n}\n\nconst LabelCluster = memo<\n Pick<Props, 'subActionArea'> & {\n label: ReactNode\n labelType: TextProps['styleType']\n labelIcon?: IconType\n unrecommendedHideLabel?: boolean\n isFieldset: boolean\n managedHtmlFor: string\n managedLabelId: string\n labelClassName: string\n statusLabels: StatusLabelType[]\n }\n>(\n ({\n isFieldset,\n managedHtmlFor,\n managedLabelId,\n unrecommendedHideLabel,\n labelType = 'blockTitle',\n label,\n labelIcon,\n subActionArea,\n labelClassName,\n statusLabels,\n }) => {\n const body = (\n <>\n <Text styleType={labelType} icon={labelIcon}>\n {label}\n </Text>\n <StatusLabelCluster statusLabels={statusLabels} />\n </>\n )\n\n const attrs = useMemo(() => {\n if (unrecommendedHideLabel) {\n return {\n label: null,\n visuallyHidden: isFieldset\n ? {\n as: 'legend',\n }\n : {\n as: 'label',\n htmlFor: managedHtmlFor,\n id: managedLabelId,\n },\n }\n }\n\n if (isFieldset) {\n return {\n label: { 'aria-hidden': 'true' } as const,\n visuallyHidden: { as: 'legend' },\n }\n }\n\n return {\n label: {\n as: 'label' as const,\n htmlFor: managedHtmlFor,\n id: managedLabelId,\n },\n visuallyHidden: null,\n }\n }, [managedLabelId, managedHtmlFor, unrecommendedHideLabel, isFieldset])\n\n return (\n <>\n {attrs.visuallyHidden && (\n <VisuallyHiddenText {...attrs.visuallyHidden}>\n {\n // HINT: innerTextでは正しく文字が取得できない場合がある\n // 安全策としてinnerTextが空を取得してきたらbody自体を埋めこみます\n innerText(body) || body\n }\n </VisuallyHiddenText>\n )}\n {attrs.label && (\n <Cluster\n justify=\"space-between\"\n // HINT: UI上、常にトップの要素になるため、Stackの計算が狂わないよう、\n // 常にmargin-topを0にする\n className=\"[&&&]:shr--mt-0\"\n >\n <Cluster {...attrs.label} align=\"center\" className={labelClassName}>\n {body}\n </Cluster>\n {subActionArea && <div className=\"shr-grow\">{subActionArea}</div>}\n </Cluster>\n )}\n </>\n )\n },\n)\n\nconst StatusLabelCluster = memo<{ statusLabels: StatusLabelType[] }>(({ statusLabels }) =>\n statusLabels.length === 0 ? null : (\n <Cluster gap={0.25} as=\"span\">\n {statusLabels}\n </Cluster>\n ),\n)\n\nconst HelpMessageParagraph = memo<Pick<Props, 'helpMessage'> & { managedHtmlFor: string }>(\n ({ helpMessage, managedHtmlFor }) =>\n helpMessage ? (\n <p className=\"smarthr-ui-FormControl-helpMessage\" id={`${managedHtmlFor}_helpMessage`}>\n {helpMessage}\n </p>\n ) : null,\n)\n\nconst ExampleMessageText = memo<Pick<Props, 'exampleMessage'> & { managedHtmlFor: string }>(\n ({ exampleMessage, managedHtmlFor }) =>\n exampleMessage ? (\n <Text\n as=\"p\"\n color=\"TEXT_GREY\"\n italic\n id={`${managedHtmlFor}_exampleMessage`}\n className=\"smarthr-ui-FormControl-exampleMessage\"\n >\n {exampleMessage}\n </Text>\n ) : null,\n)\n\nconst ErrorMessageList = memo<{\n errorMessages: ReactNode[]\n managedHtmlFor: string\n classNames: {\n errorList: string\n errorIcon: string\n errorMessage: string\n }\n}>(({ errorMessages, managedHtmlFor, classNames }) =>\n errorMessages.length > 0 ? (\n <div id={`${managedHtmlFor}_errorMessages`} className={classNames.errorList} role=\"alert\">\n {errorMessages.map((message, index) => (\n <p key={index}>\n <Text\n className={classNames.errorMessage}\n icon={<FaCircleExclamationIcon className={classNames.errorIcon} />}\n >\n {message}\n </Text>\n </p>\n ))}\n </div>\n ) : null,\n)\n\nconst SupplementaryMessageText = memo<\n Pick<Props, 'supplementaryMessage'> & { managedHtmlFor: string }\n>(({ supplementaryMessage, managedHtmlFor }) =>\n supplementaryMessage ? (\n <Text\n as=\"p\"\n size=\"S\"\n color=\"TEXT_GREY\"\n id={`${managedHtmlFor}_supplementaryMessage`}\n className=\"smarthr-ui-FormControl-supplementaryMessage\"\n >\n {supplementaryMessage}\n </Text>\n ) : null,\n)\n\nexport const FormControl: FC<Omit<Props, 'as' | 'disabled'>> = ActualFormControl\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAuEA;AAEA;AACE;AACE;;;;;;;;;AASC;;;AAGD;;;AAGA;AACD;AACD;AACE;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAC2B;AAC7B;AACE;AACA;AACD;AACF;AACD;;;;;AAKE;AACE;AACA;AACA;AACE;AACD;AACF;AACD;AACE;AACA;AACA;AACE;AACD;AACF;AACF;AACF;AAED;AAEO;;AAmBL;AACA;;;AAGA;AACA;AACA;AAEA;;;AAII;;;AAGA;;;AAGA;;;AAGA;;AAGF;AACF;AAEA;AAKA;;AAEI;;AAGF;AACF;AAEA;AAEA;;;;AAKI;;;AAGA;AACA;AACA;AACA;AACA;;AAEJ;;AAGE;;;AAIE;;;;;;;;;;;;AAgBA;;;;;;;;;;;;;;;AAmBF;;;;;;;;AAYE;;AAEJ;;;;;;;;AAYI;AACE;;;AAEA;;;;;;;AAQJ;;;;;;AASA;;AAEA;AACE;AAEE;;;AAIF;AAEE;AACA;;;AAIJ;;AAGF;;;;AAsBA;AACE;;AAOF;AAuBF;AAEA;;AAkCI;;;AAGM;AACA;AACE;AACI;AACD;AACH;AACI;AACA;AACA;AACD;;;;;AAML;AACA;;;;AAKF;AACE;AACA;AACA;AACD;AACD;;;AAIJ;;;AAOU;;;AASF;AAUV;AAGF;AAQA;AASA;AAeA;AAyBA;AAgBO;;;"}
|
|
1
|
+
{"version":3,"file":"FormControl.cjs","sources":["../../../src/components/FormControl/FormControl.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type ComponentPropsWithoutRef,\n type ComponentType,\n type FC,\n type FunctionComponentElement,\n type PropsWithChildren,\n type ReactNode,\n memo,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { useId } from 'react'\nimport innerText from 'react-innertext'\nimport { tv } from 'tailwind-variants'\n\nimport { useObjectAttributes } from '../../hooks/useObjectAttributes'\nimport { FaCircleExclamationIcon } from '../Icon'\nimport { Cluster, Stack } from '../Layout'\nimport { Text, type TextProps } from '../Text'\nimport { VisuallyHiddenText, visuallyHiddenTextClassName } from '../VisuallyHiddenText'\n\nimport type { Gap } from '../../types'\nimport type { StatusLabel } from '../StatusLabel'\n\ntype StatusLabelType = FunctionComponentElement<ComponentProps<typeof StatusLabel>>\ntype IconType = ComponentProps<typeof Text>['icon']\n\ntype ObjectLabelType = {\n text: ReactNode\n /** ラベルの表示タイプ */\n styleType?: TextProps['styleType']\n /** ラベル左に設置するアイコン */\n icon?: IconType\n /** ラベルを視覚的に隠すかどうか */\n unrecommendedHide?: boolean\n /** ラベルを紐づける入力要素のID属性と同じ値 */\n htmlFor?: string\n /** ラベルに適用する `id` 値 */\n id?: string\n}\ntype AbstractProps = PropsWithChildren<{\n /** グループのラベル名 */\n label: ReactNode | ObjectLabelType\n /** タイトル右の領域 */\n subActionArea?: ReactNode\n /** タイトル群と子要素の間の間隔調整用(基本的には不要) */\n innerMargin?: Gap\n /** タイトルの隣に表示する `StatusLabel` の配列 */\n statusLabels?: StatusLabelType | StatusLabelType[]\n /** タイトルの下に表示するヘルプメッセージ */\n helpMessage?: ReactNode\n /** タイトルの下に表示する入力例 */\n exampleMessage?: ReactNode\n /** タイトルの下に表示するエラーメッセージ */\n errorMessages?: ReactNode | ReactNode[]\n /** エラーがある場合に自動的に入力要素を error にするかどうか */\n autoBindErrorInput?: boolean\n /** フォームコントロールの下に表示する補足メッセージ */\n supplementaryMessage?: ReactNode\n /** `true` のとき、文字色を `TEXT_DISABLED` にする */\n disabled?: boolean\n as?: string | ComponentType<any>\n}>\ntype Props = AbstractProps &\n Omit<ComponentPropsWithoutRef<'div'>, keyof AbstractProps | 'aria-labelledby'>\n\nconst labelObjectConverter = (label: ReactNode) => ({ text: label })\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: [\n 'smarthr-ui-FormControl',\n 'shr-mx-[unset] shr-border-none shr-p-[unset]',\n 'disabled:shr-text-disabled',\n '[&:disabled_.smarthr-ui-FormControl-label_>_span]:shr-text-disabled',\n '[&:disabled_.smarthr-ui-FormControl-exampleMessage]:shr-text-color-inherit',\n '[&:disabled_.smarthr-ui-FormControl-errorMessage-Icon]:shr-text-color-inherit',\n '[&:disabled_.smarthr-ui-FormControl-supplementaryMessage]:shr-text-color-inherit',\n '[&:disabled_.smarthr-ui-Input]:shr-border-default/50 [&:disabled_.smarthr-ui-Input]:shr-bg-white-darken',\n ],\n label: ['smarthr-ui-FormControl-label'],\n errorList: ['shr-list-none'],\n errorIcon: ['smarthr-ui-FormControl-errorMessage-Icon', 'shr-text-danger'],\n errorMessage: ['smarthr-ui-FormControl-errorMessage'],\n childrenWrapper: [],\n },\n variants: {\n innerMargin: {\n 0: {},\n 0.25: {},\n 0.5: {},\n 0.75: {},\n 1: {},\n 1.25: {},\n 1.5: {},\n 2: {},\n 2.5: {},\n 3: {},\n 3.5: {},\n 4: {},\n 8: {},\n X3S: {},\n XXS: {},\n XS: {},\n S: {},\n M: {},\n L: {},\n XL: {},\n XXL: {},\n X3L: {},\n } as { [key in Gap]: string },\n isFieldset: {\n true: {},\n false: {},\n },\n },\n compoundVariants: [\n // TODO: innerMarginが未指定、初期値の場合、かつFieldsetの場合、childrenの上部の余白を広げることで\n // FormControltとの差をわかりやすくしている\n // 微妙な方法ではあるので、必要に応じてinnerMarginではない属性を用意する\n // https://kufuinc.slack.com/archives/CGC58MW01/p1737944965871159?thread_ts=1737541173.404369&cid=CGC58MW01\n {\n innerMargin: undefined,\n isFieldset: true,\n class: {\n childrenWrapper: '[:not([hidden])_~_&&&]:shr-mt-0.5',\n },\n },\n ],\n})\n\nconst SMARTHR_UI_INPUT_SELECTOR = '[data-smarthr-ui-input=\"true\"]'\n\nexport const ActualFormControl: FC<Props> = ({\n label: orgLabel,\n subActionArea,\n innerMargin,\n statusLabels,\n helpMessage,\n exampleMessage,\n errorMessages,\n autoBindErrorInput = true,\n supplementaryMessage,\n as = 'div',\n className,\n children,\n ...rest\n}) => {\n const label = useObjectAttributes<ReactNode | ObjectLabelType, ObjectLabelType>(\n orgLabel,\n labelObjectConverter,\n )\n const defaultHtmlFor = useId()\n const defaultLabelId = useId()\n const [childInputId, setChildInputId] = useState<string>('')\n const managedHtmlFor = label.htmlFor || childInputId || defaultHtmlFor\n const managedLabelId = label.id || defaultLabelId\n const inputWrapperRef = useRef<HTMLDivElement>(null)\n const isFieldset = as === 'fieldset'\n\n const describedbyIds = useMemo(() => {\n const temp = []\n\n if (helpMessage) {\n temp.push(`${managedHtmlFor}_helpMessage`)\n }\n if (exampleMessage) {\n temp.push(`${managedHtmlFor}_exampleMessage`)\n }\n if (supplementaryMessage) {\n temp.push(`${managedHtmlFor}_supplementaryMessage`)\n }\n if (errorMessages) {\n temp.push(`${managedHtmlFor}_errorMessages`)\n }\n\n return temp.join(' ')\n }, [helpMessage, exampleMessage, supplementaryMessage, errorMessages, managedHtmlFor])\n\n const actualStatusLabels = useMemo(\n () => (statusLabels ? (Array.isArray(statusLabels) ? statusLabels : [statusLabels]) : []),\n [statusLabels],\n )\n\n const actualErrorMessages = useMemo(() => {\n if (!errorMessages) {\n return []\n }\n\n return Array.isArray(errorMessages) ? errorMessages : [errorMessages]\n }, [errorMessages])\n\n const classNames = useMemo(() => {\n const generators = classNameGenerator({ innerMargin, isFieldset })\n\n return {\n wrapper: generators.wrapper({ className }),\n label: generators.label({\n className: label.unrecommendedHide ? visuallyHiddenTextClassName : '',\n }),\n errorList: generators.errorList(),\n errorIcon: generators.errorIcon(),\n errorMessage: generators.errorMessage(),\n childrenWrapper: generators.childrenWrapper(),\n }\n }, [innerMargin, isFieldset, label.unrecommendedHide, className])\n\n useEffect(() => {\n if (\n isFieldset ||\n !inputWrapperRef?.current ||\n // HINT: 対象idを持つ要素が既に存在する場合、何もしない\n document.getElementById(managedHtmlFor)\n ) {\n return\n }\n\n const input = inputWrapperRef.current.querySelector(SMARTHR_UI_INPUT_SELECTOR)\n\n if (!input) {\n return\n }\n\n const inputId = input.getAttribute('id')\n\n if (inputId) {\n setChildInputId(inputId)\n } else {\n input.setAttribute('id', managedHtmlFor)\n }\n\n if (input instanceof HTMLInputElement && input.type === 'file') {\n const attrName = 'aria-labelledby'\n const inputLabelledByIds = input.getAttribute(attrName)\n\n if (inputLabelledByIds) {\n // InputFileの場合はlabel要素の可視ラベルをアクセシブルネームに含める\n input.setAttribute(attrName, `${inputLabelledByIds} ${managedLabelId}`)\n }\n }\n }, [managedHtmlFor, isFieldset, managedLabelId])\n\n useEffect(() => {\n if (!describedbyIds || !inputWrapperRef?.current) {\n return\n }\n\n const inputWrapper = inputWrapperRef.current\n const attrName = 'aria-describedby'\n\n if (inputWrapper.querySelector(`[${attrName}=\"${describedbyIds}\"]`)) {\n return\n }\n\n const input = inputWrapper.querySelector(SMARTHR_UI_INPUT_SELECTOR)\n\n if (input) {\n const attribute = input.getAttribute(attrName)\n\n input.setAttribute(attrName, attribute ? `${attribute} ${describedbyIds}` : describedbyIds)\n }\n }, [describedbyIds])\n\n useEffect(() => {\n if (!autoBindErrorInput || !inputWrapperRef?.current) {\n return\n }\n\n const input = inputWrapperRef.current.querySelector(SMARTHR_UI_INPUT_SELECTOR)\n\n if (input) {\n const attrName = 'aria-invalid'\n\n if (actualErrorMessages.length > 0) {\n input.setAttribute(attrName, 'true')\n } else {\n input.removeAttribute(attrName)\n }\n }\n }, [actualErrorMessages.length, autoBindErrorInput])\n\n // HINT: Fieldset内の可視ラベルが無いinputに、legend文言をアクセシブルネームに追加する\n // https://waic.jp/translations/WCAG21/Understanding/label-in-name.html\n useEffect(() => {\n if (!isFieldset || !inputWrapperRef.current) return\n\n const inputs =\n inputWrapperRef.current.querySelectorAll<HTMLInputElement>(SMARTHR_UI_INPUT_SELECTOR)\n\n if (!inputs.length) return\n\n const legendText = innerText(label.text)\n\n if (!legendText) return\n\n inputs.forEach((input: HTMLInputElement) => {\n const accessibleName =\n input.getAttribute('aria-label') ||\n (input.labels?.[0]?.classList.contains('smarthr-ui-VisuallyHiddenText')\n ? input.labels![0].textContent\n : '')\n\n if (\n accessibleName &&\n !accessibleName.includes(legendText) &&\n !legendText.includes(accessibleName)\n ) {\n input.setAttribute('aria-label', `${accessibleName} ${legendText}`)\n }\n })\n }, [isFieldset, label.text])\n\n return (\n <Stack\n {...rest}\n as={as}\n gap={innerMargin ?? 0.5}\n aria-describedby={isFieldset && describedbyIds ? describedbyIds : undefined}\n className={classNames.wrapper}\n >\n <LabelCluster\n isFieldset={isFieldset}\n managedHtmlFor={managedHtmlFor}\n managedLabelId={managedLabelId}\n unrecommendedHideLabel={label.unrecommendedHide}\n labelType={label.styleType}\n label={label.text}\n labelIcon={label.icon}\n statusLabels={actualStatusLabels}\n subActionArea={subActionArea}\n labelClassName={classNames.label}\n />\n <HelpMessageParagraph helpMessage={helpMessage} managedHtmlFor={managedHtmlFor} />\n <ExampleMessageText exampleMessage={exampleMessage} managedHtmlFor={managedHtmlFor} />\n <ErrorMessageList\n errorMessages={actualErrorMessages}\n managedHtmlFor={managedHtmlFor}\n classNames={classNames}\n />\n <div className={classNames.childrenWrapper} ref={inputWrapperRef}>\n {children}\n </div>\n <SupplementaryMessageText\n supplementaryMessage={supplementaryMessage}\n managedHtmlFor={managedHtmlFor}\n />\n </Stack>\n )\n}\n\nconst LabelCluster = memo<\n Pick<Props, 'subActionArea'> & {\n label: ReactNode\n labelType: TextProps['styleType']\n labelIcon?: IconType\n unrecommendedHideLabel?: boolean\n isFieldset: boolean\n managedHtmlFor: string\n managedLabelId: string\n labelClassName: string\n statusLabels: StatusLabelType[]\n }\n>(\n ({\n isFieldset,\n managedHtmlFor,\n managedLabelId,\n unrecommendedHideLabel,\n labelType = 'blockTitle',\n label,\n labelIcon,\n subActionArea,\n labelClassName,\n statusLabels,\n }) => {\n const body = (\n <>\n <Text styleType={labelType} icon={labelIcon}>\n {label}\n </Text>\n <StatusLabelCluster statusLabels={statusLabels} />\n </>\n )\n\n const attrs = useMemo(() => {\n if (unrecommendedHideLabel) {\n return {\n label: null,\n visuallyHidden: isFieldset\n ? {\n as: 'legend',\n }\n : {\n as: 'label',\n htmlFor: managedHtmlFor,\n id: managedLabelId,\n },\n }\n }\n\n if (isFieldset) {\n return {\n label: { 'aria-hidden': 'true' } as const,\n visuallyHidden: { as: 'legend' },\n }\n }\n\n return {\n label: {\n as: 'label' as const,\n htmlFor: managedHtmlFor,\n id: managedLabelId,\n },\n visuallyHidden: null,\n }\n }, [managedLabelId, managedHtmlFor, unrecommendedHideLabel, isFieldset])\n\n return (\n <>\n {attrs.visuallyHidden && (\n <VisuallyHiddenText {...attrs.visuallyHidden}>\n {\n // HINT: innerTextでは正しく文字が取得できない場合がある\n // 安全策としてinnerTextが空を取得してきたらbody自体を埋めこみます\n innerText(body) || body\n }\n </VisuallyHiddenText>\n )}\n {attrs.label && (\n <Cluster justify=\"space-between\">\n <Cluster {...attrs.label} align=\"center\" className={labelClassName}>\n {body}\n </Cluster>\n {subActionArea && <div className=\"shr-grow\">{subActionArea}</div>}\n </Cluster>\n )}\n </>\n )\n },\n)\n\nconst StatusLabelCluster = memo<{ statusLabels: StatusLabelType[] }>(({ statusLabels }) =>\n statusLabels.length === 0 ? null : (\n <Cluster gap={0.25} as=\"span\">\n {statusLabels}\n </Cluster>\n ),\n)\n\nconst HelpMessageParagraph = memo<Pick<Props, 'helpMessage'> & { managedHtmlFor: string }>(\n ({ helpMessage, managedHtmlFor }) =>\n helpMessage ? (\n <p className=\"smarthr-ui-FormControl-helpMessage\" id={`${managedHtmlFor}_helpMessage`}>\n {helpMessage}\n </p>\n ) : null,\n)\n\nconst ExampleMessageText = memo<Pick<Props, 'exampleMessage'> & { managedHtmlFor: string }>(\n ({ exampleMessage, managedHtmlFor }) =>\n exampleMessage ? (\n <Text\n as=\"p\"\n color=\"TEXT_GREY\"\n italic\n id={`${managedHtmlFor}_exampleMessage`}\n className=\"smarthr-ui-FormControl-exampleMessage\"\n >\n {exampleMessage}\n </Text>\n ) : null,\n)\n\nconst ErrorMessageList = memo<{\n errorMessages: ReactNode[]\n managedHtmlFor: string\n classNames: {\n errorList: string\n errorIcon: string\n errorMessage: string\n }\n}>(({ errorMessages, managedHtmlFor, classNames }) =>\n errorMessages.length > 0 ? (\n <div id={`${managedHtmlFor}_errorMessages`} className={classNames.errorList} role=\"alert\">\n {errorMessages.map((message, index) => (\n <p key={index}>\n <Text\n className={classNames.errorMessage}\n icon={<FaCircleExclamationIcon className={classNames.errorIcon} />}\n >\n {message}\n </Text>\n </p>\n ))}\n </div>\n ) : null,\n)\n\nconst SupplementaryMessageText = memo<\n Pick<Props, 'supplementaryMessage'> & { managedHtmlFor: string }\n>(({ supplementaryMessage, managedHtmlFor }) =>\n supplementaryMessage ? (\n <Text\n as=\"p\"\n size=\"S\"\n color=\"TEXT_GREY\"\n id={`${managedHtmlFor}_supplementaryMessage`}\n className=\"smarthr-ui-FormControl-supplementaryMessage\"\n >\n {supplementaryMessage}\n </Text>\n ) : null,\n)\n\nexport const FormControl: FC<Omit<Props, 'as' | 'disabled'>> = ActualFormControl\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAuEA;AAEA;AACE;AACE;;;;;;;;;AASC;;;AAGD;;AAEA;AACD;AACD;AACE;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAC2B;AAC7B;AACE;AACA;AACD;AACF;AACD;;;;;AAKE;AACE;AACA;AACA;AACE;AACD;AACF;AACF;AACF;AAED;AAEO;;AAmBL;AACA;;;AAGA;AACA;AACA;AAEA;;;AAII;;;AAGA;;;AAGA;;;AAGA;;AAGF;AACF;AAEA;AAKA;;AAEI;;AAGF;AACF;AAEA;;;;AAKI;;;AAGA;AACA;AACA;AACA;;AAEJ;;AAGE;;;AAIE;;;;;;;;;;;;AAgBA;;;;;;;;;;;;;;;AAmBF;;;;;;;;AAYE;;AAEJ;;;;;;;;AAYI;AACE;;;AAEA;;;;;;;AAQJ;;;;;;AASA;;AAEA;AACE;AAEE;;;AAIF;AAEE;AACA;;;AAIJ;;AAGF;AAoCF;AAEA;;AAkCI;;;AAGM;AACA;AACE;AACI;AACD;AACH;AACI;AACA;AACA;AACD;;;;;AAML;AACA;;;;AAKF;AACE;AACA;AACA;AACD;AACD;;;AAIJ;;;;AAqBF;AAGF;AAQA;AASA;AAeA;AAyBA;AAgBO;;;"}
|
|
@@ -33,7 +33,6 @@ const classNameGenerator = ce({
|
|
|
33
33
|
errorList: ['shr-list-none'],
|
|
34
34
|
errorIcon: ['smarthr-ui-FormControl-errorMessage-Icon', 'shr-text-danger'],
|
|
35
35
|
errorMessage: ['smarthr-ui-FormControl-errorMessage'],
|
|
36
|
-
underLabelStack: ['[&&&]:shr-mt-0'],
|
|
37
36
|
childrenWrapper: [],
|
|
38
37
|
},
|
|
39
38
|
variants: {
|
|
@@ -74,13 +73,6 @@ const classNameGenerator = ce({
|
|
|
74
73
|
{
|
|
75
74
|
innerMargin: undefined,
|
|
76
75
|
isFieldset: true,
|
|
77
|
-
class: {
|
|
78
|
-
childrenWrapper: '[:not([hidden])_~_&&&]:shr-mt-1',
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
innerMargin: undefined,
|
|
83
|
-
isFieldset: false,
|
|
84
76
|
class: {
|
|
85
77
|
childrenWrapper: '[:not([hidden])_~_&&&]:shr-mt-0.5',
|
|
86
78
|
},
|
|
@@ -120,7 +112,6 @@ const ActualFormControl = ({ label: orgLabel, subActionArea, innerMargin, status
|
|
|
120
112
|
}
|
|
121
113
|
return Array.isArray(errorMessages) ? errorMessages : [errorMessages];
|
|
122
114
|
}, [errorMessages]);
|
|
123
|
-
const actualInnerMargin = useMemo(() => innerMargin ?? 0.5, [innerMargin]);
|
|
124
115
|
const classNames = useMemo(() => {
|
|
125
116
|
const generators = classNameGenerator({ innerMargin, isFieldset });
|
|
126
117
|
return {
|
|
@@ -131,7 +122,6 @@ const ActualFormControl = ({ label: orgLabel, subActionArea, innerMargin, status
|
|
|
131
122
|
errorList: generators.errorList(),
|
|
132
123
|
errorIcon: generators.errorIcon(),
|
|
133
124
|
errorMessage: generators.errorMessage(),
|
|
134
|
-
underLabelStack: generators.underLabelStack(),
|
|
135
125
|
childrenWrapper: generators.childrenWrapper(),
|
|
136
126
|
};
|
|
137
127
|
}, [innerMargin, isFieldset, label.unrecommendedHide, className]);
|
|
@@ -215,14 +205,7 @@ const ActualFormControl = ({ label: orgLabel, subActionArea, innerMargin, status
|
|
|
215
205
|
}
|
|
216
206
|
});
|
|
217
207
|
}, [isFieldset, label.text]);
|
|
218
|
-
|
|
219
|
-
// HINT: label.unrecommendedHideの場合、body以下の余白の計算を簡略化するため
|
|
220
|
-
// Stackをネストし、そのStackに対してmargin-top: 0を指定する
|
|
221
|
-
// こうすることでinner Stack以下の要素は擬似的にStackの最初の要素になる
|
|
222
|
-
if (label.unrecommendedHide) {
|
|
223
|
-
body = (jsx(Stack, { gap: actualInnerMargin, className: classNames.underLabelStack, children: body }));
|
|
224
|
-
}
|
|
225
|
-
return (jsxs(Stack, { ...rest, as: as, gap: actualInnerMargin, "aria-describedby": isFieldset && describedbyIds ? describedbyIds : undefined, className: classNames.wrapper, children: [jsx(LabelCluster, { isFieldset: isFieldset, managedHtmlFor: managedHtmlFor, managedLabelId: managedLabelId, unrecommendedHideLabel: label.unrecommendedHide, labelType: label.styleType, label: label.text, labelIcon: label.icon, statusLabels: actualStatusLabels, subActionArea: subActionArea, labelClassName: classNames.label }), body] }));
|
|
208
|
+
return (jsxs(Stack, { ...rest, as: as, gap: innerMargin ?? 0.5, "aria-describedby": isFieldset && describedbyIds ? describedbyIds : undefined, className: classNames.wrapper, children: [jsx(LabelCluster, { isFieldset: isFieldset, managedHtmlFor: managedHtmlFor, managedLabelId: managedLabelId, unrecommendedHideLabel: label.unrecommendedHide, labelType: label.styleType, label: label.text, labelIcon: label.icon, statusLabels: actualStatusLabels, subActionArea: subActionArea, labelClassName: classNames.label }), jsx(HelpMessageParagraph, { helpMessage: helpMessage, managedHtmlFor: managedHtmlFor }), jsx(ExampleMessageText, { exampleMessage: exampleMessage, managedHtmlFor: managedHtmlFor }), jsx(ErrorMessageList, { errorMessages: actualErrorMessages, managedHtmlFor: managedHtmlFor, classNames: classNames }), jsx("div", { className: classNames.childrenWrapper, ref: inputWrapperRef, children: children }), jsx(SupplementaryMessageText, { supplementaryMessage: supplementaryMessage, managedHtmlFor: managedHtmlFor })] }));
|
|
226
209
|
};
|
|
227
210
|
const LabelCluster = memo(({ isFieldset, managedHtmlFor, managedLabelId, unrecommendedHideLabel, labelType = 'blockTitle', label, labelIcon, subActionArea, labelClassName, statusLabels, }) => {
|
|
228
211
|
const body = (jsxs(Fragment, { children: [jsx(Text, { styleType: labelType, icon: labelIcon, children: label }), jsx(StatusLabelCluster, { statusLabels: statusLabels })] }));
|
|
@@ -259,10 +242,7 @@ const LabelCluster = memo(({ isFieldset, managedHtmlFor, managedLabelId, unrecom
|
|
|
259
242
|
return (jsxs(Fragment, { children: [attrs.visuallyHidden && (jsx(VisuallyHiddenText, { ...attrs.visuallyHidden, children:
|
|
260
243
|
// HINT: innerTextでは正しく文字が取得できない場合がある
|
|
261
244
|
// 安全策としてinnerTextが空を取得してきたらbody自体を埋めこみます
|
|
262
|
-
innerText(body) || body })), attrs.label && (jsxs(Cluster, { justify: "space-between",
|
|
263
|
-
// HINT: UI上、常にトップの要素になるため、Stackの計算が狂わないよう、
|
|
264
|
-
// 常にmargin-topを0にする
|
|
265
|
-
className: "[&&&]:shr--mt-0", children: [jsx(Cluster, { ...attrs.label, align: "center", className: labelClassName, children: body }), subActionArea && jsx("div", { className: "shr-grow", children: subActionArea })] }))] }));
|
|
245
|
+
innerText(body) || body })), attrs.label && (jsxs(Cluster, { justify: "space-between", children: [jsx(Cluster, { ...attrs.label, align: "center", className: labelClassName, children: body }), subActionArea && jsx("div", { className: "shr-grow", children: subActionArea })] }))] }));
|
|
266
246
|
});
|
|
267
247
|
const StatusLabelCluster = memo(({ statusLabels }) => statusLabels.length === 0 ? null : (jsx(Cluster, { gap: 0.25, as: "span", children: statusLabels })));
|
|
268
248
|
const HelpMessageParagraph = memo(({ helpMessage, managedHtmlFor }) => helpMessage ? (jsx("p", { className: "smarthr-ui-FormControl-helpMessage", id: `${managedHtmlFor}_helpMessage`, children: helpMessage })) : null);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormControl.js","sources":["../../../src/components/FormControl/FormControl.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type ComponentPropsWithoutRef,\n type ComponentType,\n type FC,\n type FunctionComponentElement,\n type PropsWithChildren,\n type ReactNode,\n memo,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { useId } from 'react'\nimport innerText from 'react-innertext'\nimport { tv } from 'tailwind-variants'\n\nimport { useObjectAttributes } from '../../hooks/useObjectAttributes'\nimport { FaCircleExclamationIcon } from '../Icon'\nimport { Cluster, Stack } from '../Layout'\nimport { Text, type TextProps } from '../Text'\nimport { VisuallyHiddenText, visuallyHiddenTextClassName } from '../VisuallyHiddenText'\n\nimport type { Gap } from '../../types'\nimport type { StatusLabel } from '../StatusLabel'\n\ntype StatusLabelType = FunctionComponentElement<ComponentProps<typeof StatusLabel>>\ntype IconType = ComponentProps<typeof Text>['icon']\n\ntype ObjectLabelType = {\n text: ReactNode\n /** ラベルの表示タイプ */\n styleType?: TextProps['styleType']\n /** ラベル左に設置するアイコン */\n icon?: IconType\n /** ラベルを視覚的に隠すかどうか */\n unrecommendedHide?: boolean\n /** ラベルを紐づける入力要素のID属性と同じ値 */\n htmlFor?: string\n /** ラベルに適用する `id` 値 */\n id?: string\n}\ntype AbstractProps = PropsWithChildren<{\n /** グループのラベル名 */\n label: ReactNode | ObjectLabelType\n /** タイトル右の領域 */\n subActionArea?: ReactNode\n /** タイトル群と子要素の間の間隔調整用(基本的には不要) */\n innerMargin?: Gap\n /** タイトルの隣に表示する `StatusLabel` の配列 */\n statusLabels?: StatusLabelType | StatusLabelType[]\n /** タイトルの下に表示するヘルプメッセージ */\n helpMessage?: ReactNode\n /** タイトルの下に表示する入力例 */\n exampleMessage?: ReactNode\n /** タイトルの下に表示するエラーメッセージ */\n errorMessages?: ReactNode | ReactNode[]\n /** エラーがある場合に自動的に入力要素を error にするかどうか */\n autoBindErrorInput?: boolean\n /** フォームコントロールの下に表示する補足メッセージ */\n supplementaryMessage?: ReactNode\n /** `true` のとき、文字色を `TEXT_DISABLED` にする */\n disabled?: boolean\n as?: string | ComponentType<any>\n}>\ntype Props = AbstractProps &\n Omit<ComponentPropsWithoutRef<'div'>, keyof AbstractProps | 'aria-labelledby'>\n\nconst labelObjectConverter = (label: ReactNode) => ({ text: label })\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: [\n 'smarthr-ui-FormControl',\n 'shr-mx-[unset] shr-border-none shr-p-[unset]',\n 'disabled:shr-text-disabled',\n '[&:disabled_.smarthr-ui-FormControl-label_>_span]:shr-text-disabled',\n '[&:disabled_.smarthr-ui-FormControl-exampleMessage]:shr-text-color-inherit',\n '[&:disabled_.smarthr-ui-FormControl-errorMessage-Icon]:shr-text-color-inherit',\n '[&:disabled_.smarthr-ui-FormControl-supplementaryMessage]:shr-text-color-inherit',\n '[&:disabled_.smarthr-ui-Input]:shr-border-default/50 [&:disabled_.smarthr-ui-Input]:shr-bg-white-darken',\n ],\n label: ['smarthr-ui-FormControl-label'],\n errorList: ['shr-list-none'],\n errorIcon: ['smarthr-ui-FormControl-errorMessage-Icon', 'shr-text-danger'],\n errorMessage: ['smarthr-ui-FormControl-errorMessage'],\n underLabelStack: ['[&&&]:shr-mt-0'],\n childrenWrapper: [],\n },\n variants: {\n innerMargin: {\n 0: {},\n 0.25: {},\n 0.5: {},\n 0.75: {},\n 1: {},\n 1.25: {},\n 1.5: {},\n 2: {},\n 2.5: {},\n 3: {},\n 3.5: {},\n 4: {},\n 8: {},\n X3S: {},\n XXS: {},\n XS: {},\n S: {},\n M: {},\n L: {},\n XL: {},\n XXL: {},\n X3L: {},\n } as { [key in Gap]: string },\n isFieldset: {\n true: {},\n false: {},\n },\n },\n compoundVariants: [\n // TODO: innerMarginが未指定、初期値の場合、かつFieldsetの場合、childrenの上部の余白を広げることで\n // FormControltとの差をわかりやすくしている\n // 微妙な方法ではあるので、必要に応じてinnerMarginではない属性を用意する\n // https://kufuinc.slack.com/archives/CGC58MW01/p1737944965871159?thread_ts=1737541173.404369&cid=CGC58MW01\n {\n innerMargin: undefined,\n isFieldset: true,\n class: {\n childrenWrapper: '[:not([hidden])_~_&&&]:shr-mt-1',\n },\n },\n {\n innerMargin: undefined,\n isFieldset: false,\n class: {\n childrenWrapper: '[:not([hidden])_~_&&&]:shr-mt-0.5',\n },\n },\n ],\n})\n\nconst SMARTHR_UI_INPUT_SELECTOR = '[data-smarthr-ui-input=\"true\"]'\n\nexport const ActualFormControl: FC<Props> = ({\n label: orgLabel,\n subActionArea,\n innerMargin,\n statusLabels,\n helpMessage,\n exampleMessage,\n errorMessages,\n autoBindErrorInput = true,\n supplementaryMessage,\n as = 'div',\n className,\n children,\n ...rest\n}) => {\n const label = useObjectAttributes<ReactNode | ObjectLabelType, ObjectLabelType>(\n orgLabel,\n labelObjectConverter,\n )\n const defaultHtmlFor = useId()\n const defaultLabelId = useId()\n const [childInputId, setChildInputId] = useState<string>('')\n const managedHtmlFor = label.htmlFor || childInputId || defaultHtmlFor\n const managedLabelId = label.id || defaultLabelId\n const inputWrapperRef = useRef<HTMLDivElement>(null)\n const isFieldset = as === 'fieldset'\n\n const describedbyIds = useMemo(() => {\n const temp = []\n\n if (helpMessage) {\n temp.push(`${managedHtmlFor}_helpMessage`)\n }\n if (exampleMessage) {\n temp.push(`${managedHtmlFor}_exampleMessage`)\n }\n if (supplementaryMessage) {\n temp.push(`${managedHtmlFor}_supplementaryMessage`)\n }\n if (errorMessages) {\n temp.push(`${managedHtmlFor}_errorMessages`)\n }\n\n return temp.join(' ')\n }, [helpMessage, exampleMessage, supplementaryMessage, errorMessages, managedHtmlFor])\n\n const actualStatusLabels = useMemo(\n () => (statusLabels ? (Array.isArray(statusLabels) ? statusLabels : [statusLabels]) : []),\n [statusLabels],\n )\n\n const actualErrorMessages = useMemo(() => {\n if (!errorMessages) {\n return []\n }\n\n return Array.isArray(errorMessages) ? errorMessages : [errorMessages]\n }, [errorMessages])\n\n const actualInnerMargin = useMemo(() => innerMargin ?? 0.5, [innerMargin])\n\n const classNames = useMemo(() => {\n const generators = classNameGenerator({ innerMargin, isFieldset })\n\n return {\n wrapper: generators.wrapper({ className }),\n label: generators.label({\n className: label.unrecommendedHide ? visuallyHiddenTextClassName : '',\n }),\n errorList: generators.errorList(),\n errorIcon: generators.errorIcon(),\n errorMessage: generators.errorMessage(),\n underLabelStack: generators.underLabelStack(),\n childrenWrapper: generators.childrenWrapper(),\n }\n }, [innerMargin, isFieldset, label.unrecommendedHide, className])\n\n useEffect(() => {\n if (\n isFieldset ||\n !inputWrapperRef?.current ||\n // HINT: 対象idを持つ要素が既に存在する場合、何もしない\n document.getElementById(managedHtmlFor)\n ) {\n return\n }\n\n const input = inputWrapperRef.current.querySelector(SMARTHR_UI_INPUT_SELECTOR)\n\n if (!input) {\n return\n }\n\n const inputId = input.getAttribute('id')\n\n if (inputId) {\n setChildInputId(inputId)\n } else {\n input.setAttribute('id', managedHtmlFor)\n }\n\n if (input instanceof HTMLInputElement && input.type === 'file') {\n const attrName = 'aria-labelledby'\n const inputLabelledByIds = input.getAttribute(attrName)\n\n if (inputLabelledByIds) {\n // InputFileの場合はlabel要素の可視ラベルをアクセシブルネームに含める\n input.setAttribute(attrName, `${inputLabelledByIds} ${managedLabelId}`)\n }\n }\n }, [managedHtmlFor, isFieldset, managedLabelId])\n\n useEffect(() => {\n if (!describedbyIds || !inputWrapperRef?.current) {\n return\n }\n\n const inputWrapper = inputWrapperRef.current\n const attrName = 'aria-describedby'\n\n if (inputWrapper.querySelector(`[${attrName}=\"${describedbyIds}\"]`)) {\n return\n }\n\n const input = inputWrapper.querySelector(SMARTHR_UI_INPUT_SELECTOR)\n\n if (input) {\n const attribute = input.getAttribute(attrName)\n\n input.setAttribute(attrName, attribute ? `${attribute} ${describedbyIds}` : describedbyIds)\n }\n }, [describedbyIds])\n\n useEffect(() => {\n if (!autoBindErrorInput || !inputWrapperRef?.current) {\n return\n }\n\n const input = inputWrapperRef.current.querySelector(SMARTHR_UI_INPUT_SELECTOR)\n\n if (input) {\n const attrName = 'aria-invalid'\n\n if (actualErrorMessages.length > 0) {\n input.setAttribute(attrName, 'true')\n } else {\n input.removeAttribute(attrName)\n }\n }\n }, [actualErrorMessages.length, autoBindErrorInput])\n\n // HINT: Fieldset内の可視ラベルが無いinputに、legend文言をアクセシブルネームに追加する\n // https://waic.jp/translations/WCAG21/Understanding/label-in-name.html\n useEffect(() => {\n if (!isFieldset || !inputWrapperRef.current) return\n\n const inputs =\n inputWrapperRef.current.querySelectorAll<HTMLInputElement>(SMARTHR_UI_INPUT_SELECTOR)\n\n if (!inputs.length) return\n\n const legendText = innerText(label.text)\n\n if (!legendText) return\n\n inputs.forEach((input: HTMLInputElement) => {\n const accessibleName =\n input.getAttribute('aria-label') ||\n (input.labels?.[0]?.classList.contains('smarthr-ui-VisuallyHiddenText')\n ? input.labels![0].textContent\n : '')\n\n if (\n accessibleName &&\n !accessibleName.includes(legendText) &&\n !legendText.includes(accessibleName)\n ) {\n input.setAttribute('aria-label', `${accessibleName} ${legendText}`)\n }\n })\n }, [isFieldset, label.text])\n\n let body = (\n <>\n <HelpMessageParagraph helpMessage={helpMessage} managedHtmlFor={managedHtmlFor} />\n <ExampleMessageText exampleMessage={exampleMessage} managedHtmlFor={managedHtmlFor} />\n <ErrorMessageList\n errorMessages={actualErrorMessages}\n managedHtmlFor={managedHtmlFor}\n classNames={classNames}\n />\n <div className={classNames.childrenWrapper} ref={inputWrapperRef}>\n {children}\n </div>\n <SupplementaryMessageText\n supplementaryMessage={supplementaryMessage}\n managedHtmlFor={managedHtmlFor}\n />\n </>\n )\n\n // HINT: label.unrecommendedHideの場合、body以下の余白の計算を簡略化するため\n // Stackをネストし、そのStackに対してmargin-top: 0を指定する\n // こうすることでinner Stack以下の要素は擬似的にStackの最初の要素になる\n if (label.unrecommendedHide) {\n body = (\n <Stack gap={actualInnerMargin} className={classNames.underLabelStack}>\n {body}\n </Stack>\n )\n }\n\n return (\n <Stack\n {...rest}\n as={as}\n gap={actualInnerMargin}\n aria-describedby={isFieldset && describedbyIds ? describedbyIds : undefined}\n className={classNames.wrapper}\n >\n <LabelCluster\n isFieldset={isFieldset}\n managedHtmlFor={managedHtmlFor}\n managedLabelId={managedLabelId}\n unrecommendedHideLabel={label.unrecommendedHide}\n labelType={label.styleType}\n label={label.text}\n labelIcon={label.icon}\n statusLabels={actualStatusLabels}\n subActionArea={subActionArea}\n labelClassName={classNames.label}\n />\n {body}\n </Stack>\n )\n}\n\nconst LabelCluster = memo<\n Pick<Props, 'subActionArea'> & {\n label: ReactNode\n labelType: TextProps['styleType']\n labelIcon?: IconType\n unrecommendedHideLabel?: boolean\n isFieldset: boolean\n managedHtmlFor: string\n managedLabelId: string\n labelClassName: string\n statusLabels: StatusLabelType[]\n }\n>(\n ({\n isFieldset,\n managedHtmlFor,\n managedLabelId,\n unrecommendedHideLabel,\n labelType = 'blockTitle',\n label,\n labelIcon,\n subActionArea,\n labelClassName,\n statusLabels,\n }) => {\n const body = (\n <>\n <Text styleType={labelType} icon={labelIcon}>\n {label}\n </Text>\n <StatusLabelCluster statusLabels={statusLabels} />\n </>\n )\n\n const attrs = useMemo(() => {\n if (unrecommendedHideLabel) {\n return {\n label: null,\n visuallyHidden: isFieldset\n ? {\n as: 'legend',\n }\n : {\n as: 'label',\n htmlFor: managedHtmlFor,\n id: managedLabelId,\n },\n }\n }\n\n if (isFieldset) {\n return {\n label: { 'aria-hidden': 'true' } as const,\n visuallyHidden: { as: 'legend' },\n }\n }\n\n return {\n label: {\n as: 'label' as const,\n htmlFor: managedHtmlFor,\n id: managedLabelId,\n },\n visuallyHidden: null,\n }\n }, [managedLabelId, managedHtmlFor, unrecommendedHideLabel, isFieldset])\n\n return (\n <>\n {attrs.visuallyHidden && (\n <VisuallyHiddenText {...attrs.visuallyHidden}>\n {\n // HINT: innerTextでは正しく文字が取得できない場合がある\n // 安全策としてinnerTextが空を取得してきたらbody自体を埋めこみます\n innerText(body) || body\n }\n </VisuallyHiddenText>\n )}\n {attrs.label && (\n <Cluster\n justify=\"space-between\"\n // HINT: UI上、常にトップの要素になるため、Stackの計算が狂わないよう、\n // 常にmargin-topを0にする\n className=\"[&&&]:shr--mt-0\"\n >\n <Cluster {...attrs.label} align=\"center\" className={labelClassName}>\n {body}\n </Cluster>\n {subActionArea && <div className=\"shr-grow\">{subActionArea}</div>}\n </Cluster>\n )}\n </>\n )\n },\n)\n\nconst StatusLabelCluster = memo<{ statusLabels: StatusLabelType[] }>(({ statusLabels }) =>\n statusLabels.length === 0 ? null : (\n <Cluster gap={0.25} as=\"span\">\n {statusLabels}\n </Cluster>\n ),\n)\n\nconst HelpMessageParagraph = memo<Pick<Props, 'helpMessage'> & { managedHtmlFor: string }>(\n ({ helpMessage, managedHtmlFor }) =>\n helpMessage ? (\n <p className=\"smarthr-ui-FormControl-helpMessage\" id={`${managedHtmlFor}_helpMessage`}>\n {helpMessage}\n </p>\n ) : null,\n)\n\nconst ExampleMessageText = memo<Pick<Props, 'exampleMessage'> & { managedHtmlFor: string }>(\n ({ exampleMessage, managedHtmlFor }) =>\n exampleMessage ? (\n <Text\n as=\"p\"\n color=\"TEXT_GREY\"\n italic\n id={`${managedHtmlFor}_exampleMessage`}\n className=\"smarthr-ui-FormControl-exampleMessage\"\n >\n {exampleMessage}\n </Text>\n ) : null,\n)\n\nconst ErrorMessageList = memo<{\n errorMessages: ReactNode[]\n managedHtmlFor: string\n classNames: {\n errorList: string\n errorIcon: string\n errorMessage: string\n }\n}>(({ errorMessages, managedHtmlFor, classNames }) =>\n errorMessages.length > 0 ? (\n <div id={`${managedHtmlFor}_errorMessages`} className={classNames.errorList} role=\"alert\">\n {errorMessages.map((message, index) => (\n <p key={index}>\n <Text\n className={classNames.errorMessage}\n icon={<FaCircleExclamationIcon className={classNames.errorIcon} />}\n >\n {message}\n </Text>\n </p>\n ))}\n </div>\n ) : null,\n)\n\nconst SupplementaryMessageText = memo<\n Pick<Props, 'supplementaryMessage'> & { managedHtmlFor: string }\n>(({ supplementaryMessage, managedHtmlFor }) =>\n supplementaryMessage ? (\n <Text\n as=\"p\"\n size=\"S\"\n color=\"TEXT_GREY\"\n id={`${managedHtmlFor}_supplementaryMessage`}\n className=\"smarthr-ui-FormControl-supplementaryMessage\"\n >\n {supplementaryMessage}\n </Text>\n ) : null,\n)\n\nexport const FormControl: FC<Omit<Props, 'as' | 'disabled'>> = ActualFormControl\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAuEA;AAEA;AACE;AACE;;;;;;;;;AASC;;;AAGD;;;AAGA;AACD;AACD;AACE;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAC2B;AAC7B;AACE;AACA;AACD;AACF;AACD;;;;;AAKE;AACE;AACA;AACA;AACE;AACD;AACF;AACD;AACE;AACA;AACA;AACE;AACD;AACF;AACF;AACF;AAED;AAEO;;AAmBL;AACA;;;AAGA;AACA;AACA;AAEA;;;AAII;;;AAGA;;;AAGA;;;AAGA;;AAGF;AACF;AAEA;AAKA;;AAEI;;AAGF;AACF;AAEA;AAEA;;;;AAKI;;;AAGA;AACA;AACA;AACA;AACA;;AAEJ;;AAGE;;;AAIE;;;;;;;;;;;;AAgBA;;;;;;;;;;;;;;;AAmBF;;;;;;;;AAYE;;AAEJ;;;;;;;;AAYI;AACE;;;AAEA;;;;;;;AAQJ;;;;;;AASA;;AAEA;AACE;AAEE;;;AAIF;AAEE;AACA;;;AAIJ;;AAGF;;;;AAsBA;AACE;;AAOF;AAuBF;AAEA;;AAkCI;;;AAGM;AACA;AACE;AACI;AACD;AACH;AACI;AACA;AACA;AACD;;;;;AAML;AACA;;;;AAKF;AACE;AACA;AACA;AACD;AACD;;;AAIJ;;;AAOU;;;AASF;AAUV;AAGF;AAQA;AASA;AAeA;AAyBA;AAgBO;;"}
|
|
1
|
+
{"version":3,"file":"FormControl.js","sources":["../../../src/components/FormControl/FormControl.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type ComponentPropsWithoutRef,\n type ComponentType,\n type FC,\n type FunctionComponentElement,\n type PropsWithChildren,\n type ReactNode,\n memo,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { useId } from 'react'\nimport innerText from 'react-innertext'\nimport { tv } from 'tailwind-variants'\n\nimport { useObjectAttributes } from '../../hooks/useObjectAttributes'\nimport { FaCircleExclamationIcon } from '../Icon'\nimport { Cluster, Stack } from '../Layout'\nimport { Text, type TextProps } from '../Text'\nimport { VisuallyHiddenText, visuallyHiddenTextClassName } from '../VisuallyHiddenText'\n\nimport type { Gap } from '../../types'\nimport type { StatusLabel } from '../StatusLabel'\n\ntype StatusLabelType = FunctionComponentElement<ComponentProps<typeof StatusLabel>>\ntype IconType = ComponentProps<typeof Text>['icon']\n\ntype ObjectLabelType = {\n text: ReactNode\n /** ラベルの表示タイプ */\n styleType?: TextProps['styleType']\n /** ラベル左に設置するアイコン */\n icon?: IconType\n /** ラベルを視覚的に隠すかどうか */\n unrecommendedHide?: boolean\n /** ラベルを紐づける入力要素のID属性と同じ値 */\n htmlFor?: string\n /** ラベルに適用する `id` 値 */\n id?: string\n}\ntype AbstractProps = PropsWithChildren<{\n /** グループのラベル名 */\n label: ReactNode | ObjectLabelType\n /** タイトル右の領域 */\n subActionArea?: ReactNode\n /** タイトル群と子要素の間の間隔調整用(基本的には不要) */\n innerMargin?: Gap\n /** タイトルの隣に表示する `StatusLabel` の配列 */\n statusLabels?: StatusLabelType | StatusLabelType[]\n /** タイトルの下に表示するヘルプメッセージ */\n helpMessage?: ReactNode\n /** タイトルの下に表示する入力例 */\n exampleMessage?: ReactNode\n /** タイトルの下に表示するエラーメッセージ */\n errorMessages?: ReactNode | ReactNode[]\n /** エラーがある場合に自動的に入力要素を error にするかどうか */\n autoBindErrorInput?: boolean\n /** フォームコントロールの下に表示する補足メッセージ */\n supplementaryMessage?: ReactNode\n /** `true` のとき、文字色を `TEXT_DISABLED` にする */\n disabled?: boolean\n as?: string | ComponentType<any>\n}>\ntype Props = AbstractProps &\n Omit<ComponentPropsWithoutRef<'div'>, keyof AbstractProps | 'aria-labelledby'>\n\nconst labelObjectConverter = (label: ReactNode) => ({ text: label })\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: [\n 'smarthr-ui-FormControl',\n 'shr-mx-[unset] shr-border-none shr-p-[unset]',\n 'disabled:shr-text-disabled',\n '[&:disabled_.smarthr-ui-FormControl-label_>_span]:shr-text-disabled',\n '[&:disabled_.smarthr-ui-FormControl-exampleMessage]:shr-text-color-inherit',\n '[&:disabled_.smarthr-ui-FormControl-errorMessage-Icon]:shr-text-color-inherit',\n '[&:disabled_.smarthr-ui-FormControl-supplementaryMessage]:shr-text-color-inherit',\n '[&:disabled_.smarthr-ui-Input]:shr-border-default/50 [&:disabled_.smarthr-ui-Input]:shr-bg-white-darken',\n ],\n label: ['smarthr-ui-FormControl-label'],\n errorList: ['shr-list-none'],\n errorIcon: ['smarthr-ui-FormControl-errorMessage-Icon', 'shr-text-danger'],\n errorMessage: ['smarthr-ui-FormControl-errorMessage'],\n childrenWrapper: [],\n },\n variants: {\n innerMargin: {\n 0: {},\n 0.25: {},\n 0.5: {},\n 0.75: {},\n 1: {},\n 1.25: {},\n 1.5: {},\n 2: {},\n 2.5: {},\n 3: {},\n 3.5: {},\n 4: {},\n 8: {},\n X3S: {},\n XXS: {},\n XS: {},\n S: {},\n M: {},\n L: {},\n XL: {},\n XXL: {},\n X3L: {},\n } as { [key in Gap]: string },\n isFieldset: {\n true: {},\n false: {},\n },\n },\n compoundVariants: [\n // TODO: innerMarginが未指定、初期値の場合、かつFieldsetの場合、childrenの上部の余白を広げることで\n // FormControltとの差をわかりやすくしている\n // 微妙な方法ではあるので、必要に応じてinnerMarginではない属性を用意する\n // https://kufuinc.slack.com/archives/CGC58MW01/p1737944965871159?thread_ts=1737541173.404369&cid=CGC58MW01\n {\n innerMargin: undefined,\n isFieldset: true,\n class: {\n childrenWrapper: '[:not([hidden])_~_&&&]:shr-mt-0.5',\n },\n },\n ],\n})\n\nconst SMARTHR_UI_INPUT_SELECTOR = '[data-smarthr-ui-input=\"true\"]'\n\nexport const ActualFormControl: FC<Props> = ({\n label: orgLabel,\n subActionArea,\n innerMargin,\n statusLabels,\n helpMessage,\n exampleMessage,\n errorMessages,\n autoBindErrorInput = true,\n supplementaryMessage,\n as = 'div',\n className,\n children,\n ...rest\n}) => {\n const label = useObjectAttributes<ReactNode | ObjectLabelType, ObjectLabelType>(\n orgLabel,\n labelObjectConverter,\n )\n const defaultHtmlFor = useId()\n const defaultLabelId = useId()\n const [childInputId, setChildInputId] = useState<string>('')\n const managedHtmlFor = label.htmlFor || childInputId || defaultHtmlFor\n const managedLabelId = label.id || defaultLabelId\n const inputWrapperRef = useRef<HTMLDivElement>(null)\n const isFieldset = as === 'fieldset'\n\n const describedbyIds = useMemo(() => {\n const temp = []\n\n if (helpMessage) {\n temp.push(`${managedHtmlFor}_helpMessage`)\n }\n if (exampleMessage) {\n temp.push(`${managedHtmlFor}_exampleMessage`)\n }\n if (supplementaryMessage) {\n temp.push(`${managedHtmlFor}_supplementaryMessage`)\n }\n if (errorMessages) {\n temp.push(`${managedHtmlFor}_errorMessages`)\n }\n\n return temp.join(' ')\n }, [helpMessage, exampleMessage, supplementaryMessage, errorMessages, managedHtmlFor])\n\n const actualStatusLabels = useMemo(\n () => (statusLabels ? (Array.isArray(statusLabels) ? statusLabels : [statusLabels]) : []),\n [statusLabels],\n )\n\n const actualErrorMessages = useMemo(() => {\n if (!errorMessages) {\n return []\n }\n\n return Array.isArray(errorMessages) ? errorMessages : [errorMessages]\n }, [errorMessages])\n\n const classNames = useMemo(() => {\n const generators = classNameGenerator({ innerMargin, isFieldset })\n\n return {\n wrapper: generators.wrapper({ className }),\n label: generators.label({\n className: label.unrecommendedHide ? visuallyHiddenTextClassName : '',\n }),\n errorList: generators.errorList(),\n errorIcon: generators.errorIcon(),\n errorMessage: generators.errorMessage(),\n childrenWrapper: generators.childrenWrapper(),\n }\n }, [innerMargin, isFieldset, label.unrecommendedHide, className])\n\n useEffect(() => {\n if (\n isFieldset ||\n !inputWrapperRef?.current ||\n // HINT: 対象idを持つ要素が既に存在する場合、何もしない\n document.getElementById(managedHtmlFor)\n ) {\n return\n }\n\n const input = inputWrapperRef.current.querySelector(SMARTHR_UI_INPUT_SELECTOR)\n\n if (!input) {\n return\n }\n\n const inputId = input.getAttribute('id')\n\n if (inputId) {\n setChildInputId(inputId)\n } else {\n input.setAttribute('id', managedHtmlFor)\n }\n\n if (input instanceof HTMLInputElement && input.type === 'file') {\n const attrName = 'aria-labelledby'\n const inputLabelledByIds = input.getAttribute(attrName)\n\n if (inputLabelledByIds) {\n // InputFileの場合はlabel要素の可視ラベルをアクセシブルネームに含める\n input.setAttribute(attrName, `${inputLabelledByIds} ${managedLabelId}`)\n }\n }\n }, [managedHtmlFor, isFieldset, managedLabelId])\n\n useEffect(() => {\n if (!describedbyIds || !inputWrapperRef?.current) {\n return\n }\n\n const inputWrapper = inputWrapperRef.current\n const attrName = 'aria-describedby'\n\n if (inputWrapper.querySelector(`[${attrName}=\"${describedbyIds}\"]`)) {\n return\n }\n\n const input = inputWrapper.querySelector(SMARTHR_UI_INPUT_SELECTOR)\n\n if (input) {\n const attribute = input.getAttribute(attrName)\n\n input.setAttribute(attrName, attribute ? `${attribute} ${describedbyIds}` : describedbyIds)\n }\n }, [describedbyIds])\n\n useEffect(() => {\n if (!autoBindErrorInput || !inputWrapperRef?.current) {\n return\n }\n\n const input = inputWrapperRef.current.querySelector(SMARTHR_UI_INPUT_SELECTOR)\n\n if (input) {\n const attrName = 'aria-invalid'\n\n if (actualErrorMessages.length > 0) {\n input.setAttribute(attrName, 'true')\n } else {\n input.removeAttribute(attrName)\n }\n }\n }, [actualErrorMessages.length, autoBindErrorInput])\n\n // HINT: Fieldset内の可視ラベルが無いinputに、legend文言をアクセシブルネームに追加する\n // https://waic.jp/translations/WCAG21/Understanding/label-in-name.html\n useEffect(() => {\n if (!isFieldset || !inputWrapperRef.current) return\n\n const inputs =\n inputWrapperRef.current.querySelectorAll<HTMLInputElement>(SMARTHR_UI_INPUT_SELECTOR)\n\n if (!inputs.length) return\n\n const legendText = innerText(label.text)\n\n if (!legendText) return\n\n inputs.forEach((input: HTMLInputElement) => {\n const accessibleName =\n input.getAttribute('aria-label') ||\n (input.labels?.[0]?.classList.contains('smarthr-ui-VisuallyHiddenText')\n ? input.labels![0].textContent\n : '')\n\n if (\n accessibleName &&\n !accessibleName.includes(legendText) &&\n !legendText.includes(accessibleName)\n ) {\n input.setAttribute('aria-label', `${accessibleName} ${legendText}`)\n }\n })\n }, [isFieldset, label.text])\n\n return (\n <Stack\n {...rest}\n as={as}\n gap={innerMargin ?? 0.5}\n aria-describedby={isFieldset && describedbyIds ? describedbyIds : undefined}\n className={classNames.wrapper}\n >\n <LabelCluster\n isFieldset={isFieldset}\n managedHtmlFor={managedHtmlFor}\n managedLabelId={managedLabelId}\n unrecommendedHideLabel={label.unrecommendedHide}\n labelType={label.styleType}\n label={label.text}\n labelIcon={label.icon}\n statusLabels={actualStatusLabels}\n subActionArea={subActionArea}\n labelClassName={classNames.label}\n />\n <HelpMessageParagraph helpMessage={helpMessage} managedHtmlFor={managedHtmlFor} />\n <ExampleMessageText exampleMessage={exampleMessage} managedHtmlFor={managedHtmlFor} />\n <ErrorMessageList\n errorMessages={actualErrorMessages}\n managedHtmlFor={managedHtmlFor}\n classNames={classNames}\n />\n <div className={classNames.childrenWrapper} ref={inputWrapperRef}>\n {children}\n </div>\n <SupplementaryMessageText\n supplementaryMessage={supplementaryMessage}\n managedHtmlFor={managedHtmlFor}\n />\n </Stack>\n )\n}\n\nconst LabelCluster = memo<\n Pick<Props, 'subActionArea'> & {\n label: ReactNode\n labelType: TextProps['styleType']\n labelIcon?: IconType\n unrecommendedHideLabel?: boolean\n isFieldset: boolean\n managedHtmlFor: string\n managedLabelId: string\n labelClassName: string\n statusLabels: StatusLabelType[]\n }\n>(\n ({\n isFieldset,\n managedHtmlFor,\n managedLabelId,\n unrecommendedHideLabel,\n labelType = 'blockTitle',\n label,\n labelIcon,\n subActionArea,\n labelClassName,\n statusLabels,\n }) => {\n const body = (\n <>\n <Text styleType={labelType} icon={labelIcon}>\n {label}\n </Text>\n <StatusLabelCluster statusLabels={statusLabels} />\n </>\n )\n\n const attrs = useMemo(() => {\n if (unrecommendedHideLabel) {\n return {\n label: null,\n visuallyHidden: isFieldset\n ? {\n as: 'legend',\n }\n : {\n as: 'label',\n htmlFor: managedHtmlFor,\n id: managedLabelId,\n },\n }\n }\n\n if (isFieldset) {\n return {\n label: { 'aria-hidden': 'true' } as const,\n visuallyHidden: { as: 'legend' },\n }\n }\n\n return {\n label: {\n as: 'label' as const,\n htmlFor: managedHtmlFor,\n id: managedLabelId,\n },\n visuallyHidden: null,\n }\n }, [managedLabelId, managedHtmlFor, unrecommendedHideLabel, isFieldset])\n\n return (\n <>\n {attrs.visuallyHidden && (\n <VisuallyHiddenText {...attrs.visuallyHidden}>\n {\n // HINT: innerTextでは正しく文字が取得できない場合がある\n // 安全策としてinnerTextが空を取得してきたらbody自体を埋めこみます\n innerText(body) || body\n }\n </VisuallyHiddenText>\n )}\n {attrs.label && (\n <Cluster justify=\"space-between\">\n <Cluster {...attrs.label} align=\"center\" className={labelClassName}>\n {body}\n </Cluster>\n {subActionArea && <div className=\"shr-grow\">{subActionArea}</div>}\n </Cluster>\n )}\n </>\n )\n },\n)\n\nconst StatusLabelCluster = memo<{ statusLabels: StatusLabelType[] }>(({ statusLabels }) =>\n statusLabels.length === 0 ? null : (\n <Cluster gap={0.25} as=\"span\">\n {statusLabels}\n </Cluster>\n ),\n)\n\nconst HelpMessageParagraph = memo<Pick<Props, 'helpMessage'> & { managedHtmlFor: string }>(\n ({ helpMessage, managedHtmlFor }) =>\n helpMessage ? (\n <p className=\"smarthr-ui-FormControl-helpMessage\" id={`${managedHtmlFor}_helpMessage`}>\n {helpMessage}\n </p>\n ) : null,\n)\n\nconst ExampleMessageText = memo<Pick<Props, 'exampleMessage'> & { managedHtmlFor: string }>(\n ({ exampleMessage, managedHtmlFor }) =>\n exampleMessage ? (\n <Text\n as=\"p\"\n color=\"TEXT_GREY\"\n italic\n id={`${managedHtmlFor}_exampleMessage`}\n className=\"smarthr-ui-FormControl-exampleMessage\"\n >\n {exampleMessage}\n </Text>\n ) : null,\n)\n\nconst ErrorMessageList = memo<{\n errorMessages: ReactNode[]\n managedHtmlFor: string\n classNames: {\n errorList: string\n errorIcon: string\n errorMessage: string\n }\n}>(({ errorMessages, managedHtmlFor, classNames }) =>\n errorMessages.length > 0 ? (\n <div id={`${managedHtmlFor}_errorMessages`} className={classNames.errorList} role=\"alert\">\n {errorMessages.map((message, index) => (\n <p key={index}>\n <Text\n className={classNames.errorMessage}\n icon={<FaCircleExclamationIcon className={classNames.errorIcon} />}\n >\n {message}\n </Text>\n </p>\n ))}\n </div>\n ) : null,\n)\n\nconst SupplementaryMessageText = memo<\n Pick<Props, 'supplementaryMessage'> & { managedHtmlFor: string }\n>(({ supplementaryMessage, managedHtmlFor }) =>\n supplementaryMessage ? (\n <Text\n as=\"p\"\n size=\"S\"\n color=\"TEXT_GREY\"\n id={`${managedHtmlFor}_supplementaryMessage`}\n className=\"smarthr-ui-FormControl-supplementaryMessage\"\n >\n {supplementaryMessage}\n </Text>\n ) : null,\n)\n\nexport const FormControl: FC<Omit<Props, 'as' | 'disabled'>> = ActualFormControl\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAuEA;AAEA;AACE;AACE;;;;;;;;;AASC;;;AAGD;;AAEA;AACD;AACD;AACE;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAC2B;AAC7B;AACE;AACA;AACD;AACF;AACD;;;;;AAKE;AACE;AACA;AACA;AACE;AACD;AACF;AACF;AACF;AAED;AAEO;;AAmBL;AACA;;;AAGA;AACA;AACA;AAEA;;;AAII;;;AAGA;;;AAGA;;;AAGA;;AAGF;AACF;AAEA;AAKA;;AAEI;;AAGF;AACF;AAEA;;;;AAKI;;;AAGA;AACA;AACA;AACA;;AAEJ;;AAGE;;;AAIE;;;;;;;;;;;;AAgBA;;;;;;;;;;;;;;;AAmBF;;;;;;;;AAYE;;AAEJ;;;;;;;;AAYI;AACE;;;AAEA;;;;;;;AAQJ;;;;;;AASA;;AAEA;AACE;AAEE;;;AAIF;AAEE;AACA;;;AAIJ;;AAGF;AAoCF;AAEA;;AAkCI;;;AAGM;AACA;AACE;AACI;AACD;AACH;AACI;AACA;AACA;AACD;;;;;AAML;AACA;;;;AAKF;AACE;AACA;AACA;AACD;AACD;;;AAIJ;;;;AAqBF;AAGF;AAQA;AASA;AAeA;AAyBA;AAgBO;;"}
|
|
@@ -6,7 +6,7 @@ var React = require('react');
|
|
|
6
6
|
var index = require('./../../../vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.17_@types_node@20.19.35_typescript@5.9.3__/vendor/tailwind-variants/dist/index.cjs');
|
|
7
7
|
require('../../../intl/IntlProvider.cjs');
|
|
8
8
|
var intl_useIntl = require('../../../intl/useIntl.cjs');
|
|
9
|
-
require('
|
|
9
|
+
require('../../../intl/Localizer.cjs');
|
|
10
10
|
require('../../../_virtual/dayjs.min.cjs');
|
|
11
11
|
require('../../../intl/TimeFormatter.cjs');
|
|
12
12
|
var components_Button_Button = require('../../Button/Button.cjs');
|
|
@@ -4,7 +4,7 @@ import { memo, useMemo } from 'react';
|
|
|
4
4
|
import { tv as ce } from './../../../vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.17_@types_node@20.19.35_typescript@5.9.3__/vendor/tailwind-variants/dist/index.js';
|
|
5
5
|
import '../../../intl/IntlProvider.js';
|
|
6
6
|
import { useIntl } from '../../../intl/useIntl.js';
|
|
7
|
-
import '
|
|
7
|
+
import '../../../intl/Localizer.js';
|
|
8
8
|
import '../../../_virtual/dayjs.min.js';
|
|
9
9
|
import '../../../intl/TimeFormatter.js';
|
|
10
10
|
import { Button } from '../../Button/Button.js';
|