smarthr-ui 96.0.1 → 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 (48) 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/Input/InputWithTooltip/InputWithTooltip.cjs +1 -1
  12. package/lib/components/Input/InputWithTooltip/InputWithTooltip.cjs.map +1 -1
  13. package/lib/components/Input/InputWithTooltip/InputWithTooltip.js +1 -1
  14. package/lib/components/Input/InputWithTooltip/InputWithTooltip.js.map +1 -1
  15. package/lib/components/LineClamp/LineClamp.cjs +3 -5
  16. package/lib/components/LineClamp/LineClamp.cjs.map +1 -1
  17. package/lib/components/LineClamp/LineClamp.js +3 -5
  18. package/lib/components/LineClamp/LineClamp.js.map +1 -1
  19. package/lib/components/SideNav/SideNavItemButton.cjs.map +1 -1
  20. package/lib/components/SideNav/SideNavItemButton.d.ts +11 -10
  21. package/lib/components/SideNav/SideNavItemButton.js.map +1 -1
  22. package/lib/components/TabBar/TabItem.cjs +1 -1
  23. package/lib/components/TabBar/TabItem.cjs.map +1 -1
  24. package/lib/components/TabBar/TabItem.js +1 -1
  25. package/lib/components/TabBar/TabItem.js.map +1 -1
  26. package/lib/components/Tooltip/Tooltip.cjs +20 -6
  27. package/lib/components/Tooltip/Tooltip.cjs.map +1 -1
  28. package/lib/components/Tooltip/Tooltip.d.ts +4 -2
  29. package/lib/components/Tooltip/Tooltip.js +20 -6
  30. package/lib/components/Tooltip/Tooltip.js.map +1 -1
  31. 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
  32. package/lib/vendor/.pnpm/react-icons@5.6.0_react@19.2.6/vendor/react-icons/fa6/index.cjs.map +1 -0
  33. 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
  34. package/lib/vendor/.pnpm/react-icons@5.6.0_react@19.2.6/vendor/react-icons/fa6/index.js.map +1 -0
  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/lib/iconBase.cjs +4 -4
  36. package/lib/vendor/.pnpm/react-icons@5.6.0_react@19.2.6/vendor/react-icons/lib/iconBase.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/lib/iconBase.js +4 -4
  38. package/lib/vendor/.pnpm/react-icons@5.6.0_react@19.2.6/vendor/react-icons/lib/iconBase.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/iconContext.cjs.map +1 -1
  40. 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
  41. package/metadata.json +1 -1
  42. package/package.json +4 -4
  43. package/lib/vendor/.pnpm/react-icons@5.5.0_react@19.2.6/vendor/react-icons/fa6/index.cjs.map +0 -1
  44. package/lib/vendor/.pnpm/react-icons@5.5.0_react@19.2.6/vendor/react-icons/fa6/index.js.map +0 -1
  45. package/lib/vendor/.pnpm/react-icons@5.5.0_react@19.2.6/vendor/react-icons/lib/iconBase.cjs.map +0 -1
  46. package/lib/vendor/.pnpm/react-icons@5.5.0_react@19.2.6/vendor/react-icons/lib/iconBase.js.map +0 -1
  47. /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
  48. /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":"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;;"}