smarthr-ui 96.0.0 → 96.1.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.
Files changed (52) hide show
  1. package/lib/components/Icon/FaIcon.cjs +1 -1
  2. package/lib/components/Icon/FaIcon.js +1 -1
  3. package/lib/components/Icon/LanguageIcon.cjs +1 -1
  4. package/lib/components/Icon/LanguageIcon.js +1 -1
  5. package/lib/components/Icon/OpenInNewTabIcon.cjs +1 -1
  6. package/lib/components/Icon/OpenInNewTabIcon.js +1 -1
  7. package/lib/components/Icon/SparklesIcon.cjs +1 -1
  8. package/lib/components/Icon/SparklesIcon.js +1 -1
  9. package/lib/components/Icon/WarningIcon.cjs +1 -1
  10. package/lib/components/Icon/WarningIcon.js +1 -1
  11. package/lib/components/Icon/generateIcon.cjs +0 -1
  12. package/lib/components/Icon/generateIcon.cjs.map +1 -1
  13. package/lib/components/Icon/generateIcon.js +0 -1
  14. package/lib/components/Icon/generateIcon.js.map +1 -1
  15. package/lib/components/Input/InputWithTooltip/InputWithTooltip.cjs +1 -1
  16. package/lib/components/Input/InputWithTooltip/InputWithTooltip.cjs.map +1 -1
  17. package/lib/components/Input/InputWithTooltip/InputWithTooltip.js +1 -1
  18. package/lib/components/Input/InputWithTooltip/InputWithTooltip.js.map +1 -1
  19. package/lib/components/LineClamp/LineClamp.cjs +3 -5
  20. package/lib/components/LineClamp/LineClamp.cjs.map +1 -1
  21. package/lib/components/LineClamp/LineClamp.js +3 -5
  22. package/lib/components/LineClamp/LineClamp.js.map +1 -1
  23. package/lib/components/SideNav/SideNavItemButton.cjs.map +1 -1
  24. package/lib/components/SideNav/SideNavItemButton.d.ts +11 -10
  25. package/lib/components/SideNav/SideNavItemButton.js.map +1 -1
  26. package/lib/components/TabBar/TabItem.cjs +1 -1
  27. package/lib/components/TabBar/TabItem.cjs.map +1 -1
  28. package/lib/components/TabBar/TabItem.js +1 -1
  29. package/lib/components/TabBar/TabItem.js.map +1 -1
  30. package/lib/components/Tooltip/Tooltip.cjs +20 -6
  31. package/lib/components/Tooltip/Tooltip.cjs.map +1 -1
  32. package/lib/components/Tooltip/Tooltip.d.ts +4 -2
  33. package/lib/components/Tooltip/Tooltip.js +20 -6
  34. package/lib/components/Tooltip/Tooltip.js.map +1 -1
  35. package/lib/vendor/.pnpm/{react-icons@5.5.0_react@19.2.6 → react-icons@5.6.0_react@19.2.6}/vendor/react-icons/fa6/index.cjs +404 -404
  36. package/lib/vendor/.pnpm/react-icons@5.6.0_react@19.2.6/vendor/react-icons/fa6/index.cjs.map +1 -0
  37. package/lib/vendor/.pnpm/{react-icons@5.5.0_react@19.2.6 → react-icons@5.6.0_react@19.2.6}/vendor/react-icons/fa6/index.js +404 -404
  38. package/lib/vendor/.pnpm/react-icons@5.6.0_react@19.2.6/vendor/react-icons/fa6/index.js.map +1 -0
  39. package/lib/vendor/.pnpm/{react-icons@5.5.0_react@19.2.6 → react-icons@5.6.0_react@19.2.6}/vendor/react-icons/lib/iconBase.cjs +4 -4
  40. package/lib/vendor/.pnpm/react-icons@5.6.0_react@19.2.6/vendor/react-icons/lib/iconBase.cjs.map +1 -0
  41. package/lib/vendor/.pnpm/{react-icons@5.5.0_react@19.2.6 → react-icons@5.6.0_react@19.2.6}/vendor/react-icons/lib/iconBase.js +4 -4
  42. package/lib/vendor/.pnpm/react-icons@5.6.0_react@19.2.6/vendor/react-icons/lib/iconBase.js.map +1 -0
  43. package/lib/vendor/.pnpm/{react-icons@5.5.0_react@19.2.6 → react-icons@5.6.0_react@19.2.6}/vendor/react-icons/lib/iconContext.cjs.map +1 -1
  44. package/lib/vendor/.pnpm/{react-icons@5.5.0_react@19.2.6 → react-icons@5.6.0_react@19.2.6}/vendor/react-icons/lib/iconContext.js.map +1 -1
  45. package/metadata.json +1 -1
  46. package/package.json +4 -4
  47. package/lib/vendor/.pnpm/react-icons@5.5.0_react@19.2.6/vendor/react-icons/fa6/index.cjs.map +0 -1
  48. package/lib/vendor/.pnpm/react-icons@5.5.0_react@19.2.6/vendor/react-icons/fa6/index.js.map +0 -1
  49. package/lib/vendor/.pnpm/react-icons@5.5.0_react@19.2.6/vendor/react-icons/lib/iconBase.cjs.map +0 -1
  50. package/lib/vendor/.pnpm/react-icons@5.5.0_react@19.2.6/vendor/react-icons/lib/iconBase.js.map +0 -1
  51. /package/lib/vendor/.pnpm/{react-icons@5.5.0_react@19.2.6 → react-icons@5.6.0_react@19.2.6}/vendor/react-icons/lib/iconContext.cjs +0 -0
  52. /package/lib/vendor/.pnpm/{react-icons@5.5.0_react@19.2.6 → react-icons@5.6.0_react@19.2.6}/vendor/react-icons/lib/iconContext.js +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"TabItem.js","sources":["../../../src/components/TabBar/TabItem.tsx"],"sourcesContent":["import {\n type ComponentProps,\n type FC,\n type MouseEvent,\n type PropsWithChildren,\n type ReactNode,\n memo,\n useCallback,\n useMemo,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { UnstyledButton } from '../Button'\nimport { FaCircleInfoIcon } from '../Icon'\nimport { Tooltip } from '../Tooltip'\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: [\n 'smarthr-ui-TabItem',\n 'shr-group/tabitem',\n 'shr-relative shr-inline-flex shr-items-center shr-gap-0.5 shr-px-1 shr-py-0.75',\n 'hover:shr-bg-white-darken',\n 'focus-visible:shr-focus-indicator focus-visible:shr-z-1',\n 'disabled:shr-cursor-not-allowed disabled:shr-bg-transparent',\n 'aria-selected:before:shr-absolute aria-selected:before:shr-inset-x-0 aria-selected:before:shr-bottom-0 aria-selected:before:shr-z-1 aria-selected:before:shr-block aria-selected:before:shr-h-0.25 aria-selected:before:shr-bg-main aria-selected:before:shr-content-[\"\"]',\n 'forced-colors:aria-selected:before:shr-bg-[Highlight]',\n ],\n label: [\n 'shr-font-bold shr-leading-none shr-text-grey',\n 'group-hover/tabitem:shr-text-black',\n 'group-disabled/tabitem:shr-text-grey/50',\n 'group-aria-selected/tabitem:shr-text-black',\n ],\n suffixWrapper: [\n // Badge など内包要素に依って高さが変わらないようにするため、ネガティブマージンを指定\n '-shr-my-0.25',\n // 内包アイコンの leading を詰める\n '[&_.smarthr-ui-Icon]:shr-block',\n ],\n },\n})\n\ntype AbstractProps = PropsWithChildren<{\n /** タブの ID */\n id: string\n /** ボタン内の末尾に表示する内容 */\n suffix?: ReactNode\n /** `true` のとき、タブが選択状態のスタイルになる */\n selected?: boolean\n /** `true` のとき、タブを無効状態にしてクリック不能にする */\n disabled?: boolean\n /**\n * 無効な理由\n */\n disabledReason?: {\n icon?: ReactNode\n message: ReactNode\n }\n /** タブをクリックした時に発火するコールバック関数 */\n onClick: (tabId: string) => void\n}>\ntype Props = AbstractProps &\n Omit<ComponentProps<typeof UnstyledButton>, keyof AbstractProps | 'aria-selected' | 'type'>\n\nexport const TabItem: FC<Props> = ({ selected = false, disabled, disabledReason, ...rest }) => {\n const tabAttrs = {\n role: 'tab',\n 'aria-selected': selected,\n }\n\n if (disabled && disabledReason) {\n const Icon = disabledReason.icon || <FaCircleInfoIcon color=\"TEXT_GREY\" />\n\n return (\n <Tooltip\n {...tabAttrs}\n message={disabledReason.message}\n ariaDescribedbyTarget=\"inner\"\n aria-disabled={disabled}\n className=\"focus-visible:shr-focus-indicator\"\n >\n <TabButton {...rest} disabled={disabled} suffix={Icon} />\n </Tooltip>\n )\n }\n\n return <TabButton {...rest} {...tabAttrs} disabled={disabled} />\n}\n\nconst TabButton: FC<Props> = ({ id, children, suffix, onClick, className, ...rest }) => {\n const classNames = useMemo(() => {\n const { wrapper, label, suffixWrapper } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n label: label(),\n suffixWrapper: suffixWrapper(),\n }\n }, [className])\n\n const actualOnClick = useCallback(\n (e: MouseEvent<HTMLButtonElement>) => onClick(e.currentTarget.value),\n [onClick],\n )\n\n return (\n <UnstyledButton\n {...rest}\n type=\"button\"\n value={id}\n id={id}\n className={classNames.wrapper}\n onClick={actualOnClick}\n >\n <TabLabel className={classNames.label}>{children}</TabLabel>\n <TabButtonSuffix className={classNames.suffixWrapper}>{suffix}</TabButtonSuffix>\n </UnstyledButton>\n )\n}\n\nconst TabLabel = memo<PropsWithChildren<{ className: string }>>(({ children, className }) => (\n <span className={className}>{children}</span>\n))\nconst TabButtonSuffix = memo<PropsWithChildren<{ className: string }>>(\n ({ children, className }) => children && <span className={className}>{children}</span>,\n)\n"],"names":["tv","_jsx","_jsxs"],"mappings":";;;;;;;;;;;;AAgBA,MAAM,kBAAkB,GAAGA,EAAE,CAAC;AAC5B,IAAA,KAAK,EAAE;AACL,QAAA,OAAO,EAAE;YACP,oBAAoB;YACpB,mBAAmB;YACnB,gFAAgF;YAChF,2BAA2B;YAC3B,yDAAyD;YACzD,6DAA6D;YAC7D,2QAA2Q;YAC3Q,uDAAuD;AACxD,SAAA;AACD,QAAA,KAAK,EAAE;YACL,8CAA8C;YAC9C,oCAAoC;YACpC,yCAAyC;YACzC,4CAA4C;AAC7C,SAAA;AACD,QAAA,aAAa,EAAE;;YAEb,cAAc;;YAEd,gCAAgC;AACjC,SAAA;AACF,KAAA;AACF,CAAA,CAAC;AAwBK,MAAM,OAAO,GAAc,CAAC,EAAE,QAAQ,GAAG,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,GAAG,IAAI,EAAE,KAAI;AAC5F,IAAA,MAAM,QAAQ,GAAG;AACf,QAAA,IAAI,EAAE,KAAK;AACX,QAAA,eAAe,EAAE,QAAQ;KAC1B;AAED,IAAA,IAAI,QAAQ,IAAI,cAAc,EAAE;AAC9B,QAAA,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,IAAIC,GAAA,CAAC,gBAAgB,EAAA,EAAC,KAAK,EAAC,WAAW,GAAG;AAE1E,QAAA,QACEA,GAAA,CAAC,OAAO,EAAA,EAAA,GACF,QAAQ,EACZ,OAAO,EAAE,cAAc,CAAC,OAAO,EAC/B,qBAAqB,EAAC,OAAO,mBACd,QAAQ,EACvB,SAAS,EAAC,mCAAmC,EAAA,QAAA,EAE7CA,GAAA,CAAC,SAAS,EAAA,EAAA,GAAK,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAA,CAAI,EAAA,CACjD;IAEd;IAEA,OAAOA,GAAA,CAAC,SAAS,EAAA,EAAA,GAAK,IAAI,EAAA,GAAM,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAA,CAAI;AAClE;AAEA,MAAM,SAAS,GAAc,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,KAAI;AACrF,IAAA,MAAM,UAAU,GAAG,OAAO,CAAC,MAAK;QAC9B,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,kBAAkB,EAAE;QAE9D,OAAO;AACL,YAAA,OAAO,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;YAC/B,KAAK,EAAE,KAAK,EAAE;YACd,aAAa,EAAE,aAAa,EAAE;SAC/B;AACH,IAAA,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC;IAEf,MAAM,aAAa,GAAG,WAAW,CAC/B,CAAC,CAAgC,KAAK,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,EACpE,CAAC,OAAO,CAAC,CACV;IAED,QACEC,IAAA,CAAC,cAAc,EAAA,EAAA,GACT,IAAI,EACR,IAAI,EAAC,QAAQ,EACb,KAAK,EAAE,EAAE,EACT,EAAE,EAAE,EAAE,EACN,SAAS,EAAE,UAAU,CAAC,OAAO,EAC7B,OAAO,EAAE,aAAa,EAAA,QAAA,EAAA,CAEtBD,GAAA,CAAC,QAAQ,EAAA,EAAC,SAAS,EAAE,UAAU,CAAC,KAAK,EAAA,QAAA,EAAG,QAAQ,EAAA,CAAY,EAC5DA,IAAC,eAAe,EAAA,EAAC,SAAS,EAAE,UAAU,CAAC,aAAa,EAAA,QAAA,EAAG,MAAM,EAAA,CAAmB,CAAA,EAAA,CACjE;AAErB,CAAC;AAED,MAAM,QAAQ,GAAG,IAAI,CAA2C,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,MACtFA,GAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAE,SAAS,YAAG,QAAQ,EAAA,CAAQ,CAC9C,CAAC;AACF,MAAM,eAAe,GAAG,IAAI,CAC1B,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,QAAQ,IAAIA,GAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAE,SAAS,EAAA,QAAA,EAAG,QAAQ,EAAA,CAAQ,CACvF;;;;"}
1
+ {"version":3,"file":"TabItem.js","sources":["../../../src/components/TabBar/TabItem.tsx"],"sourcesContent":["import {\n type ComponentProps,\n type FC,\n type MouseEvent,\n type PropsWithChildren,\n type ReactNode,\n memo,\n useCallback,\n useMemo,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { UnstyledButton } from '../Button'\nimport { FaCircleInfoIcon } from '../Icon'\nimport { Tooltip } from '../Tooltip'\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: [\n 'smarthr-ui-TabItem',\n 'shr-group/tabitem',\n 'shr-relative shr-inline-flex shr-items-center shr-gap-0.5 shr-px-1 shr-py-0.75',\n 'hover:shr-bg-white-darken',\n 'focus-visible:shr-focus-indicator focus-visible:shr-z-1',\n 'disabled:shr-cursor-not-allowed disabled:shr-bg-transparent',\n 'aria-selected:before:shr-absolute aria-selected:before:shr-inset-x-0 aria-selected:before:shr-bottom-0 aria-selected:before:shr-z-1 aria-selected:before:shr-block aria-selected:before:shr-h-0.25 aria-selected:before:shr-bg-main aria-selected:before:shr-content-[\"\"]',\n 'forced-colors:aria-selected:before:shr-bg-[Highlight]',\n ],\n label: [\n 'shr-font-bold shr-leading-none shr-text-grey',\n 'group-hover/tabitem:shr-text-black',\n 'group-disabled/tabitem:shr-text-grey/50',\n 'group-aria-selected/tabitem:shr-text-black',\n ],\n suffixWrapper: [\n // Badge など内包要素に依って高さが変わらないようにするため、ネガティブマージンを指定\n '-shr-my-0.25',\n // 内包アイコンの leading を詰める\n '[&_.smarthr-ui-Icon]:shr-block',\n ],\n },\n})\n\ntype AbstractProps = PropsWithChildren<{\n /** タブの ID */\n id: string\n /** ボタン内の末尾に表示する内容 */\n suffix?: ReactNode\n /** `true` のとき、タブが選択状態のスタイルになる */\n selected?: boolean\n /** `true` のとき、タブを無効状態にしてクリック不能にする */\n disabled?: boolean\n /**\n * 無効な理由\n */\n disabledReason?: {\n icon?: ReactNode\n message: ReactNode\n }\n /** タブをクリックした時に発火するコールバック関数 */\n onClick: (tabId: string) => void\n}>\ntype Props = AbstractProps &\n Omit<ComponentProps<typeof UnstyledButton>, keyof AbstractProps | 'aria-selected' | 'type'>\n\nexport const TabItem: FC<Props> = ({ selected = false, disabled, disabledReason, ...rest }) => {\n const tabAttrs = {\n role: 'tab',\n 'aria-selected': selected,\n }\n\n if (disabled && disabledReason) {\n const Icon = disabledReason.icon || <FaCircleInfoIcon color=\"TEXT_GREY\" />\n\n return (\n <Tooltip\n {...tabAttrs}\n message={disabledReason.message}\n aria-disabled={disabled}\n className=\"focus-visible:shr-focus-indicator\"\n >\n <TabButton {...rest} disabled={disabled} suffix={Icon} />\n </Tooltip>\n )\n }\n\n return <TabButton {...rest} {...tabAttrs} disabled={disabled} />\n}\n\nconst TabButton: FC<Props> = ({ id, children, suffix, onClick, className, ...rest }) => {\n const classNames = useMemo(() => {\n const { wrapper, label, suffixWrapper } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n label: label(),\n suffixWrapper: suffixWrapper(),\n }\n }, [className])\n\n const actualOnClick = useCallback(\n (e: MouseEvent<HTMLButtonElement>) => onClick(e.currentTarget.value),\n [onClick],\n )\n\n return (\n <UnstyledButton\n {...rest}\n type=\"button\"\n value={id}\n id={id}\n className={classNames.wrapper}\n onClick={actualOnClick}\n >\n <TabLabel className={classNames.label}>{children}</TabLabel>\n <TabButtonSuffix className={classNames.suffixWrapper}>{suffix}</TabButtonSuffix>\n </UnstyledButton>\n )\n}\n\nconst TabLabel = memo<PropsWithChildren<{ className: string }>>(({ children, className }) => (\n <span className={className}>{children}</span>\n))\nconst TabButtonSuffix = memo<PropsWithChildren<{ className: string }>>(\n ({ children, className }) => children && <span className={className}>{children}</span>,\n)\n"],"names":["tv","_jsx","_jsxs"],"mappings":"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"}
@@ -31,16 +31,26 @@ const classNameGenerator = index.tv({
31
31
  },
32
32
  },
33
33
  });
34
- const Tooltip = ({ message, children, triggerType, ellipsisOnly, tabIndex = 0, ariaDescribedbyTarget = 'wrapper', className, onPointerEnter, onPointerLeave, onTouchStart, onTouchEnd, onFocus, onBlur, ...rest }) => {
34
+ const Tooltip = ({ message, children, type = 'description', triggerType, ellipsisOnly, tabIndex, ariaDescribedbyTarget = 'wrapper', className, onPointerEnter, onPointerLeave, onTouchStart, onTouchEnd, onFocus, onBlur, ...rest }) => {
35
35
  const [portalRoot, setPortalRoot] = React.useState(null);
36
36
  const [isVisible, setIsVisible] = React.useState(false);
37
37
  const [rect, setRect] = React.useState(null);
38
38
  const ref = React.useRef(null);
39
39
  const messageId = React.useId();
40
40
  const fullscreenElement = React.useSyncExternalStore(subscribeFullscreenChange, getFullscreenElement, getFullscreenElementOnSSR);
41
+ const [isFocusableChild, setIsFocusableChild] = React.useState(false);
42
+ const [actualTabIndex, setActualTabIndex] = React.useState(tabIndex ?? 0);
41
43
  hooks_useEnhancedEffect.useEnhancedEffect(() => {
42
44
  setPortalRoot(fullscreenElement ?? document.body);
43
45
  }, [fullscreenElement]);
46
+ hooks_useEnhancedEffect.useEnhancedEffect(() => {
47
+ const childElement = ref.current?.querySelector(':scope > :not(.smarthr-ui-VisuallyHiddenText)');
48
+ const focusable = !!childElement &&
49
+ childElement.matches('a[href], button, input, select, textarea, [tabindex]') &&
50
+ !childElement.matches('[tabindex="-1"]');
51
+ setIsFocusableChild(focusable);
52
+ setActualTabIndex(tabIndex !== undefined ? tabIndex : focusable ? undefined : 0);
53
+ }, [tabIndex]);
44
54
  const toShowAction = React.useCallback((e) => {
45
55
  // Tooltipのtriggerの他の要素(Dropwdown menu buttonで開いたmenu contentとか)に移動されたらtooltipを表示しない
46
56
  if (!ref.current?.contains(e.target)) {
@@ -96,13 +106,17 @@ const Tooltip = ({ message, children, triggerType, ellipsisOnly, tabIndex = 0, a
96
106
  : toCloseAction, [onBlur, toCloseAction]);
97
107
  const isIcon = triggerType === 'icon';
98
108
  const actualClassName = React.useMemo(() => classNameGenerator({ isIcon, className }), [isIcon, className]);
99
- const isInnerTarget = ariaDescribedbyTarget === 'inner';
100
- const childrenWithProps = React.useMemo(() => isInnerTarget
101
- ? React.cloneElement(children, { 'aria-describedby': messageId })
102
- : children, [children, isInnerTarget, messageId]);
109
+ const isLabel = type === 'label';
110
+ const isInnerTarget = isLabel || isFocusableChild || ariaDescribedbyTarget === 'inner';
111
+ const childrenWithProps = React.useMemo(() => isLabel
112
+ ? React.cloneElement(children, { 'aria-labelledby': messageId })
113
+ : isInnerTarget
114
+ ? React.cloneElement(children, { 'aria-describedby': messageId })
115
+ : children, [children, isLabel, isInnerTarget, messageId]);
116
+ const actualWrapperAriaDescribedby = React.useMemo(() => (!isInnerTarget ? messageId : undefined), [isInnerTarget, messageId]);
103
117
  return (
104
118
  // eslint-disable-next-line jsx-a11y/no-static-element-interactions
105
- jsxRuntime.jsxs("span", { ...rest, ref: ref, tabIndex: tabIndex, "aria-describedby": isInnerTarget ? undefined : messageId, onPointerEnter: onDelegatePointerEnter, onTouchStart: onDelegateTouchStart, onFocus: onDelegateFocus, onPointerLeave: onDelegatePointerLeave, onTouchEnd: onDelegateTouchEnd, onBlur: onDelegateBlur, className: actualClassName, children: [portalRoot &&
119
+ jsxRuntime.jsxs("span", { ...rest, ref: ref, tabIndex: actualTabIndex, "aria-describedby": actualWrapperAriaDescribedby, onPointerEnter: onDelegatePointerEnter, onTouchStart: onDelegateTouchStart, onFocus: onDelegateFocus, onPointerLeave: onDelegatePointerLeave, onTouchEnd: onDelegateTouchEnd, onBlur: onDelegateBlur, className: actualClassName, children: [portalRoot &&
106
120
  ReactDOM.createPortal(jsxRuntime.jsx(components_Tooltip_TooltipPortal.TooltipPortal, { message: message, isVisible: isVisible, parentRect: rect, isIcon: isIcon }), portalRoot), childrenWithProps, jsxRuntime.jsx(MemoizedVisuallyHiddenText, { id: messageId, visible: isVisible, children: message })] }));
107
121
  };
108
122
  const MemoizedVisuallyHiddenText = React.memo(({ id, visible, children }) => {
@@ -1 +1 @@
1
- {"version":3,"file":"Tooltip.cjs","sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["'use client'\n\nimport {\n type BaseSyntheticEvent,\n type ComponentProps,\n type FC,\n type FocusEvent,\n type PointerEvent,\n type PropsWithChildren,\n type ReactElement,\n type ReactNode,\n type TouchEvent,\n cloneElement,\n memo,\n useCallback,\n useId,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} from 'react'\nimport { createPortal } from 'react-dom'\nimport innerText from 'react-innertext'\nimport { tv } from 'tailwind-variants'\n\nimport { useEnhancedEffect } from '../../hooks/useEnhancedEffect'\nimport { VisuallyHiddenText } from '../VisuallyHiddenText'\n\nimport { TooltipPortal } from './TooltipPortal'\n\nconst subscribeFullscreenChange = (callback: () => void) => {\n window.addEventListener('fullscreenchange', callback)\n\n return () => {\n window.removeEventListener('fullscreenchange', callback)\n }\n}\nconst getFullscreenElement = () => document.fullscreenElement\nconst getFullscreenElementOnSSR = () => null\n\ntype AbstractProps = PropsWithChildren<{\n /** ツールチップ内に表示するメッセージ */\n message: ReactNode\n /** ツールチップを表示する対象のタイプ。アイコンの場合は `icon` を指定する */\n triggerType?: 'icon' | 'text'\n /** `true` のとき、ツールチップを表示する対象が省略されている場合のみツールチップ表示を有効にする */\n ellipsisOnly?: boolean\n /** ツールチップを表示する対象の tabIndex 値 */\n tabIndex?: number\n /** ツールチップを内包要素に紐付けるかどうか */\n ariaDescribedbyTarget?: 'wrapper' | 'inner'\n}>\ntype Props = AbstractProps & Omit<ComponentProps<'span'>, keyof AbstractProps | 'aria-describedby'>\n\nconst classNameGenerator = tv({\n base: [\n 'smarthr-ui-Tooltip',\n 'shr-relative',\n 'shr-inline-block shr-max-w-full shr-align-bottom',\n 'focus-visible:shr-focus-indicator--outer',\n ],\n variants: {\n isIcon: {\n true: 'shr-leading-[0]',\n },\n },\n})\n\nexport const Tooltip: FC<Props> = ({\n message,\n children,\n triggerType,\n ellipsisOnly,\n tabIndex = 0,\n ariaDescribedbyTarget = 'wrapper',\n className,\n onPointerEnter,\n onPointerLeave,\n onTouchStart,\n onTouchEnd,\n onFocus,\n onBlur,\n ...rest\n}) => {\n const [portalRoot, setPortalRoot] = useState<Element | null>(null)\n const [isVisible, setIsVisible] = useState(false)\n const [rect, setRect] = useState<DOMRect | null>(null)\n const ref = useRef<HTMLDivElement>(null)\n const messageId = useId()\n const fullscreenElement = useSyncExternalStore(\n subscribeFullscreenChange,\n getFullscreenElement,\n getFullscreenElementOnSSR,\n )\n\n useEnhancedEffect(() => {\n setPortalRoot(fullscreenElement ?? document.body)\n }, [fullscreenElement])\n\n const toShowAction = useCallback(\n (e: BaseSyntheticEvent) => {\n // Tooltipのtriggerの他の要素(Dropwdown menu buttonで開いたmenu contentとか)に移動されたらtooltipを表示しない\n if (!ref.current?.contains(e.target)) {\n return\n }\n\n if (ellipsisOnly) {\n const outerWidth = parseInt(\n window\n .getComputedStyle(ref.current.parentNode! as HTMLElement, null)\n .width.match(/\\d+/)![0],\n 10,\n )\n\n if (outerWidth < 0 || outerWidth > ref.current.clientWidth) {\n return\n }\n }\n\n setRect(ref.current.getBoundingClientRect())\n setIsVisible(true)\n },\n [ellipsisOnly],\n )\n const onDelegatePointerEnter = useMemo(\n () =>\n onPointerEnter\n ? (e: PointerEvent<HTMLSpanElement>) => {\n onPointerEnter(e)\n toShowAction(e)\n }\n : toShowAction,\n [onPointerEnter, toShowAction],\n )\n const onDelegateTouchStart = useMemo(\n () =>\n onTouchStart\n ? (e: TouchEvent<HTMLSpanElement>) => {\n onTouchStart(e)\n toShowAction(e)\n }\n : toShowAction,\n [onTouchStart, toShowAction],\n )\n const onDelegateFocus = useMemo(\n () =>\n onFocus\n ? (e: FocusEvent<HTMLSpanElement>) => {\n onFocus(e)\n toShowAction(e)\n }\n : toShowAction,\n [onFocus, toShowAction],\n )\n\n const toCloseAction = useCallback(() => setIsVisible(false), [])\n const onDelegatePointerLeave = useMemo(\n () =>\n onPointerLeave\n ? (e: PointerEvent<HTMLSpanElement>) => {\n onPointerLeave(e)\n toCloseAction()\n }\n : toCloseAction,\n [onPointerLeave, toCloseAction],\n )\n const onDelegateTouchEnd = useMemo(\n () =>\n onTouchEnd\n ? (e: TouchEvent<HTMLSpanElement>) => {\n onTouchEnd(e)\n toCloseAction()\n }\n : toCloseAction,\n [onTouchEnd, toCloseAction],\n )\n const onDelegateBlur = useMemo(\n () =>\n onBlur\n ? (e: FocusEvent<HTMLSpanElement>) => {\n onBlur(e)\n toCloseAction()\n }\n : toCloseAction,\n [onBlur, toCloseAction],\n )\n\n const isIcon = triggerType === 'icon'\n const actualClassName = useMemo(\n () => classNameGenerator({ isIcon, className }),\n [isIcon, className],\n )\n const isInnerTarget = ariaDescribedbyTarget === 'inner'\n const childrenWithProps = useMemo(\n () =>\n isInnerTarget\n ? cloneElement(children as ReactElement, { 'aria-describedby': messageId })\n : children,\n [children, isInnerTarget, messageId],\n )\n\n return (\n // eslint-disable-next-line jsx-a11y/no-static-element-interactions\n <span\n {...rest}\n ref={ref}\n tabIndex={tabIndex}\n aria-describedby={isInnerTarget ? undefined : messageId}\n onPointerEnter={onDelegatePointerEnter}\n onTouchStart={onDelegateTouchStart}\n onFocus={onDelegateFocus}\n onPointerLeave={onDelegatePointerLeave}\n onTouchEnd={onDelegateTouchEnd}\n onBlur={onDelegateBlur}\n className={actualClassName}\n >\n {portalRoot &&\n createPortal(\n <TooltipPortal\n message={message}\n isVisible={isVisible}\n parentRect={rect}\n isIcon={isIcon}\n />,\n portalRoot,\n )}\n {childrenWithProps}\n <MemoizedVisuallyHiddenText id={messageId} visible={isVisible}>\n {message}\n </MemoizedVisuallyHiddenText>\n </span>\n )\n}\n\nconst MemoizedVisuallyHiddenText = memo<PropsWithChildren<{ id: string; visible: boolean }>>(\n ({ id, visible, children }) => {\n const hiddenText = useMemo(() => innerText(children), [children])\n\n return (\n <VisuallyHiddenText id={id} aria-hidden={!visible}>\n {hiddenText}\n </VisuallyHiddenText>\n )\n },\n)\n"],"names":[],"mappings":";;;;;;;;;;;;AA8BA;AACE;AAEA;AACE;AACF;AACF;AACA;AACA;AAgBA;AACE;;;;;AAKC;AACD;AACE;AACE;AACD;AACF;AACF;AAEM;;;;AAmBL;AACA;;;AAQE;AACF;AAEA;;AAGI;;;;AAKE;;;AAOA;;;;;;AAOJ;AAGF;AAGM;;;;;AAON;AAGM;;;;;AAON;AAGM;;;;;AAQN;AACA;AAGM;;AAEI;;;AAKV;AAGM;;AAEI;;;AAKV;AAGM;;AAEI;;;AAMV;;AAKA;AACA;;;;;;AAwBM;AAeR;AAEA;AAEI;AAEA;AAKF;;"}
1
+ {"version":3,"file":"Tooltip.cjs","sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["'use client'\n\nimport {\n type BaseSyntheticEvent,\n type ComponentProps,\n type FC,\n type FocusEvent,\n type PointerEvent,\n type PropsWithChildren,\n type ReactElement,\n type ReactNode,\n type TouchEvent,\n cloneElement,\n memo,\n useCallback,\n useId,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} from 'react'\nimport { createPortal } from 'react-dom'\nimport innerText from 'react-innertext'\nimport { tv } from 'tailwind-variants'\n\nimport { useEnhancedEffect } from '../../hooks/useEnhancedEffect'\nimport { VisuallyHiddenText } from '../VisuallyHiddenText'\n\nimport { TooltipPortal } from './TooltipPortal'\n\nconst subscribeFullscreenChange = (callback: () => void) => {\n window.addEventListener('fullscreenchange', callback)\n\n return () => {\n window.removeEventListener('fullscreenchange', callback)\n }\n}\nconst getFullscreenElement = () => document.fullscreenElement\nconst getFullscreenElementOnSSR = () => null\n\ntype AbstractProps = PropsWithChildren<{\n /** ツールチップ内に表示するメッセージ */\n message: ReactNode\n /** ツールチップの種類。`label` の場合は children の要素に `aria-labelledby` を付与しアクセシブルネームとして機能する。`description`(デフォルト)の場合は `aria-describedby` を付与し補足説明として機能する */\n type?: 'label' | 'description'\n /** ツールチップを表示する対象のタイプ。アイコンの場合は `icon` を指定する */\n triggerType?: 'icon' | 'text'\n /** `true` のとき、ツールチップを表示する対象が省略されている場合のみツールチップ表示を有効にする */\n ellipsisOnly?: boolean\n /** ツールチップを表示する対象の tabIndex 値 */\n tabIndex?: number\n /** `type` が `description` の場合に `aria-describedby` を付与する対象。children が focusable な場合は常に children に付与されるため無視される */\n ariaDescribedbyTarget?: 'wrapper' | 'inner'\n}>\ntype Props = AbstractProps &\n Omit<ComponentProps<'span'>, keyof AbstractProps | 'aria-describedby' | 'aria-labelledby'>\n\nconst classNameGenerator = tv({\n base: [\n 'smarthr-ui-Tooltip',\n 'shr-relative',\n 'shr-inline-block shr-max-w-full shr-align-bottom',\n 'focus-visible:shr-focus-indicator--outer',\n ],\n variants: {\n isIcon: {\n true: 'shr-leading-[0]',\n },\n },\n})\n\nexport const Tooltip: FC<Props> = ({\n message,\n children,\n type = 'description',\n triggerType,\n ellipsisOnly,\n tabIndex,\n ariaDescribedbyTarget = 'wrapper',\n className,\n onPointerEnter,\n onPointerLeave,\n onTouchStart,\n onTouchEnd,\n onFocus,\n onBlur,\n ...rest\n}) => {\n const [portalRoot, setPortalRoot] = useState<Element | null>(null)\n const [isVisible, setIsVisible] = useState(false)\n const [rect, setRect] = useState<DOMRect | null>(null)\n const ref = useRef<HTMLDivElement>(null)\n const messageId = useId()\n const fullscreenElement = useSyncExternalStore(\n subscribeFullscreenChange,\n getFullscreenElement,\n getFullscreenElementOnSSR,\n )\n\n const [isFocusableChild, setIsFocusableChild] = useState(false)\n const [actualTabIndex, setActualTabIndex] = useState<number | undefined>(tabIndex ?? 0)\n\n useEnhancedEffect(() => {\n setPortalRoot(fullscreenElement ?? document.body)\n }, [fullscreenElement])\n\n useEnhancedEffect(() => {\n const childElement = ref.current?.querySelector<HTMLElement>(\n ':scope > :not(.smarthr-ui-VisuallyHiddenText)',\n )\n\n const focusable =\n !!childElement &&\n childElement.matches('a[href], button, input, select, textarea, [tabindex]') &&\n !childElement.matches('[tabindex=\"-1\"]')\n\n setIsFocusableChild(focusable)\n setActualTabIndex(tabIndex !== undefined ? tabIndex : focusable ? undefined : 0)\n }, [tabIndex])\n\n const toShowAction = useCallback(\n (e: BaseSyntheticEvent) => {\n // Tooltipのtriggerの他の要素(Dropwdown menu buttonで開いたmenu contentとか)に移動されたらtooltipを表示しない\n if (!ref.current?.contains(e.target)) {\n return\n }\n\n if (ellipsisOnly) {\n const outerWidth = parseInt(\n window\n .getComputedStyle(ref.current.parentNode! as HTMLElement, null)\n .width.match(/\\d+/)![0],\n 10,\n )\n\n if (outerWidth < 0 || outerWidth > ref.current.clientWidth) {\n return\n }\n }\n\n setRect(ref.current.getBoundingClientRect())\n setIsVisible(true)\n },\n [ellipsisOnly],\n )\n const onDelegatePointerEnter = useMemo(\n () =>\n onPointerEnter\n ? (e: PointerEvent<HTMLSpanElement>) => {\n onPointerEnter(e)\n toShowAction(e)\n }\n : toShowAction,\n [onPointerEnter, toShowAction],\n )\n const onDelegateTouchStart = useMemo(\n () =>\n onTouchStart\n ? (e: TouchEvent<HTMLSpanElement>) => {\n onTouchStart(e)\n toShowAction(e)\n }\n : toShowAction,\n [onTouchStart, toShowAction],\n )\n const onDelegateFocus = useMemo(\n () =>\n onFocus\n ? (e: FocusEvent<HTMLSpanElement>) => {\n onFocus(e)\n toShowAction(e)\n }\n : toShowAction,\n [onFocus, toShowAction],\n )\n\n const toCloseAction = useCallback(() => setIsVisible(false), [])\n const onDelegatePointerLeave = useMemo(\n () =>\n onPointerLeave\n ? (e: PointerEvent<HTMLSpanElement>) => {\n onPointerLeave(e)\n toCloseAction()\n }\n : toCloseAction,\n [onPointerLeave, toCloseAction],\n )\n const onDelegateTouchEnd = useMemo(\n () =>\n onTouchEnd\n ? (e: TouchEvent<HTMLSpanElement>) => {\n onTouchEnd(e)\n toCloseAction()\n }\n : toCloseAction,\n [onTouchEnd, toCloseAction],\n )\n const onDelegateBlur = useMemo(\n () =>\n onBlur\n ? (e: FocusEvent<HTMLSpanElement>) => {\n onBlur(e)\n toCloseAction()\n }\n : toCloseAction,\n [onBlur, toCloseAction],\n )\n\n const isIcon = triggerType === 'icon'\n const actualClassName = useMemo(\n () => classNameGenerator({ isIcon, className }),\n [isIcon, className],\n )\n const isLabel = type === 'label'\n const isInnerTarget = isLabel || isFocusableChild || ariaDescribedbyTarget === 'inner'\n const childrenWithProps = useMemo(\n () =>\n isLabel\n ? cloneElement(children as ReactElement, { 'aria-labelledby': messageId })\n : isInnerTarget\n ? cloneElement(children as ReactElement, { 'aria-describedby': messageId })\n : children,\n [children, isLabel, isInnerTarget, messageId],\n )\n const actualWrapperAriaDescribedby = useMemo(\n () => (!isInnerTarget ? messageId : undefined),\n [isInnerTarget, messageId],\n )\n\n return (\n // eslint-disable-next-line jsx-a11y/no-static-element-interactions\n <span\n {...rest}\n ref={ref}\n tabIndex={actualTabIndex}\n aria-describedby={actualWrapperAriaDescribedby}\n onPointerEnter={onDelegatePointerEnter}\n onTouchStart={onDelegateTouchStart}\n onFocus={onDelegateFocus}\n onPointerLeave={onDelegatePointerLeave}\n onTouchEnd={onDelegateTouchEnd}\n onBlur={onDelegateBlur}\n className={actualClassName}\n >\n {portalRoot &&\n createPortal(\n <TooltipPortal\n message={message}\n isVisible={isVisible}\n parentRect={rect}\n isIcon={isIcon}\n />,\n portalRoot,\n )}\n {childrenWithProps}\n <MemoizedVisuallyHiddenText id={messageId} visible={isVisible}>\n {message}\n </MemoizedVisuallyHiddenText>\n </span>\n )\n}\n\nconst MemoizedVisuallyHiddenText = memo<PropsWithChildren<{ id: string; visible: boolean }>>(\n ({ id, visible, children }) => {\n const hiddenText = useMemo(() => innerText(children), [children])\n\n return (\n <VisuallyHiddenText id={id} aria-hidden={!visible}>\n {hiddenText}\n </VisuallyHiddenText>\n )\n },\n)\n"],"names":[],"mappings":";;;;;;;;;;;;AA8BA;AACE;AAEA;AACE;AACF;AACF;AACA;AACA;AAmBA;AACE;;;;;AAKC;AACD;AACE;AACE;AACD;AACF;AACF;AAEM;;;;AAoBL;AACA;;;AAQA;;AAGE;AACF;;;AAOE;AAEE;AACA;;;AAIJ;AAEA;;AAGI;;;;AAKE;;;AAOA;;;;;;AAOJ;AAGF;AAGM;;;;;AAON;AAGM;;;;;AAON;AAGM;;;;;AAQN;AACA;AAGM;;AAEI;;;AAKV;AAGM;;AAEI;;;AAKV;AAGM;;AAEI;;;AAMV;;AAKA;;AAEA;;AAIM;;AAEE;;;;AAUN;AAcI;AAeR;AAEA;AAEI;AAEA;AAKF;;"}
@@ -2,15 +2,17 @@ import { type ComponentProps, type FC, type PropsWithChildren, type ReactNode }
2
2
  type AbstractProps = PropsWithChildren<{
3
3
  /** ツールチップ内に表示するメッセージ */
4
4
  message: ReactNode;
5
+ /** ツールチップの種類。`label` の場合は children の要素に `aria-labelledby` を付与しアクセシブルネームとして機能する。`description`(デフォルト)の場合は `aria-describedby` を付与し補足説明として機能する */
6
+ type?: 'label' | 'description';
5
7
  /** ツールチップを表示する対象のタイプ。アイコンの場合は `icon` を指定する */
6
8
  triggerType?: 'icon' | 'text';
7
9
  /** `true` のとき、ツールチップを表示する対象が省略されている場合のみツールチップ表示を有効にする */
8
10
  ellipsisOnly?: boolean;
9
11
  /** ツールチップを表示する対象の tabIndex 値 */
10
12
  tabIndex?: number;
11
- /** ツールチップを内包要素に紐付けるかどうか */
13
+ /** `type` が `description` の場合に `aria-describedby` を付与する対象。children が focusable な場合は常に children に付与されるため無視される */
12
14
  ariaDescribedbyTarget?: 'wrapper' | 'inner';
13
15
  }>;
14
- type Props = AbstractProps & Omit<ComponentProps<'span'>, keyof AbstractProps | 'aria-describedby'>;
16
+ type Props = AbstractProps & Omit<ComponentProps<'span'>, keyof AbstractProps | 'aria-describedby' | 'aria-labelledby'>;
15
17
  export declare const Tooltip: FC<Props>;
16
18
  export {};
@@ -29,16 +29,26 @@ const classNameGenerator = ce({
29
29
  },
30
30
  },
31
31
  });
32
- const Tooltip = ({ message, children, triggerType, ellipsisOnly, tabIndex = 0, ariaDescribedbyTarget = 'wrapper', className, onPointerEnter, onPointerLeave, onTouchStart, onTouchEnd, onFocus, onBlur, ...rest }) => {
32
+ const Tooltip = ({ message, children, type = 'description', triggerType, ellipsisOnly, tabIndex, ariaDescribedbyTarget = 'wrapper', className, onPointerEnter, onPointerLeave, onTouchStart, onTouchEnd, onFocus, onBlur, ...rest }) => {
33
33
  const [portalRoot, setPortalRoot] = useState(null);
34
34
  const [isVisible, setIsVisible] = useState(false);
35
35
  const [rect, setRect] = useState(null);
36
36
  const ref = useRef(null);
37
37
  const messageId = useId();
38
38
  const fullscreenElement = useSyncExternalStore(subscribeFullscreenChange, getFullscreenElement, getFullscreenElementOnSSR);
39
+ const [isFocusableChild, setIsFocusableChild] = useState(false);
40
+ const [actualTabIndex, setActualTabIndex] = useState(tabIndex ?? 0);
39
41
  useEnhancedEffect(() => {
40
42
  setPortalRoot(fullscreenElement ?? document.body);
41
43
  }, [fullscreenElement]);
44
+ useEnhancedEffect(() => {
45
+ const childElement = ref.current?.querySelector(':scope > :not(.smarthr-ui-VisuallyHiddenText)');
46
+ const focusable = !!childElement &&
47
+ childElement.matches('a[href], button, input, select, textarea, [tabindex]') &&
48
+ !childElement.matches('[tabindex="-1"]');
49
+ setIsFocusableChild(focusable);
50
+ setActualTabIndex(tabIndex !== undefined ? tabIndex : focusable ? undefined : 0);
51
+ }, [tabIndex]);
42
52
  const toShowAction = useCallback((e) => {
43
53
  // Tooltipのtriggerの他の要素(Dropwdown menu buttonで開いたmenu contentとか)に移動されたらtooltipを表示しない
44
54
  if (!ref.current?.contains(e.target)) {
@@ -94,13 +104,17 @@ const Tooltip = ({ message, children, triggerType, ellipsisOnly, tabIndex = 0, a
94
104
  : toCloseAction, [onBlur, toCloseAction]);
95
105
  const isIcon = triggerType === 'icon';
96
106
  const actualClassName = useMemo(() => classNameGenerator({ isIcon, className }), [isIcon, className]);
97
- const isInnerTarget = ariaDescribedbyTarget === 'inner';
98
- const childrenWithProps = useMemo(() => isInnerTarget
99
- ? cloneElement(children, { 'aria-describedby': messageId })
100
- : children, [children, isInnerTarget, messageId]);
107
+ const isLabel = type === 'label';
108
+ const isInnerTarget = isLabel || isFocusableChild || ariaDescribedbyTarget === 'inner';
109
+ const childrenWithProps = useMemo(() => isLabel
110
+ ? cloneElement(children, { 'aria-labelledby': messageId })
111
+ : isInnerTarget
112
+ ? cloneElement(children, { 'aria-describedby': messageId })
113
+ : children, [children, isLabel, isInnerTarget, messageId]);
114
+ const actualWrapperAriaDescribedby = useMemo(() => (!isInnerTarget ? messageId : undefined), [isInnerTarget, messageId]);
101
115
  return (
102
116
  // eslint-disable-next-line jsx-a11y/no-static-element-interactions
103
- jsxs("span", { ...rest, ref: ref, tabIndex: tabIndex, "aria-describedby": isInnerTarget ? undefined : messageId, onPointerEnter: onDelegatePointerEnter, onTouchStart: onDelegateTouchStart, onFocus: onDelegateFocus, onPointerLeave: onDelegatePointerLeave, onTouchEnd: onDelegateTouchEnd, onBlur: onDelegateBlur, className: actualClassName, children: [portalRoot &&
117
+ jsxs("span", { ...rest, ref: ref, tabIndex: actualTabIndex, "aria-describedby": actualWrapperAriaDescribedby, onPointerEnter: onDelegatePointerEnter, onTouchStart: onDelegateTouchStart, onFocus: onDelegateFocus, onPointerLeave: onDelegatePointerLeave, onTouchEnd: onDelegateTouchEnd, onBlur: onDelegateBlur, className: actualClassName, children: [portalRoot &&
104
118
  createPortal(jsx(TooltipPortal, { message: message, isVisible: isVisible, parentRect: rect, isIcon: isIcon }), portalRoot), childrenWithProps, jsx(MemoizedVisuallyHiddenText, { id: messageId, visible: isVisible, children: message })] }));
105
119
  };
106
120
  const MemoizedVisuallyHiddenText = memo(({ id, visible, children }) => {
@@ -1 +1 @@
1
- {"version":3,"file":"Tooltip.js","sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["'use client'\n\nimport {\n type BaseSyntheticEvent,\n type ComponentProps,\n type FC,\n type FocusEvent,\n type PointerEvent,\n type PropsWithChildren,\n type ReactElement,\n type ReactNode,\n type TouchEvent,\n cloneElement,\n memo,\n useCallback,\n useId,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} from 'react'\nimport { createPortal } from 'react-dom'\nimport innerText from 'react-innertext'\nimport { tv } from 'tailwind-variants'\n\nimport { useEnhancedEffect } from '../../hooks/useEnhancedEffect'\nimport { VisuallyHiddenText } from '../VisuallyHiddenText'\n\nimport { TooltipPortal } from './TooltipPortal'\n\nconst subscribeFullscreenChange = (callback: () => void) => {\n window.addEventListener('fullscreenchange', callback)\n\n return () => {\n window.removeEventListener('fullscreenchange', callback)\n }\n}\nconst getFullscreenElement = () => document.fullscreenElement\nconst getFullscreenElementOnSSR = () => null\n\ntype AbstractProps = PropsWithChildren<{\n /** ツールチップ内に表示するメッセージ */\n message: ReactNode\n /** ツールチップを表示する対象のタイプ。アイコンの場合は `icon` を指定する */\n triggerType?: 'icon' | 'text'\n /** `true` のとき、ツールチップを表示する対象が省略されている場合のみツールチップ表示を有効にする */\n ellipsisOnly?: boolean\n /** ツールチップを表示する対象の tabIndex 値 */\n tabIndex?: number\n /** ツールチップを内包要素に紐付けるかどうか */\n ariaDescribedbyTarget?: 'wrapper' | 'inner'\n}>\ntype Props = AbstractProps & Omit<ComponentProps<'span'>, keyof AbstractProps | 'aria-describedby'>\n\nconst classNameGenerator = tv({\n base: [\n 'smarthr-ui-Tooltip',\n 'shr-relative',\n 'shr-inline-block shr-max-w-full shr-align-bottom',\n 'focus-visible:shr-focus-indicator--outer',\n ],\n variants: {\n isIcon: {\n true: 'shr-leading-[0]',\n },\n },\n})\n\nexport const Tooltip: FC<Props> = ({\n message,\n children,\n triggerType,\n ellipsisOnly,\n tabIndex = 0,\n ariaDescribedbyTarget = 'wrapper',\n className,\n onPointerEnter,\n onPointerLeave,\n onTouchStart,\n onTouchEnd,\n onFocus,\n onBlur,\n ...rest\n}) => {\n const [portalRoot, setPortalRoot] = useState<Element | null>(null)\n const [isVisible, setIsVisible] = useState(false)\n const [rect, setRect] = useState<DOMRect | null>(null)\n const ref = useRef<HTMLDivElement>(null)\n const messageId = useId()\n const fullscreenElement = useSyncExternalStore(\n subscribeFullscreenChange,\n getFullscreenElement,\n getFullscreenElementOnSSR,\n )\n\n useEnhancedEffect(() => {\n setPortalRoot(fullscreenElement ?? document.body)\n }, [fullscreenElement])\n\n const toShowAction = useCallback(\n (e: BaseSyntheticEvent) => {\n // Tooltipのtriggerの他の要素(Dropwdown menu buttonで開いたmenu contentとか)に移動されたらtooltipを表示しない\n if (!ref.current?.contains(e.target)) {\n return\n }\n\n if (ellipsisOnly) {\n const outerWidth = parseInt(\n window\n .getComputedStyle(ref.current.parentNode! as HTMLElement, null)\n .width.match(/\\d+/)![0],\n 10,\n )\n\n if (outerWidth < 0 || outerWidth > ref.current.clientWidth) {\n return\n }\n }\n\n setRect(ref.current.getBoundingClientRect())\n setIsVisible(true)\n },\n [ellipsisOnly],\n )\n const onDelegatePointerEnter = useMemo(\n () =>\n onPointerEnter\n ? (e: PointerEvent<HTMLSpanElement>) => {\n onPointerEnter(e)\n toShowAction(e)\n }\n : toShowAction,\n [onPointerEnter, toShowAction],\n )\n const onDelegateTouchStart = useMemo(\n () =>\n onTouchStart\n ? (e: TouchEvent<HTMLSpanElement>) => {\n onTouchStart(e)\n toShowAction(e)\n }\n : toShowAction,\n [onTouchStart, toShowAction],\n )\n const onDelegateFocus = useMemo(\n () =>\n onFocus\n ? (e: FocusEvent<HTMLSpanElement>) => {\n onFocus(e)\n toShowAction(e)\n }\n : toShowAction,\n [onFocus, toShowAction],\n )\n\n const toCloseAction = useCallback(() => setIsVisible(false), [])\n const onDelegatePointerLeave = useMemo(\n () =>\n onPointerLeave\n ? (e: PointerEvent<HTMLSpanElement>) => {\n onPointerLeave(e)\n toCloseAction()\n }\n : toCloseAction,\n [onPointerLeave, toCloseAction],\n )\n const onDelegateTouchEnd = useMemo(\n () =>\n onTouchEnd\n ? (e: TouchEvent<HTMLSpanElement>) => {\n onTouchEnd(e)\n toCloseAction()\n }\n : toCloseAction,\n [onTouchEnd, toCloseAction],\n )\n const onDelegateBlur = useMemo(\n () =>\n onBlur\n ? (e: FocusEvent<HTMLSpanElement>) => {\n onBlur(e)\n toCloseAction()\n }\n : toCloseAction,\n [onBlur, toCloseAction],\n )\n\n const isIcon = triggerType === 'icon'\n const actualClassName = useMemo(\n () => classNameGenerator({ isIcon, className }),\n [isIcon, className],\n )\n const isInnerTarget = ariaDescribedbyTarget === 'inner'\n const childrenWithProps = useMemo(\n () =>\n isInnerTarget\n ? cloneElement(children as ReactElement, { 'aria-describedby': messageId })\n : children,\n [children, isInnerTarget, messageId],\n )\n\n return (\n // eslint-disable-next-line jsx-a11y/no-static-element-interactions\n <span\n {...rest}\n ref={ref}\n tabIndex={tabIndex}\n aria-describedby={isInnerTarget ? undefined : messageId}\n onPointerEnter={onDelegatePointerEnter}\n onTouchStart={onDelegateTouchStart}\n onFocus={onDelegateFocus}\n onPointerLeave={onDelegatePointerLeave}\n onTouchEnd={onDelegateTouchEnd}\n onBlur={onDelegateBlur}\n className={actualClassName}\n >\n {portalRoot &&\n createPortal(\n <TooltipPortal\n message={message}\n isVisible={isVisible}\n parentRect={rect}\n isIcon={isIcon}\n />,\n portalRoot,\n )}\n {childrenWithProps}\n <MemoizedVisuallyHiddenText id={messageId} visible={isVisible}>\n {message}\n </MemoizedVisuallyHiddenText>\n </span>\n )\n}\n\nconst MemoizedVisuallyHiddenText = memo<PropsWithChildren<{ id: string; visible: boolean }>>(\n ({ id, visible, children }) => {\n const hiddenText = useMemo(() => innerText(children), [children])\n\n return (\n <VisuallyHiddenText id={id} aria-hidden={!visible}>\n {hiddenText}\n </VisuallyHiddenText>\n )\n },\n)\n"],"names":[],"mappings":";;;;;;;;;;AA8BA;AACE;AAEA;AACE;AACF;AACF;AACA;AACA;AAgBA;AACE;;;;;AAKC;AACD;AACE;AACE;AACD;AACF;AACF;AAEM;;;;AAmBL;AACA;;;AAQE;AACF;AAEA;;AAGI;;;;AAKE;;;AAOA;;;;;;AAOJ;AAGF;AAGM;;;;;AAON;AAGM;;;;;AAON;AAGM;;;;;AAQN;AACA;AAGM;;AAEI;;;AAKV;AAGM;;AAEI;;;AAKV;AAGM;;AAEI;;;AAMV;;AAKA;AACA;;;;;;AAwBM;AAeR;AAEA;AAEI;AAEA;AAKF;;"}
1
+ {"version":3,"file":"Tooltip.js","sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["'use client'\n\nimport {\n type BaseSyntheticEvent,\n type ComponentProps,\n type FC,\n type FocusEvent,\n type PointerEvent,\n type PropsWithChildren,\n type ReactElement,\n type ReactNode,\n type TouchEvent,\n cloneElement,\n memo,\n useCallback,\n useId,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} from 'react'\nimport { createPortal } from 'react-dom'\nimport innerText from 'react-innertext'\nimport { tv } from 'tailwind-variants'\n\nimport { useEnhancedEffect } from '../../hooks/useEnhancedEffect'\nimport { VisuallyHiddenText } from '../VisuallyHiddenText'\n\nimport { TooltipPortal } from './TooltipPortal'\n\nconst subscribeFullscreenChange = (callback: () => void) => {\n window.addEventListener('fullscreenchange', callback)\n\n return () => {\n window.removeEventListener('fullscreenchange', callback)\n }\n}\nconst getFullscreenElement = () => document.fullscreenElement\nconst getFullscreenElementOnSSR = () => null\n\ntype AbstractProps = PropsWithChildren<{\n /** ツールチップ内に表示するメッセージ */\n message: ReactNode\n /** ツールチップの種類。`label` の場合は children の要素に `aria-labelledby` を付与しアクセシブルネームとして機能する。`description`(デフォルト)の場合は `aria-describedby` を付与し補足説明として機能する */\n type?: 'label' | 'description'\n /** ツールチップを表示する対象のタイプ。アイコンの場合は `icon` を指定する */\n triggerType?: 'icon' | 'text'\n /** `true` のとき、ツールチップを表示する対象が省略されている場合のみツールチップ表示を有効にする */\n ellipsisOnly?: boolean\n /** ツールチップを表示する対象の tabIndex 値 */\n tabIndex?: number\n /** `type` が `description` の場合に `aria-describedby` を付与する対象。children が focusable な場合は常に children に付与されるため無視される */\n ariaDescribedbyTarget?: 'wrapper' | 'inner'\n}>\ntype Props = AbstractProps &\n Omit<ComponentProps<'span'>, keyof AbstractProps | 'aria-describedby' | 'aria-labelledby'>\n\nconst classNameGenerator = tv({\n base: [\n 'smarthr-ui-Tooltip',\n 'shr-relative',\n 'shr-inline-block shr-max-w-full shr-align-bottom',\n 'focus-visible:shr-focus-indicator--outer',\n ],\n variants: {\n isIcon: {\n true: 'shr-leading-[0]',\n },\n },\n})\n\nexport const Tooltip: FC<Props> = ({\n message,\n children,\n type = 'description',\n triggerType,\n ellipsisOnly,\n tabIndex,\n ariaDescribedbyTarget = 'wrapper',\n className,\n onPointerEnter,\n onPointerLeave,\n onTouchStart,\n onTouchEnd,\n onFocus,\n onBlur,\n ...rest\n}) => {\n const [portalRoot, setPortalRoot] = useState<Element | null>(null)\n const [isVisible, setIsVisible] = useState(false)\n const [rect, setRect] = useState<DOMRect | null>(null)\n const ref = useRef<HTMLDivElement>(null)\n const messageId = useId()\n const fullscreenElement = useSyncExternalStore(\n subscribeFullscreenChange,\n getFullscreenElement,\n getFullscreenElementOnSSR,\n )\n\n const [isFocusableChild, setIsFocusableChild] = useState(false)\n const [actualTabIndex, setActualTabIndex] = useState<number | undefined>(tabIndex ?? 0)\n\n useEnhancedEffect(() => {\n setPortalRoot(fullscreenElement ?? document.body)\n }, [fullscreenElement])\n\n useEnhancedEffect(() => {\n const childElement = ref.current?.querySelector<HTMLElement>(\n ':scope > :not(.smarthr-ui-VisuallyHiddenText)',\n )\n\n const focusable =\n !!childElement &&\n childElement.matches('a[href], button, input, select, textarea, [tabindex]') &&\n !childElement.matches('[tabindex=\"-1\"]')\n\n setIsFocusableChild(focusable)\n setActualTabIndex(tabIndex !== undefined ? tabIndex : focusable ? undefined : 0)\n }, [tabIndex])\n\n const toShowAction = useCallback(\n (e: BaseSyntheticEvent) => {\n // Tooltipのtriggerの他の要素(Dropwdown menu buttonで開いたmenu contentとか)に移動されたらtooltipを表示しない\n if (!ref.current?.contains(e.target)) {\n return\n }\n\n if (ellipsisOnly) {\n const outerWidth = parseInt(\n window\n .getComputedStyle(ref.current.parentNode! as HTMLElement, null)\n .width.match(/\\d+/)![0],\n 10,\n )\n\n if (outerWidth < 0 || outerWidth > ref.current.clientWidth) {\n return\n }\n }\n\n setRect(ref.current.getBoundingClientRect())\n setIsVisible(true)\n },\n [ellipsisOnly],\n )\n const onDelegatePointerEnter = useMemo(\n () =>\n onPointerEnter\n ? (e: PointerEvent<HTMLSpanElement>) => {\n onPointerEnter(e)\n toShowAction(e)\n }\n : toShowAction,\n [onPointerEnter, toShowAction],\n )\n const onDelegateTouchStart = useMemo(\n () =>\n onTouchStart\n ? (e: TouchEvent<HTMLSpanElement>) => {\n onTouchStart(e)\n toShowAction(e)\n }\n : toShowAction,\n [onTouchStart, toShowAction],\n )\n const onDelegateFocus = useMemo(\n () =>\n onFocus\n ? (e: FocusEvent<HTMLSpanElement>) => {\n onFocus(e)\n toShowAction(e)\n }\n : toShowAction,\n [onFocus, toShowAction],\n )\n\n const toCloseAction = useCallback(() => setIsVisible(false), [])\n const onDelegatePointerLeave = useMemo(\n () =>\n onPointerLeave\n ? (e: PointerEvent<HTMLSpanElement>) => {\n onPointerLeave(e)\n toCloseAction()\n }\n : toCloseAction,\n [onPointerLeave, toCloseAction],\n )\n const onDelegateTouchEnd = useMemo(\n () =>\n onTouchEnd\n ? (e: TouchEvent<HTMLSpanElement>) => {\n onTouchEnd(e)\n toCloseAction()\n }\n : toCloseAction,\n [onTouchEnd, toCloseAction],\n )\n const onDelegateBlur = useMemo(\n () =>\n onBlur\n ? (e: FocusEvent<HTMLSpanElement>) => {\n onBlur(e)\n toCloseAction()\n }\n : toCloseAction,\n [onBlur, toCloseAction],\n )\n\n const isIcon = triggerType === 'icon'\n const actualClassName = useMemo(\n () => classNameGenerator({ isIcon, className }),\n [isIcon, className],\n )\n const isLabel = type === 'label'\n const isInnerTarget = isLabel || isFocusableChild || ariaDescribedbyTarget === 'inner'\n const childrenWithProps = useMemo(\n () =>\n isLabel\n ? cloneElement(children as ReactElement, { 'aria-labelledby': messageId })\n : isInnerTarget\n ? cloneElement(children as ReactElement, { 'aria-describedby': messageId })\n : children,\n [children, isLabel, isInnerTarget, messageId],\n )\n const actualWrapperAriaDescribedby = useMemo(\n () => (!isInnerTarget ? messageId : undefined),\n [isInnerTarget, messageId],\n )\n\n return (\n // eslint-disable-next-line jsx-a11y/no-static-element-interactions\n <span\n {...rest}\n ref={ref}\n tabIndex={actualTabIndex}\n aria-describedby={actualWrapperAriaDescribedby}\n onPointerEnter={onDelegatePointerEnter}\n onTouchStart={onDelegateTouchStart}\n onFocus={onDelegateFocus}\n onPointerLeave={onDelegatePointerLeave}\n onTouchEnd={onDelegateTouchEnd}\n onBlur={onDelegateBlur}\n className={actualClassName}\n >\n {portalRoot &&\n createPortal(\n <TooltipPortal\n message={message}\n isVisible={isVisible}\n parentRect={rect}\n isIcon={isIcon}\n />,\n portalRoot,\n )}\n {childrenWithProps}\n <MemoizedVisuallyHiddenText id={messageId} visible={isVisible}>\n {message}\n </MemoizedVisuallyHiddenText>\n </span>\n )\n}\n\nconst MemoizedVisuallyHiddenText = memo<PropsWithChildren<{ id: string; visible: boolean }>>(\n ({ id, visible, children }) => {\n const hiddenText = useMemo(() => innerText(children), [children])\n\n return (\n <VisuallyHiddenText id={id} aria-hidden={!visible}>\n {hiddenText}\n </VisuallyHiddenText>\n )\n },\n)\n"],"names":[],"mappings":";;;;;;;;;;AA8BA;AACE;AAEA;AACE;AACF;AACF;AACA;AACA;AAmBA;AACE;;;;;AAKC;AACD;AACE;AACE;AACD;AACF;AACF;AAEM;;;;AAoBL;AACA;;;AAQA;;AAGE;AACF;;;AAOE;AAEE;AACA;;;AAIJ;AAEA;;AAGI;;;;AAKE;;;AAOA;;;;;;AAOJ;AAGF;AAGM;;;;;AAON;AAGM;;;;;AAON;AAGM;;;;;AAQN;AACA;AAGM;;AAEI;;;AAKV;AAGM;;AAEI;;;AAKV;AAGM;;AAEI;;;AAMV;;AAKA;;AAEA;;AAIM;;AAEE;;;;AAUN;AAcI;AAeR;AAEA;AAEI;AAEA;AAKF;;"}