smarthr-ui 96.1.2 → 97.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/Combobox/helper.cjs +29 -13
- package/lib/components/Combobox/helper.cjs.map +1 -1
- package/lib/components/Combobox/helper.d.ts +1 -1
- package/lib/components/Combobox/helper.js +29 -13
- package/lib/components/Combobox/helper.js.map +1 -1
- package/lib/components/Combobox/useOptions.cjs +21 -17
- package/lib/components/Combobox/useOptions.cjs.map +1 -1
- package/lib/components/Combobox/useOptions.js +21 -17
- package/lib/components/Combobox/useOptions.js.map +1 -1
- package/lib/components/Dialog/DialogContentInner.cjs +8 -1
- package/lib/components/Dialog/DialogContentInner.cjs.map +1 -1
- package/lib/components/Dialog/DialogContentInner.js +9 -2
- package/lib/components/Dialog/DialogContentInner.js.map +1 -1
- package/lib/components/Dialog/ModelessDialog/ModelessDialog.cjs +13 -11
- package/lib/components/Dialog/ModelessDialog/ModelessDialog.cjs.map +1 -1
- package/lib/components/Dialog/ModelessDialog/ModelessDialog.js +13 -11
- package/lib/components/Dialog/ModelessDialog/ModelessDialog.js.map +1 -1
- package/lib/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.cjs +5 -2
- package/lib/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.cjs.map +1 -1
- package/lib/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.js +5 -2
- package/lib/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.js.map +1 -1
- package/lib/components/Disclosure/DisclosureTrigger.cjs +39 -18
- package/lib/components/Disclosure/DisclosureTrigger.cjs.map +1 -1
- package/lib/components/Disclosure/DisclosureTrigger.js +40 -19
- package/lib/components/Disclosure/DisclosureTrigger.js.map +1 -1
- package/lib/components/Disclosure/useDisclosure.cjs +8 -14
- package/lib/components/Disclosure/useDisclosure.cjs.map +1 -1
- package/lib/components/Disclosure/useDisclosure.js +8 -14
- package/lib/components/Disclosure/useDisclosure.js.map +1 -1
- package/lib/components/DropZone/DropZone.cjs +3 -0
- package/lib/components/DropZone/DropZone.cjs.map +1 -1
- package/lib/components/DropZone/DropZone.js +3 -0
- package/lib/components/DropZone/DropZone.js.map +1 -1
- package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.cjs +4 -2
- package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.cjs.map +1 -1
- package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.js +4 -2
- package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.js.map +1 -1
- package/lib/components/Dropdown/DropdownTrigger.cjs +3 -0
- package/lib/components/Dropdown/DropdownTrigger.cjs.map +1 -1
- package/lib/components/Dropdown/DropdownTrigger.js +3 -0
- package/lib/components/Dropdown/DropdownTrigger.js.map +1 -1
- package/lib/components/InputFile/InputFileMultiplyAppendable.cjs +12 -17
- package/lib/components/InputFile/InputFileMultiplyAppendable.cjs.map +1 -1
- package/lib/components/InputFile/InputFileMultiplyAppendable.js +12 -17
- package/lib/components/InputFile/InputFileMultiplyAppendable.js.map +1 -1
- package/lib/components/LineClamp/LineClamp.cjs +14 -3
- package/lib/components/LineClamp/LineClamp.cjs.map +1 -1
- package/lib/components/LineClamp/LineClamp.js +14 -3
- package/lib/components/LineClamp/LineClamp.js.map +1 -1
- package/lib/components/Select/Select.cjs +11 -11
- package/lib/components/Select/Select.cjs.map +1 -1
- package/lib/components/Select/Select.js +12 -12
- package/lib/components/Select/Select.js.map +1 -1
- package/lib/components/TabBar/TabItem.cjs +11 -9
- package/lib/components/TabBar/TabItem.cjs.map +1 -1
- package/lib/components/TabBar/TabItem.d.ts +2 -2
- package/lib/components/TabBar/TabItem.js +13 -11
- package/lib/components/TabBar/TabItem.js.map +1 -1
- package/lib/hooks/useHandleEscape.cjs +4 -4
- package/lib/hooks/useHandleEscape.cjs.map +1 -1
- package/lib/hooks/useHandleEscape.d.ts +1 -1
- package/lib/hooks/useHandleEscape.js +4 -4
- package/lib/hooks/useHandleEscape.js.map +1 -1
- package/lib/intl/Localizer.d.ts +3 -3
- package/lib/intl/locales/en_us.json.cjs +133 -0
- package/lib/intl/locales/en_us.json.cjs.map +1 -0
- package/lib/intl/locales/en_us.json.js +131 -0
- package/lib/intl/locales/en_us.json.js.map +1 -0
- package/lib/intl/locales/id_id.json.cjs +133 -0
- package/lib/intl/locales/id_id.json.cjs.map +1 -0
- package/lib/intl/locales/id_id.json.js +131 -0
- package/lib/intl/locales/id_id.json.js.map +1 -0
- package/lib/intl/locales/index.cjs +24 -18
- package/lib/intl/locales/index.cjs.map +1 -1
- package/lib/intl/locales/index.d.ts +1135 -1136
- package/lib/intl/locales/index.js +25 -19
- package/lib/intl/locales/index.js.map +1 -1
- package/lib/intl/locales/ja.json.cjs +133 -0
- package/lib/intl/locales/ja.json.cjs.map +1 -0
- package/lib/intl/locales/ja.json.js +131 -0
- package/lib/intl/locales/ja.json.js.map +1 -0
- package/lib/intl/locales/ja_easy.json.cjs +133 -0
- package/lib/intl/locales/ja_easy.json.cjs.map +1 -0
- package/lib/intl/locales/ja_easy.json.js +131 -0
- package/lib/intl/locales/ja_easy.json.js.map +1 -0
- package/lib/intl/locales/ko_kr.json.cjs +133 -0
- package/lib/intl/locales/ko_kr.json.cjs.map +1 -0
- package/lib/intl/locales/ko_kr.json.js +131 -0
- package/lib/intl/locales/ko_kr.json.js.map +1 -0
- package/lib/intl/locales/pt_br.json.cjs +133 -0
- package/lib/intl/locales/pt_br.json.cjs.map +1 -0
- package/lib/intl/locales/pt_br.json.js +131 -0
- package/lib/intl/locales/pt_br.json.js.map +1 -0
- package/lib/intl/locales/vi_vn.json.cjs +133 -0
- package/lib/intl/locales/vi_vn.json.cjs.map +1 -0
- package/lib/intl/locales/vi_vn.json.js +131 -0
- package/lib/intl/locales/vi_vn.json.js.map +1 -0
- package/lib/intl/locales/zh_hans_cn.json.cjs +133 -0
- package/lib/intl/locales/zh_hans_cn.json.cjs.map +1 -0
- package/lib/intl/locales/zh_hans_cn.json.js +131 -0
- package/lib/intl/locales/zh_hans_cn.json.js.map +1 -0
- package/lib/intl/locales/zh_hant_tw.json.cjs +133 -0
- package/lib/intl/locales/zh_hant_tw.json.cjs.map +1 -0
- package/lib/intl/locales/zh_hant_tw.json.js +131 -0
- package/lib/intl/locales/zh_hant_tw.json.js.map +1 -0
- package/lib/intl/useIntl.cjs.map +1 -1
- package/lib/intl/useIntl.d.ts +3 -3
- package/lib/intl/useIntl.js.map +1 -1
- package/metadata.json +1 -1
- package/package.json +2 -1
- package/lib/intl/locales/en_us.cjs +0 -135
- package/lib/intl/locales/en_us.cjs.map +0 -1
- package/lib/intl/locales/en_us.d.ts +0 -128
- package/lib/intl/locales/en_us.js +0 -133
- package/lib/intl/locales/en_us.js.map +0 -1
- package/lib/intl/locales/id_id.cjs +0 -135
- package/lib/intl/locales/id_id.cjs.map +0 -1
- package/lib/intl/locales/id_id.d.ts +0 -128
- package/lib/intl/locales/id_id.js +0 -133
- package/lib/intl/locales/id_id.js.map +0 -1
- package/lib/intl/locales/ja_easy.cjs +0 -135
- package/lib/intl/locales/ja_easy.cjs.map +0 -1
- package/lib/intl/locales/ja_easy.d.ts +0 -128
- package/lib/intl/locales/ja_easy.js +0 -133
- package/lib/intl/locales/ja_easy.js.map +0 -1
- package/lib/intl/locales/ko_kr.cjs +0 -135
- package/lib/intl/locales/ko_kr.cjs.map +0 -1
- package/lib/intl/locales/ko_kr.d.ts +0 -128
- package/lib/intl/locales/ko_kr.js +0 -133
- package/lib/intl/locales/ko_kr.js.map +0 -1
- package/lib/intl/locales/pt_br.cjs +0 -135
- package/lib/intl/locales/pt_br.cjs.map +0 -1
- package/lib/intl/locales/pt_br.d.ts +0 -128
- package/lib/intl/locales/pt_br.js +0 -133
- package/lib/intl/locales/pt_br.js.map +0 -1
- package/lib/intl/locales/vi_vn.cjs +0 -135
- package/lib/intl/locales/vi_vn.cjs.map +0 -1
- package/lib/intl/locales/vi_vn.d.ts +0 -128
- package/lib/intl/locales/vi_vn.js +0 -133
- package/lib/intl/locales/vi_vn.js.map +0 -1
- package/lib/intl/locales/zh_hans_cn.cjs +0 -135
- package/lib/intl/locales/zh_hans_cn.cjs.map +0 -1
- package/lib/intl/locales/zh_hans_cn.d.ts +0 -128
- package/lib/intl/locales/zh_hans_cn.js +0 -133
- package/lib/intl/locales/zh_hans_cn.js.map +0 -1
- package/lib/intl/locales/zh_hant_tw.cjs +0 -135
- package/lib/intl/locales/zh_hant_tw.cjs.map +0 -1
- package/lib/intl/locales/zh_hant_tw.d.ts +0 -128
- package/lib/intl/locales/zh_hant_tw.js +0 -133
- package/lib/intl/locales/zh_hant_tw.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownMenuButton.cjs","sources":["../../../../src/components/Dropdown/DropdownMenuButton/DropdownMenuButton.tsx"],"sourcesContent":["'use client'\n\nimport {\n Children,\n type ComponentProps,\n type ComponentPropsWithRef,\n type ComponentType,\n type FC,\n Fragment,\n type ReactElement,\n type ReactNode,\n isValidElement,\n memo,\n useContext,\n useEffect,\n useMemo,\n useRef,\n} from 'react'\nimport
|
|
1
|
+
{"version":3,"file":"DropdownMenuButton.cjs","sources":["../../../../src/components/Dropdown/DropdownMenuButton/DropdownMenuButton.tsx"],"sourcesContent":["'use client'\n\nimport {\n Children,\n type ComponentProps,\n type ComponentPropsWithRef,\n type ComponentType,\n type FC,\n Fragment,\n type ReactElement,\n type ReactNode,\n isValidElement,\n memo,\n useContext,\n useEffect,\n useMemo,\n useRef,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { useObjectAttributes } from '../../../hooks/useObjectAttributes'\nimport { useIntl } from '../../../intl'\nimport { type AnchorButton, Button, type AbstractProps as ButtonProps } from '../../Button'\nimport { FaCaretDownIcon, FaEllipsisIcon } from '../../Icon'\nimport { Dropdown, DropdownContext } from '../Dropdown'\nimport { DropdownCloser } from '../DropdownCloser'\nimport { DropdownContent } from '../DropdownContent'\nimport { DropdownTrigger } from '../DropdownTrigger'\n\nimport { DropdownMenuGroup } from './DropdownMenuGroup'\nimport useKeyboardNavigation from './useKeyboardNavigation'\n\nimport type { RemoteDialogTrigger } from '../../Dialog'\n\ntype Actions = ActionItem | ActionItem[]\n\n// これでコンポーネントを絞れるわけではないが Button[variant=text] を使ってほしいんだよ! という気持ち\ntype ActionItem =\n | ReactElement<ComponentProps<typeof Button>>\n | ReactElement<ComponentProps<typeof AnchorButton>>\n | ReactElement<ComponentProps<typeof RemoteDialogTrigger>>\n | ReactNode\n\ntype ObjectTriggerType = {\n /** 引き金となるボタンラベル */\n children: ReactNode\n /** 引き金となるボタンの大きさ */\n size?: ButtonProps['size']\n /** 引き金となるボタンをアイコンのみとするかどうか */\n onlyIcon?:\n | boolean\n | {\n /** 引き金となるアイコンを差し替えたい場合(onlyIcon=true の場合のみ有効) */\n component?: ComponentType<ComponentProps<typeof FaCaretDownIcon>>\n }\n}\ntype AbstractProps = {\n /** 引き金となるボタン */\n trigger: ReactNode | ObjectTriggerType\n /** 操作群 */\n children: Actions\n /** ドロップダウンメニューが開かれた際のイベント */\n onOpen?: () => void\n /** ドロップダウンメニューが閉じられた際のイベント */\n onClose?: () => void\n}\ntype ElementProps = Omit<ComponentPropsWithRef<'button'>, keyof AbstractProps>\ntype Props = AbstractProps & ElementProps\n\nconst triggerObjectConverter = (trigger: ReactNode) => ({\n children: trigger,\n})\n\nconst classNameGenerator = tv({\n slots: {\n triggerWrapper: 'smarthr-ui-DropdownMenuButton',\n triggerButton:\n 'smarthr-ui-DropdownMenuButton-trigger [&[aria-expanded=\"true\"]_.smarthr-ui-Icon:last-child]:shr-rotate-180',\n actionList: [\n 'smarthr-ui-DropdownMenuButton-panel',\n 'shr-list-none shr-py-0.5',\n [\n /* unset した Button の右 padding 分 */\n '[&_.smarthr-ui-Button-disabledWrapper]:shr-pe-1',\n '[&_.smarthr-ui-Button-disabledWrapper]:shr-gap-x-0.5',\n '[&_.smarthr-ui-Button-disabledWrapper_>_.smarthr-ui-Button]:shr-w-[unset] [&_.smarthr-ui-Button-disabledWrapper_>_.smarthr-ui-Button]:shr-bg-transparent [&_.smarthr-ui-Button-disabledWrapper_>_.smarthr-ui-Button]:shr-pe-[unset]',\n ],\n ],\n actionListItemButton: [\n // HINT: 実際にレンダリングされた要素のclassに対して追加されるため、優先度を上げる必要がある\n '[&&]:shr-w-full [&&]:shr-justify-start [&&]:shr-rounded-none [&&]:shr-border-none [&&]:shr-py-0.5 [&&]:shr-font-normal',\n '[&&]:focus-visible:shr-focus-indicator',\n ],\n },\n})\n\nconst { triggerWrapper, triggerButton, actionList, actionListItemButton } = classNameGenerator()\n\nexport const DropdownMenuButton: FC<Props> = ({\n trigger,\n children,\n onOpen,\n onClose,\n className,\n ...rest\n}) => {\n const {\n children: triggerChildren,\n size: triggerSize,\n onlyIcon: onlyIconTrigger,\n } = useObjectAttributes<ReactNode | ObjectTriggerType, ObjectTriggerType>(\n trigger,\n triggerObjectConverter,\n )\n\n const containerRef = useRef<HTMLUListElement>(null)\n\n useKeyboardNavigation(containerRef)\n\n const classNames = useMemo(\n () => ({\n triggerWrapper: triggerWrapper({ className }),\n triggerButton: triggerButton(),\n actionList: actionList(),\n }),\n [className],\n )\n\n return (\n <Dropdown onOpen={onOpen} onClose={onClose}>\n <MemoizedTriggerButton\n {...rest}\n onlyIconTrigger={onlyIconTrigger}\n triggerSize={triggerSize}\n classNames={classNames}\n >\n {triggerChildren}\n </MemoizedTriggerButton>\n <DropdownContent controllable={true}>\n <menu ref={containerRef} role=\"menu\" className={classNames.actionList}>\n {renderButtonList(children)}\n </menu>\n </DropdownContent>\n </Dropdown>\n )\n}\n\nconst MemoizedTriggerButton = memo<\n ElementProps & {\n onlyIconTrigger: ObjectTriggerType['onlyIcon']\n triggerSize: ObjectTriggerType['size']\n children: ObjectTriggerType['children']\n classNames: {\n triggerWrapper: string\n triggerButton: string\n }\n }\n>(({ onlyIconTrigger, triggerSize, children, classNames, ...rest }) => {\n const { localize } = useIntl()\n\n const { active } = useContext(DropdownContext)\n\n return (\n <DropdownTrigger\n className={classNames.triggerWrapper}\n tooltip={{ show: !!onlyIconTrigger, message: children }}\n >\n <Button\n {...rest}\n suffix={\n !onlyIconTrigger && (\n <FaCaretDownIcon\n alt={\n active\n ? localize({\n id: 'smarthr-ui/DropdownMenuButton/triggerActive',\n defaultText: '候補を閉じる',\n })\n : localize({\n id: 'smarthr-ui/DropdownMenuButton/triggerInactive',\n defaultText: '候補を開く',\n })\n }\n />\n )\n }\n size={triggerSize}\n className={classNames.triggerButton}\n >\n <TriggerLabelText onlyIconTrigger={onlyIconTrigger}>{children}</TriggerLabelText>\n </Button>\n </DropdownTrigger>\n )\n})\n\nconst TriggerLabelText = memo<{\n onlyIconTrigger: ObjectTriggerType['onlyIcon']\n children: ObjectTriggerType['children']\n}>(({ children, onlyIconTrigger }) => {\n if (!onlyIconTrigger) {\n return children\n }\n\n const Icon = (typeof onlyIconTrigger === 'object' && onlyIconTrigger.component) || FaEllipsisIcon\n\n return <Icon alt={children} />\n})\n\nexport const renderButtonList = (children: Actions) =>\n Children.map(children, (item): ReactNode => {\n if (!item || !isValidElement(item)) {\n return null\n }\n\n switch (item.type) {\n case Fragment:\n return renderButtonList(item.props.children)\n case DropdownMenuGroup:\n return item\n }\n\n return <ButtonListItem>{item}</ButtonListItem>\n })\n\nconst ButtonListItem: FC<{ children: ReactElement }> = ({ children }) => {\n const ref = useRef<HTMLLIElement>(null)\n\n useEffect(() => {\n const listItem = ref.current\n if (!listItem) {\n return\n }\n\n const setupButton = () => {\n const button = listItem.querySelector('button,a')\n\n if (button) {\n button.setAttribute('role', 'menuitem')\n button.setAttribute(\n 'class',\n actionListItemButton({ className: button.getAttribute('class') }),\n )\n }\n }\n\n setupButton()\n\n const observer = new MutationObserver(setupButton)\n observer.observe(listItem, {\n childList: true,\n subtree: true,\n // button要素の disabled / aria-disabled が動的に変化した場合も検知してリスナーを貼り直す\n attributes: true,\n attributeFilter: ['disabled', 'aria-disabled'],\n })\n\n return () => {\n observer.disconnect()\n }\n }, [])\n\n return (\n <li role=\"presentation\" ref={ref}>\n <DropdownCloser>{children}</DropdownCloser>\n </li>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAqEA;AACE;AACD;AAED;AACE;AACE;AACA;AAEA;;;AAGE;;;;;AAKC;AACF;AACD;;;;AAIC;AACF;AACF;AAED;;;AAmBE;;AAIA;AAEI;;;AAGD;AAIH;AAiBF;AAEA;AAWE;;AAIA;;AAaoB;AACA;;;AAGA;AACA;;AAatB;AAEA;;AAKI;;AAGF;AAEA;AACF;AAEO;;AAGD;;AAGF;AACE;;AAEA;AACE;;AAGJ;AACF;AAEF;AACE;;AAGE;;;;;;;AASI;AACA;;AAKJ;AAEA;AAEA;AACA;AACE;AACA;;AAEA;AACA;AACD;AAED;;AAEA;;AAGF;AAKF;;;"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
3
|
import { memo, useContext, useRef, useMemo, Children, isValidElement, Fragment, useEffect } 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 { useObjectAttributes } from '../../../hooks/useObjectAttributes.js';
|
|
7
6
|
import '../../../intl/IntlProvider.js';
|
|
@@ -76,7 +75,7 @@ const TriggerLabelText = memo(({ children, onlyIconTrigger }) => {
|
|
|
76
75
|
return children;
|
|
77
76
|
}
|
|
78
77
|
const Icon = (typeof onlyIconTrigger === 'object' && onlyIconTrigger.component) || FaEllipsisIcon;
|
|
79
|
-
return jsx(Icon, { alt:
|
|
78
|
+
return jsx(Icon, { alt: children });
|
|
80
79
|
});
|
|
81
80
|
const renderButtonList = (children) => Children.map(children, (item) => {
|
|
82
81
|
if (!item || !isValidElement(item)) {
|
|
@@ -109,6 +108,9 @@ const ButtonListItem = ({ children }) => {
|
|
|
109
108
|
observer.observe(listItem, {
|
|
110
109
|
childList: true,
|
|
111
110
|
subtree: true,
|
|
111
|
+
// button要素の disabled / aria-disabled が動的に変化した場合も検知してリスナーを貼り直す
|
|
112
|
+
attributes: true,
|
|
113
|
+
attributeFilter: ['disabled', 'aria-disabled'],
|
|
112
114
|
});
|
|
113
115
|
return () => {
|
|
114
116
|
observer.disconnect();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownMenuButton.js","sources":["../../../../src/components/Dropdown/DropdownMenuButton/DropdownMenuButton.tsx"],"sourcesContent":["'use client'\n\nimport {\n Children,\n type ComponentProps,\n type ComponentPropsWithRef,\n type ComponentType,\n type FC,\n Fragment,\n type ReactElement,\n type ReactNode,\n isValidElement,\n memo,\n useContext,\n useEffect,\n useMemo,\n useRef,\n} from 'react'\nimport
|
|
1
|
+
{"version":3,"file":"DropdownMenuButton.js","sources":["../../../../src/components/Dropdown/DropdownMenuButton/DropdownMenuButton.tsx"],"sourcesContent":["'use client'\n\nimport {\n Children,\n type ComponentProps,\n type ComponentPropsWithRef,\n type ComponentType,\n type FC,\n Fragment,\n type ReactElement,\n type ReactNode,\n isValidElement,\n memo,\n useContext,\n useEffect,\n useMemo,\n useRef,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { useObjectAttributes } from '../../../hooks/useObjectAttributes'\nimport { useIntl } from '../../../intl'\nimport { type AnchorButton, Button, type AbstractProps as ButtonProps } from '../../Button'\nimport { FaCaretDownIcon, FaEllipsisIcon } from '../../Icon'\nimport { Dropdown, DropdownContext } from '../Dropdown'\nimport { DropdownCloser } from '../DropdownCloser'\nimport { DropdownContent } from '../DropdownContent'\nimport { DropdownTrigger } from '../DropdownTrigger'\n\nimport { DropdownMenuGroup } from './DropdownMenuGroup'\nimport useKeyboardNavigation from './useKeyboardNavigation'\n\nimport type { RemoteDialogTrigger } from '../../Dialog'\n\ntype Actions = ActionItem | ActionItem[]\n\n// これでコンポーネントを絞れるわけではないが Button[variant=text] を使ってほしいんだよ! という気持ち\ntype ActionItem =\n | ReactElement<ComponentProps<typeof Button>>\n | ReactElement<ComponentProps<typeof AnchorButton>>\n | ReactElement<ComponentProps<typeof RemoteDialogTrigger>>\n | ReactNode\n\ntype ObjectTriggerType = {\n /** 引き金となるボタンラベル */\n children: ReactNode\n /** 引き金となるボタンの大きさ */\n size?: ButtonProps['size']\n /** 引き金となるボタンをアイコンのみとするかどうか */\n onlyIcon?:\n | boolean\n | {\n /** 引き金となるアイコンを差し替えたい場合(onlyIcon=true の場合のみ有効) */\n component?: ComponentType<ComponentProps<typeof FaCaretDownIcon>>\n }\n}\ntype AbstractProps = {\n /** 引き金となるボタン */\n trigger: ReactNode | ObjectTriggerType\n /** 操作群 */\n children: Actions\n /** ドロップダウンメニューが開かれた際のイベント */\n onOpen?: () => void\n /** ドロップダウンメニューが閉じられた際のイベント */\n onClose?: () => void\n}\ntype ElementProps = Omit<ComponentPropsWithRef<'button'>, keyof AbstractProps>\ntype Props = AbstractProps & ElementProps\n\nconst triggerObjectConverter = (trigger: ReactNode) => ({\n children: trigger,\n})\n\nconst classNameGenerator = tv({\n slots: {\n triggerWrapper: 'smarthr-ui-DropdownMenuButton',\n triggerButton:\n 'smarthr-ui-DropdownMenuButton-trigger [&[aria-expanded=\"true\"]_.smarthr-ui-Icon:last-child]:shr-rotate-180',\n actionList: [\n 'smarthr-ui-DropdownMenuButton-panel',\n 'shr-list-none shr-py-0.5',\n [\n /* unset した Button の右 padding 分 */\n '[&_.smarthr-ui-Button-disabledWrapper]:shr-pe-1',\n '[&_.smarthr-ui-Button-disabledWrapper]:shr-gap-x-0.5',\n '[&_.smarthr-ui-Button-disabledWrapper_>_.smarthr-ui-Button]:shr-w-[unset] [&_.smarthr-ui-Button-disabledWrapper_>_.smarthr-ui-Button]:shr-bg-transparent [&_.smarthr-ui-Button-disabledWrapper_>_.smarthr-ui-Button]:shr-pe-[unset]',\n ],\n ],\n actionListItemButton: [\n // HINT: 実際にレンダリングされた要素のclassに対して追加されるため、優先度を上げる必要がある\n '[&&]:shr-w-full [&&]:shr-justify-start [&&]:shr-rounded-none [&&]:shr-border-none [&&]:shr-py-0.5 [&&]:shr-font-normal',\n '[&&]:focus-visible:shr-focus-indicator',\n ],\n },\n})\n\nconst { triggerWrapper, triggerButton, actionList, actionListItemButton } = classNameGenerator()\n\nexport const DropdownMenuButton: FC<Props> = ({\n trigger,\n children,\n onOpen,\n onClose,\n className,\n ...rest\n}) => {\n const {\n children: triggerChildren,\n size: triggerSize,\n onlyIcon: onlyIconTrigger,\n } = useObjectAttributes<ReactNode | ObjectTriggerType, ObjectTriggerType>(\n trigger,\n triggerObjectConverter,\n )\n\n const containerRef = useRef<HTMLUListElement>(null)\n\n useKeyboardNavigation(containerRef)\n\n const classNames = useMemo(\n () => ({\n triggerWrapper: triggerWrapper({ className }),\n triggerButton: triggerButton(),\n actionList: actionList(),\n }),\n [className],\n )\n\n return (\n <Dropdown onOpen={onOpen} onClose={onClose}>\n <MemoizedTriggerButton\n {...rest}\n onlyIconTrigger={onlyIconTrigger}\n triggerSize={triggerSize}\n classNames={classNames}\n >\n {triggerChildren}\n </MemoizedTriggerButton>\n <DropdownContent controllable={true}>\n <menu ref={containerRef} role=\"menu\" className={classNames.actionList}>\n {renderButtonList(children)}\n </menu>\n </DropdownContent>\n </Dropdown>\n )\n}\n\nconst MemoizedTriggerButton = memo<\n ElementProps & {\n onlyIconTrigger: ObjectTriggerType['onlyIcon']\n triggerSize: ObjectTriggerType['size']\n children: ObjectTriggerType['children']\n classNames: {\n triggerWrapper: string\n triggerButton: string\n }\n }\n>(({ onlyIconTrigger, triggerSize, children, classNames, ...rest }) => {\n const { localize } = useIntl()\n\n const { active } = useContext(DropdownContext)\n\n return (\n <DropdownTrigger\n className={classNames.triggerWrapper}\n tooltip={{ show: !!onlyIconTrigger, message: children }}\n >\n <Button\n {...rest}\n suffix={\n !onlyIconTrigger && (\n <FaCaretDownIcon\n alt={\n active\n ? localize({\n id: 'smarthr-ui/DropdownMenuButton/triggerActive',\n defaultText: '候補を閉じる',\n })\n : localize({\n id: 'smarthr-ui/DropdownMenuButton/triggerInactive',\n defaultText: '候補を開く',\n })\n }\n />\n )\n }\n size={triggerSize}\n className={classNames.triggerButton}\n >\n <TriggerLabelText onlyIconTrigger={onlyIconTrigger}>{children}</TriggerLabelText>\n </Button>\n </DropdownTrigger>\n )\n})\n\nconst TriggerLabelText = memo<{\n onlyIconTrigger: ObjectTriggerType['onlyIcon']\n children: ObjectTriggerType['children']\n}>(({ children, onlyIconTrigger }) => {\n if (!onlyIconTrigger) {\n return children\n }\n\n const Icon = (typeof onlyIconTrigger === 'object' && onlyIconTrigger.component) || FaEllipsisIcon\n\n return <Icon alt={children} />\n})\n\nexport const renderButtonList = (children: Actions) =>\n Children.map(children, (item): ReactNode => {\n if (!item || !isValidElement(item)) {\n return null\n }\n\n switch (item.type) {\n case Fragment:\n return renderButtonList(item.props.children)\n case DropdownMenuGroup:\n return item\n }\n\n return <ButtonListItem>{item}</ButtonListItem>\n })\n\nconst ButtonListItem: FC<{ children: ReactElement }> = ({ children }) => {\n const ref = useRef<HTMLLIElement>(null)\n\n useEffect(() => {\n const listItem = ref.current\n if (!listItem) {\n return\n }\n\n const setupButton = () => {\n const button = listItem.querySelector('button,a')\n\n if (button) {\n button.setAttribute('role', 'menuitem')\n button.setAttribute(\n 'class',\n actionListItemButton({ className: button.getAttribute('class') }),\n )\n }\n }\n\n setupButton()\n\n const observer = new MutationObserver(setupButton)\n observer.observe(listItem, {\n childList: true,\n subtree: true,\n // button要素の disabled / aria-disabled が動的に変化した場合も検知してリスナーを貼り直す\n attributes: true,\n attributeFilter: ['disabled', 'aria-disabled'],\n })\n\n return () => {\n observer.disconnect()\n }\n }, [])\n\n return (\n <li role=\"presentation\" ref={ref}>\n <DropdownCloser>{children}</DropdownCloser>\n </li>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAqEA;AACE;AACD;AAED;AACE;AACE;AACA;AAEA;;;AAGE;;;;;AAKC;AACF;AACD;;;;AAIC;AACF;AACF;AAED;;;AAmBE;;AAIA;AAEI;;;AAGD;AAIH;AAiBF;AAEA;AAWE;;AAIA;;AAaoB;AACA;;;AAGA;AACA;;AAatB;AAEA;;AAKI;;AAGF;AAEA;AACF;AAEO;;AAGD;;AAGF;AACE;;AAEA;AACE;;AAGJ;AACF;AAEF;AACE;;AAGE;;;;;;;AASI;AACA;;AAKJ;AAEA;AAEA;AACA;AACE;AACA;;AAEA;AACA;AACD;AAED;;AAEA;;AAGF;AAKF;;"}
|
|
@@ -63,6 +63,9 @@ const DropdownTrigger = ({ children, className, tooltip }) => {
|
|
|
63
63
|
observer.observe(triggerElement, {
|
|
64
64
|
childList: true,
|
|
65
65
|
subtree: true,
|
|
66
|
+
// button要素の disabled / aria-disabled が動的に変化した場合も検知してリスナーを貼り直す
|
|
67
|
+
attributes: true,
|
|
68
|
+
attributeFilter: ['disabled', 'aria-disabled'],
|
|
66
69
|
});
|
|
67
70
|
return () => {
|
|
68
71
|
currentCleanup?.();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownTrigger.cjs","sources":["../../../src/components/Dropdown/DropdownTrigger.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n type PropsWithChildren,\n type ReactNode,\n useContext,\n useEffect,\n useMemo,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { tabbable } from '../../libs/tabbable'\nimport { Tooltip } from '../Tooltip'\n\nimport { DropdownContext } from './Dropdown'\n\ntype ConditionalWrapperProps = {\n shouldWrapContent?: boolean\n wrapper: FC<PropsWithChildren>\n}\n\nconst CAPTURE_OPTION = {\n capture: true,\n}\n\n/**\n * 条件付きでラッパをレンダリングする\n */\nconst ConditionalWrapper: FC<PropsWithChildren<ConditionalWrapperProps>> = ({\n shouldWrapContent,\n wrapper,\n children,\n}) => (shouldWrapContent ? wrapper({ children }) : children)\n\ntype Props = PropsWithChildren<ComponentProps<'div'>> & {\n tooltip?: { message: ReactNode; show?: boolean }\n}\n\nconst classNameGenerator = tv({\n base: 'smarthr-ui-Dropdown shr-inline-block',\n})\n\nexport const DropdownTrigger: FC<Props> = ({ children, className, tooltip }) => {\n const { active, memoizedOnClickTrigger, contentId, triggerElementRef } =\n useContext(DropdownContext)\n const actualClassName = useMemo(() => classNameGenerator({ className }), [className])\n\n useEffect(() => {\n if (!triggerElementRef.current) {\n return\n }\n\n // apply ARIA to all focusable elements in trigger\n const triggers = tabbable(triggerElementRef.current, { shouldIgnoreVisibility: true })\n\n triggers.forEach((trigger) => {\n trigger.setAttribute('aria-expanded', active.toString())\n trigger.setAttribute('aria-controls', contentId)\n })\n }, [active, triggerElementRef, contentId])\n\n useEffect(() => {\n const triggerElement = triggerElementRef.current\n if (!triggerElement) {\n return\n }\n\n let currentCleanup: (() => void) | undefined\n\n const setupButton = () => {\n // 既存のクリーンアップを実行\n currentCleanup?.()\n currentCleanup = undefined\n\n const button = triggerElement.querySelector<HTMLButtonElement>('button')\n\n // 引き金となる要素が disabled な場合、処理を差し込む必要がないため、そのまま出力する\n if (!button || button.disabled || button.getAttribute('aria-disabled') === 'true') {\n return\n }\n\n // HINT: Trigger要素自体にonClickが設定されている場合、先にDropdownを開いた状態で処理を行いたい\n // そのためcaptureで開く処理を実行する\n const callback = (e: MouseEvent) => {\n memoizedOnClickTrigger((e.currentTarget! as HTMLButtonElement).getBoundingClientRect())\n }\n\n button.addEventListener('click', callback, CAPTURE_OPTION)\n\n currentCleanup = () => {\n button.removeEventListener('click', callback, CAPTURE_OPTION)\n }\n }\n\n setupButton()\n\n const observer = new MutationObserver(setupButton)\n\n observer.observe(triggerElement, {\n childList: true,\n subtree: true,\n })\n\n return () => {\n currentCleanup?.()\n observer.disconnect()\n }\n }, [memoizedOnClickTrigger, triggerElementRef])\n\n return (\n <div ref={triggerElementRef} className={actualClassName}>\n {/* eslint-disable-next-line smarthr/a11y-scroller-has-tabindex */}\n <ConditionalWrapper\n shouldWrapContent={tooltip?.show}\n wrapper={({ children: currentChildren }) =>\n tooltip?.message ? (\n // eslint-disable-next-line smarthr/a11y-scroller-has-tabindex\n <Tooltip message={tooltip?.message} triggerType=\"icon\" tabIndex={-1}>\n {currentChildren}\n </Tooltip>\n ) : (\n currentChildren\n )\n }\n >\n {children}\n </ConditionalWrapper>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;;;AAuBA;AACE;;AAGF;;AAEG;AACH;AAUA;AACE;AACD;AAEM;AACL;AAEA;;AAGE;;;;AAKA;AAEA;;AAEE;AACF;;;AAIA;;;;AAKA;;;;;;;AAUE;;;;;AAMA;;AAEA;;;;AAMA;AACF;AAEA;AAEA;AAEA;AACE;AACA;AACD;AAED;;;AAGA;AACF;AAEA;;AAQU;AAYZ;;"}
|
|
1
|
+
{"version":3,"file":"DropdownTrigger.cjs","sources":["../../../src/components/Dropdown/DropdownTrigger.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n type PropsWithChildren,\n type ReactNode,\n useContext,\n useEffect,\n useMemo,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { tabbable } from '../../libs/tabbable'\nimport { Tooltip } from '../Tooltip'\n\nimport { DropdownContext } from './Dropdown'\n\ntype ConditionalWrapperProps = {\n shouldWrapContent?: boolean\n wrapper: FC<PropsWithChildren>\n}\n\nconst CAPTURE_OPTION = {\n capture: true,\n}\n\n/**\n * 条件付きでラッパをレンダリングする\n */\nconst ConditionalWrapper: FC<PropsWithChildren<ConditionalWrapperProps>> = ({\n shouldWrapContent,\n wrapper,\n children,\n}) => (shouldWrapContent ? wrapper({ children }) : children)\n\ntype Props = PropsWithChildren<ComponentProps<'div'>> & {\n tooltip?: { message: ReactNode; show?: boolean }\n}\n\nconst classNameGenerator = tv({\n base: 'smarthr-ui-Dropdown shr-inline-block',\n})\n\nexport const DropdownTrigger: FC<Props> = ({ children, className, tooltip }) => {\n const { active, memoizedOnClickTrigger, contentId, triggerElementRef } =\n useContext(DropdownContext)\n const actualClassName = useMemo(() => classNameGenerator({ className }), [className])\n\n useEffect(() => {\n if (!triggerElementRef.current) {\n return\n }\n\n // apply ARIA to all focusable elements in trigger\n const triggers = tabbable(triggerElementRef.current, { shouldIgnoreVisibility: true })\n\n triggers.forEach((trigger) => {\n trigger.setAttribute('aria-expanded', active.toString())\n trigger.setAttribute('aria-controls', contentId)\n })\n }, [active, triggerElementRef, contentId])\n\n useEffect(() => {\n const triggerElement = triggerElementRef.current\n if (!triggerElement) {\n return\n }\n\n let currentCleanup: (() => void) | undefined\n\n const setupButton = () => {\n // 既存のクリーンアップを実行\n currentCleanup?.()\n currentCleanup = undefined\n\n const button = triggerElement.querySelector<HTMLButtonElement>('button')\n\n // 引き金となる要素が disabled な場合、処理を差し込む必要がないため、そのまま出力する\n if (!button || button.disabled || button.getAttribute('aria-disabled') === 'true') {\n return\n }\n\n // HINT: Trigger要素自体にonClickが設定されている場合、先にDropdownを開いた状態で処理を行いたい\n // そのためcaptureで開く処理を実行する\n const callback = (e: MouseEvent) => {\n memoizedOnClickTrigger((e.currentTarget! as HTMLButtonElement).getBoundingClientRect())\n }\n\n button.addEventListener('click', callback, CAPTURE_OPTION)\n\n currentCleanup = () => {\n button.removeEventListener('click', callback, CAPTURE_OPTION)\n }\n }\n\n setupButton()\n\n const observer = new MutationObserver(setupButton)\n\n observer.observe(triggerElement, {\n childList: true,\n subtree: true,\n // button要素の disabled / aria-disabled が動的に変化した場合も検知してリスナーを貼り直す\n attributes: true,\n attributeFilter: ['disabled', 'aria-disabled'],\n })\n\n return () => {\n currentCleanup?.()\n observer.disconnect()\n }\n }, [memoizedOnClickTrigger, triggerElementRef])\n\n return (\n <div ref={triggerElementRef} className={actualClassName}>\n {/* eslint-disable-next-line smarthr/a11y-scroller-has-tabindex */}\n <ConditionalWrapper\n shouldWrapContent={tooltip?.show}\n wrapper={({ children: currentChildren }) =>\n tooltip?.message ? (\n // eslint-disable-next-line smarthr/a11y-scroller-has-tabindex\n <Tooltip message={tooltip?.message} triggerType=\"icon\" tabIndex={-1}>\n {currentChildren}\n </Tooltip>\n ) : (\n currentChildren\n )\n }\n >\n {children}\n </ConditionalWrapper>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;;;AAuBA;AACE;;AAGF;;AAEG;AACH;AAUA;AACE;AACD;AAEM;AACL;AAEA;;AAGE;;;;AAKA;AAEA;;AAEE;AACF;;;AAIA;;;;AAKA;;;;;;;AAUE;;;;;AAMA;;AAEA;;;;AAMA;AACF;AAEA;AAEA;AAEA;AACE;AACA;;AAEA;AACA;AACD;AAED;;;AAGA;AACF;AAEA;;AAQU;AAYZ;;"}
|
|
@@ -61,6 +61,9 @@ const DropdownTrigger = ({ children, className, tooltip }) => {
|
|
|
61
61
|
observer.observe(triggerElement, {
|
|
62
62
|
childList: true,
|
|
63
63
|
subtree: true,
|
|
64
|
+
// button要素の disabled / aria-disabled が動的に変化した場合も検知してリスナーを貼り直す
|
|
65
|
+
attributes: true,
|
|
66
|
+
attributeFilter: ['disabled', 'aria-disabled'],
|
|
64
67
|
});
|
|
65
68
|
return () => {
|
|
66
69
|
currentCleanup?.();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownTrigger.js","sources":["../../../src/components/Dropdown/DropdownTrigger.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n type PropsWithChildren,\n type ReactNode,\n useContext,\n useEffect,\n useMemo,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { tabbable } from '../../libs/tabbable'\nimport { Tooltip } from '../Tooltip'\n\nimport { DropdownContext } from './Dropdown'\n\ntype ConditionalWrapperProps = {\n shouldWrapContent?: boolean\n wrapper: FC<PropsWithChildren>\n}\n\nconst CAPTURE_OPTION = {\n capture: true,\n}\n\n/**\n * 条件付きでラッパをレンダリングする\n */\nconst ConditionalWrapper: FC<PropsWithChildren<ConditionalWrapperProps>> = ({\n shouldWrapContent,\n wrapper,\n children,\n}) => (shouldWrapContent ? wrapper({ children }) : children)\n\ntype Props = PropsWithChildren<ComponentProps<'div'>> & {\n tooltip?: { message: ReactNode; show?: boolean }\n}\n\nconst classNameGenerator = tv({\n base: 'smarthr-ui-Dropdown shr-inline-block',\n})\n\nexport const DropdownTrigger: FC<Props> = ({ children, className, tooltip }) => {\n const { active, memoizedOnClickTrigger, contentId, triggerElementRef } =\n useContext(DropdownContext)\n const actualClassName = useMemo(() => classNameGenerator({ className }), [className])\n\n useEffect(() => {\n if (!triggerElementRef.current) {\n return\n }\n\n // apply ARIA to all focusable elements in trigger\n const triggers = tabbable(triggerElementRef.current, { shouldIgnoreVisibility: true })\n\n triggers.forEach((trigger) => {\n trigger.setAttribute('aria-expanded', active.toString())\n trigger.setAttribute('aria-controls', contentId)\n })\n }, [active, triggerElementRef, contentId])\n\n useEffect(() => {\n const triggerElement = triggerElementRef.current\n if (!triggerElement) {\n return\n }\n\n let currentCleanup: (() => void) | undefined\n\n const setupButton = () => {\n // 既存のクリーンアップを実行\n currentCleanup?.()\n currentCleanup = undefined\n\n const button = triggerElement.querySelector<HTMLButtonElement>('button')\n\n // 引き金となる要素が disabled な場合、処理を差し込む必要がないため、そのまま出力する\n if (!button || button.disabled || button.getAttribute('aria-disabled') === 'true') {\n return\n }\n\n // HINT: Trigger要素自体にonClickが設定されている場合、先にDropdownを開いた状態で処理を行いたい\n // そのためcaptureで開く処理を実行する\n const callback = (e: MouseEvent) => {\n memoizedOnClickTrigger((e.currentTarget! as HTMLButtonElement).getBoundingClientRect())\n }\n\n button.addEventListener('click', callback, CAPTURE_OPTION)\n\n currentCleanup = () => {\n button.removeEventListener('click', callback, CAPTURE_OPTION)\n }\n }\n\n setupButton()\n\n const observer = new MutationObserver(setupButton)\n\n observer.observe(triggerElement, {\n childList: true,\n subtree: true,\n })\n\n return () => {\n currentCleanup?.()\n observer.disconnect()\n }\n }, [memoizedOnClickTrigger, triggerElementRef])\n\n return (\n <div ref={triggerElementRef} className={actualClassName}>\n {/* eslint-disable-next-line smarthr/a11y-scroller-has-tabindex */}\n <ConditionalWrapper\n shouldWrapContent={tooltip?.show}\n wrapper={({ children: currentChildren }) =>\n tooltip?.message ? (\n // eslint-disable-next-line smarthr/a11y-scroller-has-tabindex\n <Tooltip message={tooltip?.message} triggerType=\"icon\" tabIndex={-1}>\n {currentChildren}\n </Tooltip>\n ) : (\n currentChildren\n )\n }\n >\n {children}\n </ConditionalWrapper>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;AAuBA;AACE;;AAGF;;AAEG;AACH;AAUA;AACE;AACD;AAEM;AACL;AAEA;;AAGE;;;;AAKA;AAEA;;AAEE;AACF;;;AAIA;;;;AAKA;;;;;;;AAUE;;;;;AAMA;;AAEA;;;;AAMA;AACF;AAEA;AAEA;AAEA;AACE;AACA;AACD;AAED;;;AAGA;AACF;AAEA;;AAQU;AAYZ;;"}
|
|
1
|
+
{"version":3,"file":"DropdownTrigger.js","sources":["../../../src/components/Dropdown/DropdownTrigger.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n type PropsWithChildren,\n type ReactNode,\n useContext,\n useEffect,\n useMemo,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { tabbable } from '../../libs/tabbable'\nimport { Tooltip } from '../Tooltip'\n\nimport { DropdownContext } from './Dropdown'\n\ntype ConditionalWrapperProps = {\n shouldWrapContent?: boolean\n wrapper: FC<PropsWithChildren>\n}\n\nconst CAPTURE_OPTION = {\n capture: true,\n}\n\n/**\n * 条件付きでラッパをレンダリングする\n */\nconst ConditionalWrapper: FC<PropsWithChildren<ConditionalWrapperProps>> = ({\n shouldWrapContent,\n wrapper,\n children,\n}) => (shouldWrapContent ? wrapper({ children }) : children)\n\ntype Props = PropsWithChildren<ComponentProps<'div'>> & {\n tooltip?: { message: ReactNode; show?: boolean }\n}\n\nconst classNameGenerator = tv({\n base: 'smarthr-ui-Dropdown shr-inline-block',\n})\n\nexport const DropdownTrigger: FC<Props> = ({ children, className, tooltip }) => {\n const { active, memoizedOnClickTrigger, contentId, triggerElementRef } =\n useContext(DropdownContext)\n const actualClassName = useMemo(() => classNameGenerator({ className }), [className])\n\n useEffect(() => {\n if (!triggerElementRef.current) {\n return\n }\n\n // apply ARIA to all focusable elements in trigger\n const triggers = tabbable(triggerElementRef.current, { shouldIgnoreVisibility: true })\n\n triggers.forEach((trigger) => {\n trigger.setAttribute('aria-expanded', active.toString())\n trigger.setAttribute('aria-controls', contentId)\n })\n }, [active, triggerElementRef, contentId])\n\n useEffect(() => {\n const triggerElement = triggerElementRef.current\n if (!triggerElement) {\n return\n }\n\n let currentCleanup: (() => void) | undefined\n\n const setupButton = () => {\n // 既存のクリーンアップを実行\n currentCleanup?.()\n currentCleanup = undefined\n\n const button = triggerElement.querySelector<HTMLButtonElement>('button')\n\n // 引き金となる要素が disabled な場合、処理を差し込む必要がないため、そのまま出力する\n if (!button || button.disabled || button.getAttribute('aria-disabled') === 'true') {\n return\n }\n\n // HINT: Trigger要素自体にonClickが設定されている場合、先にDropdownを開いた状態で処理を行いたい\n // そのためcaptureで開く処理を実行する\n const callback = (e: MouseEvent) => {\n memoizedOnClickTrigger((e.currentTarget! as HTMLButtonElement).getBoundingClientRect())\n }\n\n button.addEventListener('click', callback, CAPTURE_OPTION)\n\n currentCleanup = () => {\n button.removeEventListener('click', callback, CAPTURE_OPTION)\n }\n }\n\n setupButton()\n\n const observer = new MutationObserver(setupButton)\n\n observer.observe(triggerElement, {\n childList: true,\n subtree: true,\n // button要素の disabled / aria-disabled が動的に変化した場合も検知してリスナーを貼り直す\n attributes: true,\n attributeFilter: ['disabled', 'aria-disabled'],\n })\n\n return () => {\n currentCleanup?.()\n observer.disconnect()\n }\n }, [memoizedOnClickTrigger, triggerElementRef])\n\n return (\n <div ref={triggerElementRef} className={actualClassName}>\n {/* eslint-disable-next-line smarthr/a11y-scroller-has-tabindex */}\n <ConditionalWrapper\n shouldWrapContent={tooltip?.show}\n wrapper={({ children: currentChildren }) =>\n tooltip?.message ? (\n // eslint-disable-next-line smarthr/a11y-scroller-has-tabindex\n <Tooltip message={tooltip?.message} triggerType=\"icon\" tabIndex={-1}>\n {currentChildren}\n </Tooltip>\n ) : (\n currentChildren\n )\n }\n >\n {children}\n </ConditionalWrapper>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;AAuBA;AACE;;AAGF;;AAEG;AACH;AAUA;AACE;AACD;AAEM;AACL;AAEA;;AAGE;;;;AAKA;AAEA;;AAEE;AACF;;;AAIA;;;;AAKA;;;;;;;AAUE;;;;;AAMA;;AAEA;;;;AAMA;AACF;AAEA;AAEA;AAEA;AACE;AACA;;AAEA;AACA;AACD;AAED;;;AAGA;AACF;AAEA;;AAQU;AAYZ;;"}
|
|
@@ -48,10 +48,13 @@ const InputFileMultiplyAppendable = React.forwardRef(({ className, size, label,
|
|
|
48
48
|
const isUpdatingFilesDirectly = React.useRef(false);
|
|
49
49
|
const inputRef = React.useRef(null);
|
|
50
50
|
React.useImperativeHandle(ref, () => inputRef.current);
|
|
51
|
-
const
|
|
51
|
+
const unstableRef = React.useRef({ onChange, files });
|
|
52
|
+
unstableRef.current = { onChange, files };
|
|
53
|
+
const updateFiles = React.useCallback((newFiles) => {
|
|
52
54
|
if (!inputRef.current) {
|
|
53
55
|
return;
|
|
54
56
|
}
|
|
57
|
+
unstableRef.current.onChange?.(newFiles);
|
|
55
58
|
const buff = new DataTransfer();
|
|
56
59
|
newFiles.forEach((file) => {
|
|
57
60
|
buff.items.add(file);
|
|
@@ -59,37 +62,29 @@ const InputFileMultiplyAppendable = React.forwardRef(({ className, size, label,
|
|
|
59
62
|
isUpdatingFilesDirectly.current = true;
|
|
60
63
|
inputRef.current.files = buff.files;
|
|
61
64
|
isUpdatingFilesDirectly.current = false;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
? (newFiles) => {
|
|
65
|
-
onChange(newFiles);
|
|
66
|
-
updateInputFiles(newFiles);
|
|
67
|
-
setFiles(newFiles);
|
|
68
|
-
}
|
|
69
|
-
: (newFiles) => {
|
|
70
|
-
setFiles(newFiles);
|
|
71
|
-
updateInputFiles(newFiles);
|
|
72
|
-
}, [onChange, updateInputFiles]);
|
|
65
|
+
setFiles(newFiles);
|
|
66
|
+
}, []);
|
|
73
67
|
const handleChange = React.useCallback((e) => {
|
|
74
68
|
// Safari において、input.files への直接代入時はonChangeを発火させない
|
|
75
69
|
if (isUpdatingFilesDirectly.current) {
|
|
76
70
|
return;
|
|
77
71
|
}
|
|
78
72
|
const newFiles = Array.from(e.target.files ?? []);
|
|
79
|
-
updateFiles([...files, ...newFiles]);
|
|
80
|
-
}, [
|
|
73
|
+
updateFiles([...unstableRef.current.files, ...newFiles]);
|
|
74
|
+
}, [updateFiles]);
|
|
81
75
|
const handleDelete = React.useCallback((e) => {
|
|
82
76
|
if (!inputRef.current) {
|
|
83
77
|
return;
|
|
84
78
|
}
|
|
85
79
|
const index = parseInt(e.currentTarget.value, 10);
|
|
86
|
-
const newFiles = files.filter((_, i) => index !== i);
|
|
80
|
+
const newFiles = unstableRef.current.files.filter((_, i) => index !== i);
|
|
87
81
|
// 削除後、同一ファイルを再選択可能にするためinput.valueをリセット
|
|
88
82
|
inputRef.current.value = '';
|
|
89
83
|
updateFiles(newFiles);
|
|
90
|
-
}, [
|
|
91
|
-
return (jsxRuntime.jsxs(components_Layout_Stack_Stack.Stack, { align: "flex-start", className: classNames.wrapper, children: [!disabled && hasFileList && files.length > 0 && (jsxRuntime.jsx(components_Base_BaseColumn_BaseColumn.BaseColumn, { as: "ul", padding: BASE_COLUMN_PADDING, className: classNames.fileList, children: files.map((file, index) => (jsxRuntime.
|
|
84
|
+
}, [updateFiles]);
|
|
85
|
+
return (jsxRuntime.jsxs(components_Layout_Stack_Stack.Stack, { align: "flex-start", className: classNames.wrapper, children: [!disabled && hasFileList && files.length > 0 && (jsxRuntime.jsx(components_Base_BaseColumn_BaseColumn.BaseColumn, { as: "ul", padding: BASE_COLUMN_PADDING, className: classNames.fileList, children: files.map((file, index) => (jsxRuntime.jsx(FileListItem, { value: index, onDeleteClick: handleDelete, destroyLabel: destroyLabel, className: classNames.fileItem, children: file.name }, index))) })), jsxRuntime.jsxs("span", { className: classNames.inputWrapper, children: [jsxRuntime.jsx("input", { ...rest, multiple: true, "data-smarthr-ui-input": "true", type: "file", onChange: handleChange, disabled: disabled, ref: inputRef, "aria-invalid": error || undefined, "aria-labelledby": labelId, className: classNames.input }), jsxRuntime.jsx(StyledFaFolderOpenIcon, { className: classNames.prefix }), jsxRuntime.jsx(LabelRender, { id: labelId, label: label })] })] }));
|
|
92
86
|
});
|
|
87
|
+
const FileListItem = React.memo(({ value, onDeleteClick, destroyLabel, className, children }) => (jsxRuntime.jsxs("li", { className: className, children: [jsxRuntime.jsx("span", { className: "smarthr-ui-InputFile-fileName", children: children }), jsxRuntime.jsx(components_Button_Button.Button, { variant: "text", prefix: jsxRuntime.jsx(components_Icon_FaIcon.FaTrashCanIcon, {}), value: value, onClick: onDeleteClick, className: "smarthr-ui-InputFile-deleteButton", children: destroyLabel })] })));
|
|
93
88
|
const StyledFaFolderOpenIcon = React.memo(({ className }) => (jsxRuntime.jsx("span", { className: className, children: jsxRuntime.jsx(components_Icon_FaIcon.FaFolderOpenIcon, {}) })));
|
|
94
89
|
const LabelRender = React.memo(({ id, label }) => (jsxRuntime.jsx("span", { id: id, "aria-hidden": "true", children: label })));
|
|
95
90
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InputFileMultiplyAppendable.cjs","sources":["../../../src/components/InputFile/InputFileMultiplyAppendable.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type MouseEvent,\n type ReactNode,\n forwardRef,\n memo,\n useCallback,\n useId,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react'\n\nimport { useIntl } from '../../intl'\nimport { BaseColumn } from '../Base'\nimport { Button } from '../Button'\nimport { FaFolderOpenIcon, FaTrashCanIcon } from '../Icon'\nimport { Stack } from '../Layout'\n\nimport { classNameGenerator } from './style'\n\nimport type { Props } from './types'\n\nconst BASE_COLUMN_PADDING = { block: 0.5, inline: 1 } as const\n\nexport const InputFileMultiplyAppendable = forwardRef<HTMLInputElement, Omit<Props, 'multiple'>>(\n (\n { className, size, label, hasFileList = true, onChange, disabled = false, error, ...rest },\n ref,\n ) => {\n const [files, setFiles] = useState<File[]>([])\n const labelId = useId()\n const { localize } = useIntl()\n\n const destroyLabel = useMemo(\n () =>\n localize({\n id: 'smarthr-ui/InputFile/destroy',\n defaultText: '削除',\n }),\n [localize],\n )\n\n const classNames = useMemo(() => {\n const { wrapper, fileList, fileItem, inputWrapper, input, prefix } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n inputWrapper: inputWrapper({ size, disabled }),\n fileList: fileList(),\n fileItem: fileItem(),\n input: input(),\n prefix: prefix(),\n }\n }, [disabled, size, className])\n\n // Safari において、input.files への直接代入時に onChange が発火することを防ぐためのフラグ\n const isUpdatingFilesDirectly = useRef(false)\n\n const inputRef = useRef<HTMLInputElement>(null)\n useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(\n ref,\n () => inputRef.current,\n )\n\n const
|
|
1
|
+
{"version":3,"file":"InputFileMultiplyAppendable.cjs","sources":["../../../src/components/InputFile/InputFileMultiplyAppendable.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type MouseEvent,\n type PropsWithChildren,\n type ReactNode,\n forwardRef,\n memo,\n useCallback,\n useId,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react'\n\nimport { useIntl } from '../../intl'\nimport { BaseColumn } from '../Base'\nimport { Button } from '../Button'\nimport { FaFolderOpenIcon, FaTrashCanIcon } from '../Icon'\nimport { Stack } from '../Layout'\n\nimport { classNameGenerator } from './style'\n\nimport type { Props } from './types'\n\nconst BASE_COLUMN_PADDING = { block: 0.5, inline: 1 } as const\n\nexport const InputFileMultiplyAppendable = forwardRef<HTMLInputElement, Omit<Props, 'multiple'>>(\n (\n { className, size, label, hasFileList = true, onChange, disabled = false, error, ...rest },\n ref,\n ) => {\n const [files, setFiles] = useState<File[]>([])\n const labelId = useId()\n const { localize } = useIntl()\n\n const destroyLabel = useMemo(\n () =>\n localize({\n id: 'smarthr-ui/InputFile/destroy',\n defaultText: '削除',\n }),\n [localize],\n )\n\n const classNames = useMemo(() => {\n const { wrapper, fileList, fileItem, inputWrapper, input, prefix } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n inputWrapper: inputWrapper({ size, disabled }),\n fileList: fileList(),\n fileItem: fileItem(),\n input: input(),\n prefix: prefix(),\n }\n }, [disabled, size, className])\n\n // Safari において、input.files への直接代入時に onChange が発火することを防ぐためのフラグ\n const isUpdatingFilesDirectly = useRef(false)\n\n const inputRef = useRef<HTMLInputElement>(null)\n useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(\n ref,\n () => inputRef.current,\n )\n\n const unstableRef = useRef({ onChange, files })\n unstableRef.current = { onChange, files }\n\n const updateFiles = useCallback((newFiles: File[]) => {\n if (!inputRef.current) {\n return\n }\n\n unstableRef.current.onChange?.(newFiles)\n\n const buff = new DataTransfer()\n newFiles.forEach((file) => {\n buff.items.add(file)\n })\n\n isUpdatingFilesDirectly.current = true\n inputRef.current.files = buff.files\n isUpdatingFilesDirectly.current = false\n\n setFiles(newFiles)\n }, [])\n\n const handleChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>) => {\n // Safari において、input.files への直接代入時はonChangeを発火させない\n if (isUpdatingFilesDirectly.current) {\n return\n }\n\n const newFiles = Array.from(e.target.files ?? [])\n\n updateFiles([...unstableRef.current.files, ...newFiles])\n },\n [updateFiles],\n )\n\n const handleDelete = useCallback(\n (e: MouseEvent<HTMLButtonElement>) => {\n if (!inputRef.current) {\n return\n }\n\n const index = parseInt(e.currentTarget.value, 10)\n const newFiles = unstableRef.current.files.filter((_, i) => index !== i)\n\n // 削除後、同一ファイルを再選択可能にするためinput.valueをリセット\n inputRef.current.value = ''\n\n updateFiles(newFiles)\n },\n [updateFiles],\n )\n\n return (\n <Stack align=\"flex-start\" className={classNames.wrapper}>\n {!disabled && hasFileList && files.length > 0 && (\n <BaseColumn as=\"ul\" padding={BASE_COLUMN_PADDING} className={classNames.fileList}>\n {files.map((file, index) => (\n <FileListItem\n key={index}\n value={index}\n onDeleteClick={handleDelete}\n destroyLabel={destroyLabel}\n className={classNames.fileItem}\n >\n {file.name}\n </FileListItem>\n ))}\n </BaseColumn>\n )}\n <span className={classNames.inputWrapper}>\n <input\n {...rest}\n multiple\n data-smarthr-ui-input=\"true\"\n type=\"file\"\n onChange={handleChange}\n disabled={disabled}\n ref={inputRef}\n aria-invalid={error || undefined}\n aria-labelledby={labelId}\n className={classNames.input}\n />\n <StyledFaFolderOpenIcon className={classNames.prefix} />\n <LabelRender id={labelId} label={label} />\n </span>\n </Stack>\n )\n },\n)\n\ntype FileListItemProps = PropsWithChildren<{\n value: number\n onDeleteClick: (e: MouseEvent<HTMLButtonElement>) => void\n destroyLabel: string\n className: string\n}>\n\nconst FileListItem = memo<FileListItemProps>(\n ({ value, onDeleteClick, destroyLabel, className, children }) => (\n <li className={className}>\n <span className=\"smarthr-ui-InputFile-fileName\">{children}</span>\n <Button\n variant=\"text\"\n prefix={<FaTrashCanIcon />}\n value={value}\n onClick={onDeleteClick}\n className=\"smarthr-ui-InputFile-deleteButton\"\n >\n {destroyLabel}\n </Button>\n </li>\n ),\n)\n\nconst StyledFaFolderOpenIcon = memo<{ className: string }>(({ className }) => (\n <span className={className}>\n <FaFolderOpenIcon />\n </span>\n))\n\nconst LabelRender = memo<{ id: string; label: ReactNode }>(({ id, label }) => (\n <span id={id} aria-hidden=\"true\">\n {label}\n </span>\n))\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA;AAEO;;AAMH;AACA;;AAKM;AACA;AACD;AAIL;AACE;;AAGE;;;;;;;;;AAUJ;AAEA;;;;AASA;AACE;;;;AAMA;AACA;AACE;AACF;AAEA;;AAEA;;;AAKF;;AAGI;;;AAIA;AAEA;AACF;AAIF;AAEI;;;AAIA;;;AAIA;;AAGF;AAIF;AAmCF;AAUF;AAiBA;AAMA;;"}
|
|
@@ -46,10 +46,13 @@ const InputFileMultiplyAppendable = forwardRef(({ className, size, label, hasFil
|
|
|
46
46
|
const isUpdatingFilesDirectly = useRef(false);
|
|
47
47
|
const inputRef = useRef(null);
|
|
48
48
|
useImperativeHandle(ref, () => inputRef.current);
|
|
49
|
-
const
|
|
49
|
+
const unstableRef = useRef({ onChange, files });
|
|
50
|
+
unstableRef.current = { onChange, files };
|
|
51
|
+
const updateFiles = useCallback((newFiles) => {
|
|
50
52
|
if (!inputRef.current) {
|
|
51
53
|
return;
|
|
52
54
|
}
|
|
55
|
+
unstableRef.current.onChange?.(newFiles);
|
|
53
56
|
const buff = new DataTransfer();
|
|
54
57
|
newFiles.forEach((file) => {
|
|
55
58
|
buff.items.add(file);
|
|
@@ -57,37 +60,29 @@ const InputFileMultiplyAppendable = forwardRef(({ className, size, label, hasFil
|
|
|
57
60
|
isUpdatingFilesDirectly.current = true;
|
|
58
61
|
inputRef.current.files = buff.files;
|
|
59
62
|
isUpdatingFilesDirectly.current = false;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
? (newFiles) => {
|
|
63
|
-
onChange(newFiles);
|
|
64
|
-
updateInputFiles(newFiles);
|
|
65
|
-
setFiles(newFiles);
|
|
66
|
-
}
|
|
67
|
-
: (newFiles) => {
|
|
68
|
-
setFiles(newFiles);
|
|
69
|
-
updateInputFiles(newFiles);
|
|
70
|
-
}, [onChange, updateInputFiles]);
|
|
63
|
+
setFiles(newFiles);
|
|
64
|
+
}, []);
|
|
71
65
|
const handleChange = useCallback((e) => {
|
|
72
66
|
// Safari において、input.files への直接代入時はonChangeを発火させない
|
|
73
67
|
if (isUpdatingFilesDirectly.current) {
|
|
74
68
|
return;
|
|
75
69
|
}
|
|
76
70
|
const newFiles = Array.from(e.target.files ?? []);
|
|
77
|
-
updateFiles([...files, ...newFiles]);
|
|
78
|
-
}, [
|
|
71
|
+
updateFiles([...unstableRef.current.files, ...newFiles]);
|
|
72
|
+
}, [updateFiles]);
|
|
79
73
|
const handleDelete = useCallback((e) => {
|
|
80
74
|
if (!inputRef.current) {
|
|
81
75
|
return;
|
|
82
76
|
}
|
|
83
77
|
const index = parseInt(e.currentTarget.value, 10);
|
|
84
|
-
const newFiles = files.filter((_, i) => index !== i);
|
|
78
|
+
const newFiles = unstableRef.current.files.filter((_, i) => index !== i);
|
|
85
79
|
// 削除後、同一ファイルを再選択可能にするためinput.valueをリセット
|
|
86
80
|
inputRef.current.value = '';
|
|
87
81
|
updateFiles(newFiles);
|
|
88
|
-
}, [
|
|
89
|
-
return (jsxs(Stack, { align: "flex-start", className: classNames.wrapper, children: [!disabled && hasFileList && files.length > 0 && (jsx(BaseColumn, { as: "ul", padding: BASE_COLUMN_PADDING, className: classNames.fileList, children: files.map((file, index) => (
|
|
82
|
+
}, [updateFiles]);
|
|
83
|
+
return (jsxs(Stack, { align: "flex-start", className: classNames.wrapper, children: [!disabled && hasFileList && files.length > 0 && (jsx(BaseColumn, { as: "ul", padding: BASE_COLUMN_PADDING, className: classNames.fileList, children: files.map((file, index) => (jsx(FileListItem, { value: index, onDeleteClick: handleDelete, destroyLabel: destroyLabel, className: classNames.fileItem, children: file.name }, index))) })), jsxs("span", { className: classNames.inputWrapper, children: [jsx("input", { ...rest, multiple: true, "data-smarthr-ui-input": "true", type: "file", onChange: handleChange, disabled: disabled, ref: inputRef, "aria-invalid": error || undefined, "aria-labelledby": labelId, className: classNames.input }), jsx(StyledFaFolderOpenIcon, { className: classNames.prefix }), jsx(LabelRender, { id: labelId, label: label })] })] }));
|
|
90
84
|
});
|
|
85
|
+
const FileListItem = memo(({ value, onDeleteClick, destroyLabel, className, children }) => (jsxs("li", { className: className, children: [jsx("span", { className: "smarthr-ui-InputFile-fileName", children: children }), jsx(Button, { variant: "text", prefix: jsx(FaTrashCanIcon, {}), value: value, onClick: onDeleteClick, className: "smarthr-ui-InputFile-deleteButton", children: destroyLabel })] })));
|
|
91
86
|
const StyledFaFolderOpenIcon = memo(({ className }) => (jsx("span", { className: className, children: jsx(FaFolderOpenIcon, {}) })));
|
|
92
87
|
const LabelRender = memo(({ id, label }) => (jsx("span", { id: id, "aria-hidden": "true", children: label })));
|
|
93
88
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InputFileMultiplyAppendable.js","sources":["../../../src/components/InputFile/InputFileMultiplyAppendable.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type MouseEvent,\n type ReactNode,\n forwardRef,\n memo,\n useCallback,\n useId,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react'\n\nimport { useIntl } from '../../intl'\nimport { BaseColumn } from '../Base'\nimport { Button } from '../Button'\nimport { FaFolderOpenIcon, FaTrashCanIcon } from '../Icon'\nimport { Stack } from '../Layout'\n\nimport { classNameGenerator } from './style'\n\nimport type { Props } from './types'\n\nconst BASE_COLUMN_PADDING = { block: 0.5, inline: 1 } as const\n\nexport const InputFileMultiplyAppendable = forwardRef<HTMLInputElement, Omit<Props, 'multiple'>>(\n (\n { className, size, label, hasFileList = true, onChange, disabled = false, error, ...rest },\n ref,\n ) => {\n const [files, setFiles] = useState<File[]>([])\n const labelId = useId()\n const { localize } = useIntl()\n\n const destroyLabel = useMemo(\n () =>\n localize({\n id: 'smarthr-ui/InputFile/destroy',\n defaultText: '削除',\n }),\n [localize],\n )\n\n const classNames = useMemo(() => {\n const { wrapper, fileList, fileItem, inputWrapper, input, prefix } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n inputWrapper: inputWrapper({ size, disabled }),\n fileList: fileList(),\n fileItem: fileItem(),\n input: input(),\n prefix: prefix(),\n }\n }, [disabled, size, className])\n\n // Safari において、input.files への直接代入時に onChange が発火することを防ぐためのフラグ\n const isUpdatingFilesDirectly = useRef(false)\n\n const inputRef = useRef<HTMLInputElement>(null)\n useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(\n ref,\n () => inputRef.current,\n )\n\n const
|
|
1
|
+
{"version":3,"file":"InputFileMultiplyAppendable.js","sources":["../../../src/components/InputFile/InputFileMultiplyAppendable.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type MouseEvent,\n type PropsWithChildren,\n type ReactNode,\n forwardRef,\n memo,\n useCallback,\n useId,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react'\n\nimport { useIntl } from '../../intl'\nimport { BaseColumn } from '../Base'\nimport { Button } from '../Button'\nimport { FaFolderOpenIcon, FaTrashCanIcon } from '../Icon'\nimport { Stack } from '../Layout'\n\nimport { classNameGenerator } from './style'\n\nimport type { Props } from './types'\n\nconst BASE_COLUMN_PADDING = { block: 0.5, inline: 1 } as const\n\nexport const InputFileMultiplyAppendable = forwardRef<HTMLInputElement, Omit<Props, 'multiple'>>(\n (\n { className, size, label, hasFileList = true, onChange, disabled = false, error, ...rest },\n ref,\n ) => {\n const [files, setFiles] = useState<File[]>([])\n const labelId = useId()\n const { localize } = useIntl()\n\n const destroyLabel = useMemo(\n () =>\n localize({\n id: 'smarthr-ui/InputFile/destroy',\n defaultText: '削除',\n }),\n [localize],\n )\n\n const classNames = useMemo(() => {\n const { wrapper, fileList, fileItem, inputWrapper, input, prefix } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n inputWrapper: inputWrapper({ size, disabled }),\n fileList: fileList(),\n fileItem: fileItem(),\n input: input(),\n prefix: prefix(),\n }\n }, [disabled, size, className])\n\n // Safari において、input.files への直接代入時に onChange が発火することを防ぐためのフラグ\n const isUpdatingFilesDirectly = useRef(false)\n\n const inputRef = useRef<HTMLInputElement>(null)\n useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(\n ref,\n () => inputRef.current,\n )\n\n const unstableRef = useRef({ onChange, files })\n unstableRef.current = { onChange, files }\n\n const updateFiles = useCallback((newFiles: File[]) => {\n if (!inputRef.current) {\n return\n }\n\n unstableRef.current.onChange?.(newFiles)\n\n const buff = new DataTransfer()\n newFiles.forEach((file) => {\n buff.items.add(file)\n })\n\n isUpdatingFilesDirectly.current = true\n inputRef.current.files = buff.files\n isUpdatingFilesDirectly.current = false\n\n setFiles(newFiles)\n }, [])\n\n const handleChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>) => {\n // Safari において、input.files への直接代入時はonChangeを発火させない\n if (isUpdatingFilesDirectly.current) {\n return\n }\n\n const newFiles = Array.from(e.target.files ?? [])\n\n updateFiles([...unstableRef.current.files, ...newFiles])\n },\n [updateFiles],\n )\n\n const handleDelete = useCallback(\n (e: MouseEvent<HTMLButtonElement>) => {\n if (!inputRef.current) {\n return\n }\n\n const index = parseInt(e.currentTarget.value, 10)\n const newFiles = unstableRef.current.files.filter((_, i) => index !== i)\n\n // 削除後、同一ファイルを再選択可能にするためinput.valueをリセット\n inputRef.current.value = ''\n\n updateFiles(newFiles)\n },\n [updateFiles],\n )\n\n return (\n <Stack align=\"flex-start\" className={classNames.wrapper}>\n {!disabled && hasFileList && files.length > 0 && (\n <BaseColumn as=\"ul\" padding={BASE_COLUMN_PADDING} className={classNames.fileList}>\n {files.map((file, index) => (\n <FileListItem\n key={index}\n value={index}\n onDeleteClick={handleDelete}\n destroyLabel={destroyLabel}\n className={classNames.fileItem}\n >\n {file.name}\n </FileListItem>\n ))}\n </BaseColumn>\n )}\n <span className={classNames.inputWrapper}>\n <input\n {...rest}\n multiple\n data-smarthr-ui-input=\"true\"\n type=\"file\"\n onChange={handleChange}\n disabled={disabled}\n ref={inputRef}\n aria-invalid={error || undefined}\n aria-labelledby={labelId}\n className={classNames.input}\n />\n <StyledFaFolderOpenIcon className={classNames.prefix} />\n <LabelRender id={labelId} label={label} />\n </span>\n </Stack>\n )\n },\n)\n\ntype FileListItemProps = PropsWithChildren<{\n value: number\n onDeleteClick: (e: MouseEvent<HTMLButtonElement>) => void\n destroyLabel: string\n className: string\n}>\n\nconst FileListItem = memo<FileListItemProps>(\n ({ value, onDeleteClick, destroyLabel, className, children }) => (\n <li className={className}>\n <span className=\"smarthr-ui-InputFile-fileName\">{children}</span>\n <Button\n variant=\"text\"\n prefix={<FaTrashCanIcon />}\n value={value}\n onClick={onDeleteClick}\n className=\"smarthr-ui-InputFile-deleteButton\"\n >\n {destroyLabel}\n </Button>\n </li>\n ),\n)\n\nconst StyledFaFolderOpenIcon = memo<{ className: string }>(({ className }) => (\n <span className={className}>\n <FaFolderOpenIcon />\n </span>\n))\n\nconst LabelRender = memo<{ id: string; label: ReactNode }>(({ id, label }) => (\n <span id={id} aria-hidden=\"true\">\n {label}\n </span>\n))\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA2BA;AAEO;;AAMH;AACA;;AAKM;AACA;AACD;AAIL;AACE;;AAGE;;;;;;;;;AAUJ;AAEA;;;;AASA;AACE;;;;AAMA;AACA;AACE;AACF;AAEA;;AAEA;;;AAKF;;AAGI;;;AAIA;AAEA;AACF;AAIF;AAEI;;;AAIA;;;AAIA;;AAGF;AAIF;AAmCF;AAUF;AAiBA;AAMA;;"}
|
|
@@ -56,12 +56,23 @@ const LineClamp = ({ maxLines = 3, children, className, ...rest }) => {
|
|
|
56
56
|
React.useEffect(() => {
|
|
57
57
|
const el = ref.current;
|
|
58
58
|
const shadowEl = shadowRef.current;
|
|
59
|
+
if (!el || !shadowEl)
|
|
60
|
+
return;
|
|
59
61
|
// -webkit-line-clamp を使った要素ではel.scrollHeightとel.clientHeightの比較だと
|
|
60
62
|
// フォントの高さの計算が期待と異なり適切な高さが取得できないためshadowElと比較している
|
|
61
63
|
// 参考: https://github.com/kufu/smarthr-ui/pull/4710
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
const checkOverflow = () => {
|
|
65
|
+
setTooltipVisible(shadowEl.clientHeight > el.clientHeight);
|
|
66
|
+
};
|
|
67
|
+
checkOverflow();
|
|
68
|
+
const resizeObserver = new ResizeObserver(checkOverflow);
|
|
69
|
+
resizeObserver.observe(el);
|
|
70
|
+
resizeObserver.observe(shadowEl);
|
|
71
|
+
return () => {
|
|
72
|
+
resizeObserver.unobserve(el);
|
|
73
|
+
resizeObserver.unobserve(shadowEl);
|
|
74
|
+
};
|
|
75
|
+
}, [maxLines]);
|
|
65
76
|
const classNames = React.useMemo(() => {
|
|
66
77
|
const { base, clampedLine, shadowElementWrapper, shadowElement } = classNameGenerator({
|
|
67
78
|
maxLines,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LineClamp.cjs","sources":["../../../src/components/LineClamp/LineClamp.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentPropsWithRef,\n type FC,\n type PropsWithChildren,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { type VariantProps, tv } from 'tailwind-variants'\n\nimport { Tooltip } from '../Tooltip'\n\ntype AbstractProps = PropsWithChildren<VariantProps<typeof classNameGenerator>>\ntype Props = AbstractProps & Omit<ComponentPropsWithRef<'span'>, keyof AbstractProps>\n\nconst classNameGenerator = tv({\n slots: {\n base: 'smarthr-ui-LineClamp shr-relative',\n clampedLine: 'shr-max-w-full',\n shadowElementWrapper:\n 'shr-invisible shr-absolute shr-left-0 shr-top-0 shr-h-full shr-w-full shr-overflow-hidden shr-whitespace-normal shr-opacity-0',\n shadowElement: 'shr-absolute shr-left-0 shr-top-0 shr-w-full',\n },\n variants: {\n maxLines: {\n 1: {\n clampedLine:\n 'shr-inline-block shr-max-w-full shr-overflow-x-clip shr-overflow-ellipsis shr-whitespace-nowrap shr-align-middle',\n },\n 2: {\n clampedLine: 'shr-line-clamp-[2]',\n },\n 3: {\n clampedLine: 'shr-line-clamp-[3]',\n },\n 4: {\n clampedLine: 'shr-line-clamp-[4]',\n },\n 5: {\n clampedLine: 'shr-line-clamp-[5]',\n },\n 6: {\n clampedLine: 'shr-line-clamp-[6]',\n },\n },\n },\n compoundVariants: [\n {\n maxLines: [2, 3, 4, 5, 6],\n className: {\n // baseがdisplay:-webkit-boxでないと高さ取得用の要素が表示部分と同じ大きさで表示されないバグを回避するため\n base: '[display:-webkit-box]',\n },\n },\n ],\n})\n\nexport const LineClamp: FC<Props> = ({ maxLines = 3, children, className, ...rest }) => {\n if (maxLines < 1 || maxLines > 6) {\n throw new Error('\"maxLines\" は 1 ~ 6 の範囲で指定してください')\n }\n\n const [isTooltipVisible, setTooltipVisible] = useState(false)\n const ref = useRef<HTMLSpanElement>(null)\n const shadowRef = useRef<HTMLSpanElement>(null)\n\n useEffect(() => {\n const el = ref.current\n const shadowEl = shadowRef.current\n\n // -webkit-line-clamp を使った要素ではel.scrollHeightとel.clientHeightの比較だと\n // フォントの高さの計算が期待と異なり適切な高さが取得できないためshadowElと比較している\n // 参考: https://github.com/kufu/smarthr-ui/pull/4710\n const
|
|
1
|
+
{"version":3,"file":"LineClamp.cjs","sources":["../../../src/components/LineClamp/LineClamp.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentPropsWithRef,\n type FC,\n type PropsWithChildren,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { type VariantProps, tv } from 'tailwind-variants'\n\nimport { Tooltip } from '../Tooltip'\n\ntype AbstractProps = PropsWithChildren<VariantProps<typeof classNameGenerator>>\ntype Props = AbstractProps & Omit<ComponentPropsWithRef<'span'>, keyof AbstractProps>\n\nconst classNameGenerator = tv({\n slots: {\n base: 'smarthr-ui-LineClamp shr-relative',\n clampedLine: 'shr-max-w-full',\n shadowElementWrapper:\n 'shr-invisible shr-absolute shr-left-0 shr-top-0 shr-h-full shr-w-full shr-overflow-hidden shr-whitespace-normal shr-opacity-0',\n shadowElement: 'shr-absolute shr-left-0 shr-top-0 shr-w-full',\n },\n variants: {\n maxLines: {\n 1: {\n clampedLine:\n 'shr-inline-block shr-max-w-full shr-overflow-x-clip shr-overflow-ellipsis shr-whitespace-nowrap shr-align-middle',\n },\n 2: {\n clampedLine: 'shr-line-clamp-[2]',\n },\n 3: {\n clampedLine: 'shr-line-clamp-[3]',\n },\n 4: {\n clampedLine: 'shr-line-clamp-[4]',\n },\n 5: {\n clampedLine: 'shr-line-clamp-[5]',\n },\n 6: {\n clampedLine: 'shr-line-clamp-[6]',\n },\n },\n },\n compoundVariants: [\n {\n maxLines: [2, 3, 4, 5, 6],\n className: {\n // baseがdisplay:-webkit-boxでないと高さ取得用の要素が表示部分と同じ大きさで表示されないバグを回避するため\n base: '[display:-webkit-box]',\n },\n },\n ],\n})\n\nexport const LineClamp: FC<Props> = ({ maxLines = 3, children, className, ...rest }) => {\n if (maxLines < 1 || maxLines > 6) {\n throw new Error('\"maxLines\" は 1 ~ 6 の範囲で指定してください')\n }\n\n const [isTooltipVisible, setTooltipVisible] = useState(false)\n const ref = useRef<HTMLSpanElement>(null)\n const shadowRef = useRef<HTMLSpanElement>(null)\n\n useEffect(() => {\n const el = ref.current\n const shadowEl = shadowRef.current\n\n if (!el || !shadowEl) return\n\n // -webkit-line-clamp を使った要素ではel.scrollHeightとel.clientHeightの比較だと\n // フォントの高さの計算が期待と異なり適切な高さが取得できないためshadowElと比較している\n // 参考: https://github.com/kufu/smarthr-ui/pull/4710\n const checkOverflow = () => {\n setTooltipVisible(shadowEl.clientHeight > el.clientHeight)\n }\n\n checkOverflow()\n\n const resizeObserver = new ResizeObserver(checkOverflow)\n resizeObserver.observe(el)\n resizeObserver.observe(shadowEl)\n\n return () => {\n resizeObserver.unobserve(el)\n resizeObserver.unobserve(shadowEl)\n }\n }, [maxLines])\n\n const classNames = useMemo(() => {\n const { base, clampedLine, shadowElementWrapper, shadowElement } = classNameGenerator({\n maxLines,\n })\n\n return {\n base: base({ className }),\n clampedLine: clampedLine(),\n shadowElementWrapper: shadowElementWrapper(),\n shadowElement: shadowElement(),\n }\n }, [maxLines, className])\n\n const actualLineClamp = (\n <span {...rest} className={classNames.base}>\n <span ref={ref} className={classNames.clampedLine}>\n {children}\n </span>\n {/* 切り取られていないテキストの高さを取得するための要素 */}\n <span aria-hidden className={classNames.shadowElementWrapper}>\n <span className={classNames.shadowElement} ref={shadowRef}>\n {children}\n </span>\n </span>\n </span>\n )\n\n return isTooltipVisible ? (\n <Tooltip message={children}>{actualLineClamp}</Tooltip>\n ) : (\n actualLineClamp\n )\n}\n"],"names":[],"mappings":";;;;;;;;;AAkBA;AACE;AACE;AACA;AACA;AAEA;AACD;AACD;AACE;AACE;AACE;AAED;AACD;AACE;AACD;AACD;AACE;AACD;AACD;AACE;AACD;AACD;AACE;AACD;AACD;AACE;AACD;AACF;AACF;AACD;AACE;;AAEE;;AAEE;AACD;AACF;AACF;AACF;AAEM;;AAEH;;;AAIF;AACA;;AAGE;AACA;AAEA;;;;;;;AAOA;AAEA;AAEA;AACA;AACA;AAEA;AACE;AACA;AACF;AACF;AAEA;;;AAGG;;AAGC;;;;;AAKJ;;;AAqBF;;"}
|
|
@@ -54,12 +54,23 @@ const LineClamp = ({ maxLines = 3, children, className, ...rest }) => {
|
|
|
54
54
|
useEffect(() => {
|
|
55
55
|
const el = ref.current;
|
|
56
56
|
const shadowEl = shadowRef.current;
|
|
57
|
+
if (!el || !shadowEl)
|
|
58
|
+
return;
|
|
57
59
|
// -webkit-line-clamp を使った要素ではel.scrollHeightとel.clientHeightの比較だと
|
|
58
60
|
// フォントの高さの計算が期待と異なり適切な高さが取得できないためshadowElと比較している
|
|
59
61
|
// 参考: https://github.com/kufu/smarthr-ui/pull/4710
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
const checkOverflow = () => {
|
|
63
|
+
setTooltipVisible(shadowEl.clientHeight > el.clientHeight);
|
|
64
|
+
};
|
|
65
|
+
checkOverflow();
|
|
66
|
+
const resizeObserver = new ResizeObserver(checkOverflow);
|
|
67
|
+
resizeObserver.observe(el);
|
|
68
|
+
resizeObserver.observe(shadowEl);
|
|
69
|
+
return () => {
|
|
70
|
+
resizeObserver.unobserve(el);
|
|
71
|
+
resizeObserver.unobserve(shadowEl);
|
|
72
|
+
};
|
|
73
|
+
}, [maxLines]);
|
|
63
74
|
const classNames = useMemo(() => {
|
|
64
75
|
const { base, clampedLine, shadowElementWrapper, shadowElement } = classNameGenerator({
|
|
65
76
|
maxLines,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LineClamp.js","sources":["../../../src/components/LineClamp/LineClamp.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentPropsWithRef,\n type FC,\n type PropsWithChildren,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { type VariantProps, tv } from 'tailwind-variants'\n\nimport { Tooltip } from '../Tooltip'\n\ntype AbstractProps = PropsWithChildren<VariantProps<typeof classNameGenerator>>\ntype Props = AbstractProps & Omit<ComponentPropsWithRef<'span'>, keyof AbstractProps>\n\nconst classNameGenerator = tv({\n slots: {\n base: 'smarthr-ui-LineClamp shr-relative',\n clampedLine: 'shr-max-w-full',\n shadowElementWrapper:\n 'shr-invisible shr-absolute shr-left-0 shr-top-0 shr-h-full shr-w-full shr-overflow-hidden shr-whitespace-normal shr-opacity-0',\n shadowElement: 'shr-absolute shr-left-0 shr-top-0 shr-w-full',\n },\n variants: {\n maxLines: {\n 1: {\n clampedLine:\n 'shr-inline-block shr-max-w-full shr-overflow-x-clip shr-overflow-ellipsis shr-whitespace-nowrap shr-align-middle',\n },\n 2: {\n clampedLine: 'shr-line-clamp-[2]',\n },\n 3: {\n clampedLine: 'shr-line-clamp-[3]',\n },\n 4: {\n clampedLine: 'shr-line-clamp-[4]',\n },\n 5: {\n clampedLine: 'shr-line-clamp-[5]',\n },\n 6: {\n clampedLine: 'shr-line-clamp-[6]',\n },\n },\n },\n compoundVariants: [\n {\n maxLines: [2, 3, 4, 5, 6],\n className: {\n // baseがdisplay:-webkit-boxでないと高さ取得用の要素が表示部分と同じ大きさで表示されないバグを回避するため\n base: '[display:-webkit-box]',\n },\n },\n ],\n})\n\nexport const LineClamp: FC<Props> = ({ maxLines = 3, children, className, ...rest }) => {\n if (maxLines < 1 || maxLines > 6) {\n throw new Error('\"maxLines\" は 1 ~ 6 の範囲で指定してください')\n }\n\n const [isTooltipVisible, setTooltipVisible] = useState(false)\n const ref = useRef<HTMLSpanElement>(null)\n const shadowRef = useRef<HTMLSpanElement>(null)\n\n useEffect(() => {\n const el = ref.current\n const shadowEl = shadowRef.current\n\n // -webkit-line-clamp を使った要素ではel.scrollHeightとel.clientHeightの比較だと\n // フォントの高さの計算が期待と異なり適切な高さが取得できないためshadowElと比較している\n // 参考: https://github.com/kufu/smarthr-ui/pull/4710\n const
|
|
1
|
+
{"version":3,"file":"LineClamp.js","sources":["../../../src/components/LineClamp/LineClamp.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentPropsWithRef,\n type FC,\n type PropsWithChildren,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { type VariantProps, tv } from 'tailwind-variants'\n\nimport { Tooltip } from '../Tooltip'\n\ntype AbstractProps = PropsWithChildren<VariantProps<typeof classNameGenerator>>\ntype Props = AbstractProps & Omit<ComponentPropsWithRef<'span'>, keyof AbstractProps>\n\nconst classNameGenerator = tv({\n slots: {\n base: 'smarthr-ui-LineClamp shr-relative',\n clampedLine: 'shr-max-w-full',\n shadowElementWrapper:\n 'shr-invisible shr-absolute shr-left-0 shr-top-0 shr-h-full shr-w-full shr-overflow-hidden shr-whitespace-normal shr-opacity-0',\n shadowElement: 'shr-absolute shr-left-0 shr-top-0 shr-w-full',\n },\n variants: {\n maxLines: {\n 1: {\n clampedLine:\n 'shr-inline-block shr-max-w-full shr-overflow-x-clip shr-overflow-ellipsis shr-whitespace-nowrap shr-align-middle',\n },\n 2: {\n clampedLine: 'shr-line-clamp-[2]',\n },\n 3: {\n clampedLine: 'shr-line-clamp-[3]',\n },\n 4: {\n clampedLine: 'shr-line-clamp-[4]',\n },\n 5: {\n clampedLine: 'shr-line-clamp-[5]',\n },\n 6: {\n clampedLine: 'shr-line-clamp-[6]',\n },\n },\n },\n compoundVariants: [\n {\n maxLines: [2, 3, 4, 5, 6],\n className: {\n // baseがdisplay:-webkit-boxでないと高さ取得用の要素が表示部分と同じ大きさで表示されないバグを回避するため\n base: '[display:-webkit-box]',\n },\n },\n ],\n})\n\nexport const LineClamp: FC<Props> = ({ maxLines = 3, children, className, ...rest }) => {\n if (maxLines < 1 || maxLines > 6) {\n throw new Error('\"maxLines\" は 1 ~ 6 の範囲で指定してください')\n }\n\n const [isTooltipVisible, setTooltipVisible] = useState(false)\n const ref = useRef<HTMLSpanElement>(null)\n const shadowRef = useRef<HTMLSpanElement>(null)\n\n useEffect(() => {\n const el = ref.current\n const shadowEl = shadowRef.current\n\n if (!el || !shadowEl) return\n\n // -webkit-line-clamp を使った要素ではel.scrollHeightとel.clientHeightの比較だと\n // フォントの高さの計算が期待と異なり適切な高さが取得できないためshadowElと比較している\n // 参考: https://github.com/kufu/smarthr-ui/pull/4710\n const checkOverflow = () => {\n setTooltipVisible(shadowEl.clientHeight > el.clientHeight)\n }\n\n checkOverflow()\n\n const resizeObserver = new ResizeObserver(checkOverflow)\n resizeObserver.observe(el)\n resizeObserver.observe(shadowEl)\n\n return () => {\n resizeObserver.unobserve(el)\n resizeObserver.unobserve(shadowEl)\n }\n }, [maxLines])\n\n const classNames = useMemo(() => {\n const { base, clampedLine, shadowElementWrapper, shadowElement } = classNameGenerator({\n maxLines,\n })\n\n return {\n base: base({ className }),\n clampedLine: clampedLine(),\n shadowElementWrapper: shadowElementWrapper(),\n shadowElement: shadowElement(),\n }\n }, [maxLines, className])\n\n const actualLineClamp = (\n <span {...rest} className={classNames.base}>\n <span ref={ref} className={classNames.clampedLine}>\n {children}\n </span>\n {/* 切り取られていないテキストの高さを取得するための要素 */}\n <span aria-hidden className={classNames.shadowElementWrapper}>\n <span className={classNames.shadowElement} ref={shadowRef}>\n {children}\n </span>\n </span>\n </span>\n )\n\n return isTooltipVisible ? (\n <Tooltip message={children}>{actualLineClamp}</Tooltip>\n ) : (\n actualLineClamp\n )\n}\n"],"names":[],"mappings":";;;;;;;AAkBA;AACE;AACE;AACA;AACA;AAEA;AACD;AACD;AACE;AACE;AACE;AAED;AACD;AACE;AACD;AACD;AACE;AACD;AACD;AACE;AACD;AACD;AACE;AACD;AACD;AACE;AACD;AACF;AACF;AACD;AACE;;AAEE;;AAEE;AACD;AACF;AACF;AACF;AAEM;;AAEH;;;AAIF;AACA;;AAGE;AACA;AAEA;;;;;;;AAOA;AAEA;AAEA;AACA;AACA;AAEA;AACE;AACA;AACF;AACF;AAEA;;;AAGG;;AAGC;;;;;AAKJ;;;AAqBF;;"}
|
|
@@ -49,16 +49,6 @@ const classNameGenerator = index.tv({
|
|
|
49
49
|
},
|
|
50
50
|
});
|
|
51
51
|
const ActualSelect = ({ options, onChange, onChangeValue, error, width, hasBlank, blankLabel, size, className, disabled, required, ...rest }, ref) => {
|
|
52
|
-
const handleChange = React.useCallback((e) => {
|
|
53
|
-
onChange?.(e);
|
|
54
|
-
if (onChangeValue) {
|
|
55
|
-
const flattenOptions = options.reduce((pre, cur) => pre.concat('value' in cur ? cur : cur.options), []);
|
|
56
|
-
const selectedOption = flattenOptions.find((option) => option.value === e.target.value);
|
|
57
|
-
if (selectedOption) {
|
|
58
|
-
onChangeValue(selectedOption.value);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}, [onChange, onChangeValue, options]);
|
|
62
52
|
const classNames = React.useMemo(() => {
|
|
63
53
|
const { wrapper, select, iconWrap, blankOptgroup } = classNameGenerator();
|
|
64
54
|
const sizeProps = {
|
|
@@ -75,7 +65,17 @@ const ActualSelect = ({ options, onChange, onChangeValue, error, width, hasBlank
|
|
|
75
65
|
width: typeof width === 'number' ? `${width}px` : width,
|
|
76
66
|
}), [width]);
|
|
77
67
|
const actualBlankLabel = blankLabel ?? '';
|
|
78
|
-
return (jsxRuntime.jsxs("span", { className: classNames.wrapper, style: wrapperStyle, children: [jsxRuntime.jsxs("select", { ...rest, "data-smarthr-ui-input": "true", onChange:
|
|
68
|
+
return (jsxRuntime.jsxs("span", { className: classNames.wrapper, style: wrapperStyle, children: [jsxRuntime.jsxs("select", { ...rest, "data-smarthr-ui-input": "true", onChange: (e) => {
|
|
69
|
+
onChange?.(e);
|
|
70
|
+
if (onChangeValue) {
|
|
71
|
+
const selectedOption = options
|
|
72
|
+
.flatMap((option) => ('value' in option ? [option] : option.options))
|
|
73
|
+
.find((option) => option.value === e.target.value);
|
|
74
|
+
if (selectedOption) {
|
|
75
|
+
onChangeValue(selectedOption.value);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}, "aria-invalid": error || undefined, disabled: disabled,
|
|
79
79
|
// HINT: required属性を設定すると、iOS端末で以下の問題が発生します
|
|
80
80
|
// - フォームのsubmit時にバリデーションは行われるが、ユーザーにフィードバックがない
|
|
81
81
|
// - エラーメッセージが表示されない
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Select.cjs","sources":["../../../src/components/Select/Select.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type ComponentPropsWithoutRef,\n type ForwardedRef,\n type OptgroupHTMLAttributes,\n type OptionHTMLAttributes,\n type PropsWithChildren,\n memo,\n
|
|
1
|
+
{"version":3,"file":"Select.cjs","sources":["../../../src/components/Select/Select.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type ComponentPropsWithoutRef,\n type ForwardedRef,\n type OptgroupHTMLAttributes,\n type OptionHTMLAttributes,\n type PropsWithChildren,\n memo,\n useMemo,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { isIOS, isMobileSafari } from '../../libs/ua'\nimport { genericsForwardRef } from '../../libs/util'\nimport { FaAngleDownIcon } from '../Icon'\n\ntype Option<T extends string> = {\n value: T\n} & Omit<OptionHTMLAttributes<HTMLOptionElement>, 'value'>\ntype Optgroup<T extends string> = {\n label: string\n options: Array<Option<T>>\n} & OptgroupHTMLAttributes<HTMLOptGroupElement>\n\ntype AbstractProps<T extends string> = {\n /** 選択肢のデータの配列 */\n options: Array<Option<T> | Optgroup<T>>\n /** フォームの値が変わったときに発火するコールバック関数 */\n onChangeValue?: (value: T) => void\n /** フォームの値にエラーがあるかどうか */\n error?: boolean\n /** コンポーネントの幅 */\n width?: number | string\n /** コンポーネントの大きさ */\n size?: 'M' | 'S'\n /** 空の選択肢を表示するかどうか */\n hasBlank?: boolean\n /** 空の選択肢のラベル */\n blankLabel?: string\n}\n\ntype Props<T extends string> = AbstractProps<T> &\n Omit<ComponentPropsWithoutRef<'select'>, keyof AbstractProps<string> | 'children'>\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: 'smarthr-ui-Select shr-relative shr-inline-block',\n select: [\n 'shr-peer shr-border-shorthand shr-w-full shr-cursor-pointer shr-appearance-none shr-rounded-m shr-bg-white shr-text-base shr-leading-tight shr-text-black',\n 'hover:shr-bg-white-darken',\n 'focus-visible:shr-focus-indicator',\n 'disabled:shr-pointer-events-none disabled:shr-bg-white-darken disabled:shr-text-disabled disabled:shr-opacity-100',\n 'contrast-more:shr-border-high-contrast',\n /* padding に依る積み上げでは文字が見切れてしまうため */\n 'shr-min-h-[calc(theme(fontSize.base)_+_theme(spacing[0.75])_*_2_+_theme(spacing.px)_*_2)]',\n 'shr-border-default disabled:shr-border-disabled',\n 'aria-[invalid]:shr-border-danger',\n ],\n iconWrap: [\n 'shr-pointer-events-none shr-absolute shr-inset-y-0 shr-inline-flex shr-items-center shr-text-grey',\n 'peer-focus-visible:shr-text-black peer-disabled:shr-text-disabled',\n ],\n blankOptgroup: 'shr-hidden',\n },\n variants: {\n size: {\n M: {\n select: 'shr-py-0.5 shr-pe-2 shr-ps-0.5',\n // ((右 padding - アイコン幅) / 2) + 右 border\n iconWrap: 'shr-end-[calc(theme(spacing[0.5])_+_theme(spacing.px))]',\n },\n S: {\n select: [\n 'shr-px-0.5 shr-py-0.25 shr-pe-1.5 shr-text-sm',\n /* padding に依る積み上げでは文字が見切れてしまうため */\n 'shr-min-h-[calc(theme(fontSize.sm)_+_theme(spacing[0.5])_*_2_+_theme(spacing.px)_*_2)]',\n ],\n iconWrap: 'shr-end-0.5 shr-text-sm',\n },\n },\n },\n})\n\nconst ActualSelect = <T extends string>(\n {\n options,\n onChange,\n onChangeValue,\n error,\n width,\n hasBlank,\n blankLabel,\n size,\n className,\n disabled,\n required,\n ...rest\n }: Props<T>,\n ref: ForwardedRef<HTMLSelectElement>,\n) => {\n const classNames = useMemo(() => {\n const { wrapper, select, iconWrap, blankOptgroup } = classNameGenerator()\n const sizeProps = {\n size: size || 'M',\n }\n\n return {\n wrapper: wrapper({ className }),\n select: select(sizeProps),\n iconWrap: iconWrap(sizeProps),\n blankOptGroup: blankOptgroup(),\n }\n }, [className, size])\n const wrapperStyle = useMemo(\n () => ({\n width: typeof width === 'number' ? `${width}px` : width,\n }),\n [width],\n )\n\n const actualBlankLabel = blankLabel ?? ''\n\n return (\n <span className={classNames.wrapper} style={wrapperStyle}>\n <select\n {...rest}\n data-smarthr-ui-input=\"true\"\n onChange={(e: ChangeEvent<HTMLSelectElement>) => {\n onChange?.(e)\n\n if (onChangeValue) {\n const selectedOption = options\n .flatMap((option) => ('value' in option ? [option] : option.options))\n .find((option) => option.value === e.target.value)\n\n if (selectedOption) {\n onChangeValue(selectedOption.value)\n }\n }\n }}\n aria-invalid={error || undefined}\n disabled={disabled}\n // HINT: required属性を設定すると、iOS端末で以下の問題が発生します\n // - フォームのsubmit時にバリデーションは行われるが、ユーザーにフィードバックがない\n // - エラーメッセージが表示されない\n // - 問題のある入力フィールドまでスクロールしない\n // 歴史的に一部の端末ではrequired属性が無視されることがあるため、HTMLのバリデーションのみとすることは少ないです\n // そのため、iOS端末ではrequired属性を設定しない方がユーザーがsubmitできない理由をエラーメッセージなどで正しく理解できるようになります\n required={isIOS ? undefined : required}\n ref={ref}\n className={classNames.select}\n >\n <BlankOption hasBlank={hasBlank}>{actualBlankLabel}</BlankOption>\n {options.map((option, index) => (\n <Option {...option} key={index} />\n ))}\n <NotOmittingLabelsInMobileSafari className={classNames.blankOptGroup} />\n </select>\n <span className={classNames.iconWrap}>\n <FaAngleDownIcon />\n </span>\n </span>\n )\n}\n\nconst BlankOption = memo<\n PropsWithChildren<{\n hasBlank: boolean | undefined\n }>\n>(({ hasBlank, children }) => hasBlank && <option value=\"\">{children}</option>)\n\nconst Option = memo<Props<string>['options'][number]>((option) => {\n if ('value' in option) {\n return <option {...option}>{option.label}</option>\n }\n\n const { options: groupedOptions, label, ...rest } = option\n\n return (\n <optgroup {...rest} key={label} label={label}>\n {groupedOptions.map((groupedOption) => (\n <option {...groupedOption} key={groupedOption.value}>\n {groupedOption.label}\n </option>\n ))}\n </optgroup>\n )\n})\n\n// Support for not omitting labels in Mobile Safari\nconst NotOmittingLabelsInMobileSafari = memo<{ className: string }>(\n ({ className }) => isMobileSafari && <optgroup className={className} />,\n)\n\nexport const Select = genericsForwardRef(ActualSelect)\n"],"names":[],"mappings":";;;;;;;;;;;;AA8CA;AACE;AACE;AACA;;;;;;;;;;AAUC;AACD;;;AAGC;AACD;AACD;AACD;AACE;AACE;AACE;;AAEA;AACD;AACD;AACE;;;;AAIC;AACD;AACD;AACF;AACF;AACF;AAED;AAiBE;AACE;AACA;;;;AAKE;AACA;AACA;;;AAGJ;AACA;AAEI;AACD;AAIH;;AAQQ;;;;AAKK;;AAGD;;;AAGN;;;;;;;AASA;AAeR;AAEA;AAMA;AACE;AACE;;AAGF;AAEA;AASF;AAEA;AACA;;;"}
|