@moneyforward/mfui-components 3.29.0 → 3.30.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 (100) hide show
  1. package/dist/src/Button/Button.d.ts +4 -4
  2. package/dist/src/CheckboxCard/CheckboxCard.d.ts +2 -2
  3. package/dist/src/CheckboxCard/CheckboxCard.js +1 -1
  4. package/dist/src/DataTable/DataTableCell/DataTableCell.js +1 -1
  5. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerProvider/DateRangePickerProvider.d.ts +2 -2
  6. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerTrigger/constants.d.ts +4 -4
  7. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerTrigger/hooks/useDateRangeTriggerInputClick.d.ts +1 -1
  8. package/dist/src/DateTimeSelection/DateRangePicker/utilities/createDateRangePickerTestUtility.d.ts +1 -1
  9. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerMonthCell/constants.d.ts +5 -5
  10. package/dist/src/DateTimeSelection/TimePicker/TimePicker.d.ts +9 -9
  11. package/dist/src/DateTimeSelection/shared/BasePicker/basePickerUtilities/useBasePickerValueController.d.ts +1 -1
  12. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/BaseRangePickerCalendar/constants.d.ts +6 -6
  13. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerProvider/BaseRangePickerProvider.d.ts +2 -2
  14. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerTrigger/BaseRangePickerTrigger.js +1 -1
  15. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerTrigger/hooks/useDateRangeTriggerInputClick.d.ts +1 -1
  16. package/dist/src/DateTimeSelection/shared/BaseRangePicker/utilities/createBaseRangePickerTestUtility.d.ts +1 -1
  17. package/dist/src/DateTimeSelection/shared/accessibility/constants.d.ts +6 -6
  18. package/dist/src/Dialog/Dialog.d.ts +4 -4
  19. package/dist/src/Disclosure/Disclosure.d.ts +21 -10
  20. package/dist/src/DropdownMenu/DropdownMenu.d.ts +3 -3
  21. package/dist/src/DropdownMenu/DropdownMenuItem/DropdownMenuItem.js +11 -3
  22. package/dist/src/EmbeddedSidePane/EmbeddedSidePane.d.ts +1 -1
  23. package/dist/src/FileBox/FileBox.d.ts +4 -4
  24. package/dist/src/FileBox/FileBox.js +1 -1
  25. package/dist/src/FileDropZone/FileDropZone.d.ts +4 -4
  26. package/dist/src/FileDropZone/utils/fileHelpers.d.ts +3 -3
  27. package/dist/src/FilterTrigger/FilterTrigger.d.ts +2 -2
  28. package/dist/src/Heading/Heading.d.ts +2 -2
  29. package/dist/src/HelpMessage/HelpMessage.d.ts +3 -3
  30. package/dist/src/IconButton/IconButton.d.ts +6 -6
  31. package/dist/src/KeyValue/KeyValue.d.ts +2 -2
  32. package/dist/src/MainNavigation/MainNavigation.js +1 -1
  33. package/dist/src/MultipleSelectBox/MultipleSelectBox.js +1 -1
  34. package/dist/src/MultipleSelectBox/hooks/useApplyControls.d.ts +1 -1
  35. package/dist/src/MultipleSelectBox/hooks/useInitialFocusOnOptionPanelOpen.js +3 -6
  36. package/dist/src/MultipleSelectBox/hooks/useSelectedValues.d.ts +1 -1
  37. package/dist/src/MultipleSelectBox/utils/getDeepActiveElement.d.ts +11 -0
  38. package/dist/src/MultipleSelectBox/utils/getDeepActiveElement.js +17 -0
  39. package/dist/src/NumberBox/NumberBox.d.ts +1 -1
  40. package/dist/src/Pagination/ItemsCount/ItemsCount.js +1 -1
  41. package/dist/src/Popover/Popover.js +1 -1
  42. package/dist/src/RadioButton/RadioButton.d.ts +1 -1
  43. package/dist/src/RadioButtonCard/RadioButtonCard.d.ts +2 -2
  44. package/dist/src/RadioButtonCard/RadioButtonCard.js +1 -1
  45. package/dist/src/RadioGroup/RadioGroup.d.ts +1 -1
  46. package/dist/src/SectionMessage/SectionMessage.d.ts +5 -5
  47. package/dist/src/SectionMessage/SectionMessage.js +11 -4
  48. package/dist/src/SectionMessage/SectionMessage.types.d.ts +4 -4
  49. package/dist/src/SelectBox/SelectBox.js +1 -1
  50. package/dist/src/SelectBox/SelectBoxOption.js +1 -1
  51. package/dist/src/SelectBox/SelectBoxOptionGroup.js +1 -1
  52. package/dist/src/SidePane/SidePane.d.ts +6 -6
  53. package/dist/src/Sidebar/Sidebar.d.ts +10 -1
  54. package/dist/src/Sidebar/Sidebar.js +40 -10
  55. package/dist/src/Sidebar/Sidebar.types.d.ts +39 -2
  56. package/dist/src/Sidebar/SidebarContext.d.ts +3 -0
  57. package/dist/src/Sidebar/SidebarContext.js +2 -0
  58. package/dist/src/Sidebar/SidebarMenuHeading/SidebarMenuHeading.d.ts +49 -0
  59. package/dist/src/Sidebar/SidebarMenuHeading/SidebarMenuHeading.js +113 -0
  60. package/dist/src/Sidebar/SidebarMenuHeading/SidebarMenuHeading.types.d.ts +36 -0
  61. package/dist/src/Sidebar/SidebarMenuHeading/SidebarMenuHeading.types.js +1 -0
  62. package/dist/src/Sidebar/SidebarMenuHeading/index.d.ts +2 -0
  63. package/dist/src/Sidebar/SidebarMenuHeading/index.js +1 -0
  64. package/dist/src/Sidebar/SidebarNavigationItem.js +6 -3
  65. package/dist/src/Sidebar/hooks/useSidebarResize.d.ts +2 -1
  66. package/dist/src/Sidebar/hooks/useSidebarResize.js +31 -3
  67. package/dist/src/Sidebar/index.d.ts +2 -1
  68. package/dist/src/Stack/Stack.d.ts +5 -5
  69. package/dist/src/StatusLabel/StatusLabel.d.ts +2 -2
  70. package/dist/src/Stepper/Step/Step.js +1 -1
  71. package/dist/src/SubNavigation/SubNavigation.js +1 -1
  72. package/dist/src/TextBox/TextBox.d.ts +6 -6
  73. package/dist/src/TextLink/TextLink.d.ts +2 -2
  74. package/dist/src/Textarea/Textarea.d.ts +4 -4
  75. package/dist/src/Toast/Toast.d.ts +3 -3
  76. package/dist/src/Toast/Toast.js +9 -5
  77. package/dist/src/Toast/Toast.types.d.ts +3 -5
  78. package/dist/src/Toast/api/toast.d.ts +18 -2
  79. package/dist/src/Tooltip/Tooltip.d.ts +3 -3
  80. package/dist/src/Tooltip/Tooltip.js +1 -1
  81. package/dist/src/Tooltip/Tooltip.types.d.ts +2 -0
  82. package/dist/src/Tooltip/hooks/useTooltipDisplayController.js +14 -2
  83. package/dist/src/utilities/dom/dispatchNativeInputValueWithEvent.d.ts +1 -1
  84. package/dist/src/utilities/dom/useFocusTrap.d.ts +1 -1
  85. package/dist/src/utilities/react/useIsomorphicLayoutEffect.d.ts +2 -2
  86. package/dist/styled-system/css/conditions.d.ts +3 -3
  87. package/dist/styled-system/helpers.d.ts +36 -35
  88. package/dist/styled-system/jsx/factory-helper.d.ts +4 -4
  89. package/dist/styled-system/recipes/create-recipe.d.ts +2 -2
  90. package/dist/styled-system/recipes/index.d.ts +2 -1
  91. package/dist/styled-system/recipes/index.js +1 -0
  92. package/dist/styled-system/recipes/section-message-slot-recipe.d.ts +1 -1
  93. package/dist/styled-system/recipes/section-message-slot-recipe.js +3 -1
  94. package/dist/styled-system/recipes/sidebar-menu-heading-slot-recipe.d.ts +33 -0
  95. package/dist/styled-system/recipes/sidebar-menu-heading-slot-recipe.js +44 -0
  96. package/dist/styled-system/recipes/sidebar-slot-recipe.d.ts +1 -1
  97. package/dist/styled-system/recipes/sidebar-slot-recipe.js +4 -0
  98. package/dist/styles.css +82 -2
  99. package/dist/tsconfig.build.tsbuildinfo +1 -1
  100. package/package.json +11 -11
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Returns the truly focused element, traversing into shadow roots recursively.
3
+ *
4
+ * `document.activeElement` stops at the shadow host when focus is inside a Shadow DOM.
5
+ * This function follows the chain of `shadowRoot.activeElement` until the deepest focused element is found.
6
+ *
7
+ * @param root - The root to start from (defaults to `document`)
8
+ *
9
+ * @returns The deepest active element, or `null` if nothing is focused
10
+ */
11
+ export declare const getDeepActiveElement: (root?: Document | ShadowRoot) => Element | null;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Returns the truly focused element, traversing into shadow roots recursively.
3
+ *
4
+ * `document.activeElement` stops at the shadow host when focus is inside a Shadow DOM.
5
+ * This function follows the chain of `shadowRoot.activeElement` until the deepest focused element is found.
6
+ *
7
+ * @param root - The root to start from (defaults to `document`)
8
+ *
9
+ * @returns The deepest active element, or `null` if nothing is focused
10
+ */
11
+ export const getDeepActiveElement = (root = document) => {
12
+ const active = root.activeElement;
13
+ if (active?.shadowRoot) {
14
+ return getDeepActiveElement(active.shadowRoot) ?? active;
15
+ }
16
+ return active;
17
+ };
@@ -8,5 +8,5 @@ import React from 'react';
8
8
  */
9
9
  export declare const NumberBox: React.ForwardRefExoticComponent<Omit<import("..").TextBoxProps, "onChange" | "prefixSlot" | "suffixSlot"> & {
10
10
  onChange?: (value: string) => void;
11
- textAlign?: "left" | "right";
11
+ textAlign?: 'left' | 'right';
12
12
  } & React.RefAttributes<HTMLInputElement>>;
@@ -1,4 +1,4 @@
1
- import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { itemsCountSlotRecipe } from '../../../styled-system/recipes';
3
3
  import { Typography } from '../../Typography';
4
4
  import { cx } from '../../../styled-system/css';
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useMemo, useState, useRef, } from 'react';
4
4
  import { getOverflowAncestors } from '@floating-ui/react-dom';
5
5
  import { PopoverContextProvider, usePopoverContext } from './PopoverContext';
@@ -10,4 +10,4 @@ export declare const RadioButton: import("react").ForwardRefExoticComponent<{
10
10
  label?: string;
11
11
  checked?: boolean | undefined;
12
12
  disabled?: boolean | undefined;
13
- } & Omit<Omit<import("react").DetailedHTMLProps<import("react").InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "ref">, "disabled" | "checked"> & import("react").RefAttributes<HTMLInputElement>>;
13
+ } & Omit<Omit<import("react").DetailedHTMLProps<import("react").InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "ref">, "checked" | "disabled"> & import("react").RefAttributes<HTMLInputElement>>;
@@ -8,8 +8,8 @@ import { type RadioButtonCardResponsiveSlot } from './RadioButtonCard.types';
8
8
  * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio
9
9
  */
10
10
  export declare const RadioButtonCard: import("react").ForwardRefExoticComponent<{
11
- layout?: import("../../styled-system/recipes").RadioButtonCardSlotRecipeVariant["layout"];
11
+ layout?: import("../../styled-system/recipes").RadioButtonCardSlotRecipeVariant['layout'];
12
12
  label: string;
13
13
  renderDetailsSlot?: RadioButtonCardResponsiveSlot | import("./RadioButtonCard.types").RadioButtonCardRenderDetailsSlot;
14
- disableResponsive?: import("../../styled-system/recipes").RadioButtonCardSlotRecipeVariant["disableResponsive"];
14
+ disableResponsive?: import("../../styled-system/recipes").RadioButtonCardSlotRecipeVariant['disableResponsive'];
15
15
  } & Omit<import("..").RadioButtonProps, "label"> & import("react").RefAttributes<HTMLInputElement>>;
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useMemo } from 'react';
3
3
  import { cx } from '../../styled-system/css';
4
4
  import { FocusIndicator } from '../FocusIndicator';
@@ -10,4 +10,4 @@
10
10
  *
11
11
  * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div
12
12
  */
13
- export declare const RadioGroup: import("react").ForwardRefExoticComponent<Pick<import("..").RadioButtonProps, "disabled" | "name" | "value" | "defaultValue" | "onChange"> & Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "onChange"> & import("react").RefAttributes<HTMLDivElement>>;
13
+ export declare const RadioGroup: import("react").ForwardRefExoticComponent<Pick<import("..").RadioButtonProps, "defaultValue" | "disabled" | "name" | "onChange" | "value"> & Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "onChange"> & import("react").RefAttributes<HTMLDivElement>>;
@@ -9,12 +9,12 @@
9
9
  * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div
10
10
  */
11
11
  export declare const SectionMessage: import("react").ForwardRefExoticComponent<{
12
- messageType?: import("../../styled-system/recipes").SectionMessageSlotRecipeVariant["messageType"];
13
- region?: import("../../styled-system/recipes").SectionMessageSlotRecipeVariant["region"];
12
+ messageType?: import("../../styled-system/recipes").SectionMessageSlotRecipeVariant['messageType'];
13
+ region?: import("../../styled-system/recipes").SectionMessageSlotRecipeVariant['region'];
14
14
  actionSlot?: import("react").ReactNode;
15
- onClose?: import("..").IconButtonProps["onClick"];
15
+ onClose?: import("..").IconButtonProps['onClick'];
16
16
  children?: import("react").ReactNode;
17
- statusIconProps?: Pick<import("react").ComponentPropsWithoutRef<"svg">, "aria-label">;
18
- closeButtonProps?: Pick<import("react").ComponentPropsWithoutRef<"svg">, "aria-label">;
17
+ statusIconProps?: Pick<import("react").ComponentPropsWithoutRef<'svg'>, 'aria-label'>;
18
+ closeButtonProps?: Pick<import("react").ComponentPropsWithoutRef<'svg'>, 'aria-label'>;
19
19
  disableResponsive?: boolean;
20
20
  } & Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
@@ -1,9 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef } from 'react';
3
- import { Error, Approval, Caution, Close } from '@moneyforward/mfui-icons-react';
3
+ import { Error, Approval, Caution, Close, Pending } from '@moneyforward/mfui-icons-react';
4
4
  import { cx } from '../../styled-system/css';
5
5
  import { sectionMessageSlotRecipe } from '../../styled-system/recipes';
6
6
  import { IconButton } from '../IconButton';
7
+ import { ProgressIndicator } from '../ProgressIndicator';
7
8
  import { Typography } from '../Typography';
8
9
  /**
9
10
  * The SectionMessage component is used to display an inline message inside a page or some container.
@@ -21,11 +22,17 @@ export const SectionMessage = forwardRef(({ messageType = 'neutral', region = 'i
21
22
  region,
22
23
  disableResponsive,
23
24
  });
25
+ // The status is conveyed by the message content itself, so the status icon is decorative
26
+ // and hidden from assistive technology unless a label is explicitly provided.
27
+ const statusIconLabel = statusIconProps?.['aria-label'];
28
+ const statusIconA11yProps = statusIconLabel === undefined ? { 'aria-hidden': true } : { 'aria-label': statusIconLabel };
24
29
  const StatusIcon = {
25
30
  neutral: null,
26
- success: (_jsx(Approval, { "aria-label": statusIconProps?.['aria-label'] ?? '完了', className: cx(classes.statusIcon, 'mfui-SectionMessage__statusIcon') })),
27
- error: (_jsx(Error, { "aria-label": statusIconProps?.['aria-label'] ?? 'エラー', className: cx(classes.statusIcon, 'mfui-SectionMessage__statusIcon') })),
28
- caution: (_jsx(Caution, { "aria-label": statusIconProps?.['aria-label'] ?? '注意', className: cx(classes.statusIcon, 'mfui-SectionMessage__statusIcon') })),
31
+ success: (_jsx(Approval, { ...statusIconA11yProps, className: cx(classes.statusIcon, 'mfui-SectionMessage__statusIcon') })),
32
+ error: _jsx(Error, { ...statusIconA11yProps, className: cx(classes.statusIcon, 'mfui-SectionMessage__statusIcon') }),
33
+ caution: (_jsx(Caution, { ...statusIconA11yProps, className: cx(classes.statusIcon, 'mfui-SectionMessage__statusIcon') })),
34
+ busy: _jsx(Pending, { ...statusIconA11yProps, className: cx(classes.statusIcon, 'mfui-SectionMessage__statusIcon') }),
35
+ progress: (_jsx(ProgressIndicator, { role: "img", ...statusIconA11yProps, className: cx(classes.statusIcon, 'mfui-SectionMessage__statusIcon') })),
29
36
  }[messageType];
30
37
  const contentIsString = typeof children === 'string';
31
38
  return (_jsxs("div", { ref: ref, className: cx(classes.root, 'mfui-SectionMessage__root', className), ...props, role: "alert", children: [_jsxs("div", { className: cx(classes.topSection, 'mfui-SectionMessage__topSection'), children: [_jsxs("div", { className: cx(classes.content, 'mfui-SectionMessage__content'), children: [StatusIcon, contentIsString ? _jsx(Typography, { variant: "body", children: children }) : children] }), _jsxs("div", { className: cx(classes.rightSection, 'mfui-SectionMessage__rightSection'), children: [_jsx("div", { className: cx(classes.actionSlot, classes.desktopActionSlot, 'mfui-SectionMessage__actionSlot'), "data-mfui-content": "SectionMessage-action-slot-desktop", children: actionSlot }), onClose ? (_jsx(IconButton, { "aria-label": closeButtonProps?.['aria-label'] ?? '閉じる', onClick: onClose, children: _jsx(Close, { "aria-hidden": true, className: cx(classes.closeIcon, 'mfui-SectionMessage__closeIcon') }) })) : null] })] }), actionSlot && !disableResponsive ? (_jsx("div", { className: cx(classes.actionSlot, classes.mobileActionSlot, 'mfui-SectionMessage__actionSlot'), "data-mfui-content": "SectionMessage-action-slot-mobile", children: actionSlot })) : null] }));
@@ -35,10 +35,10 @@ export type SectionMessageProps = {
35
35
  /**
36
36
  * Props for the status icon.
37
37
  *
38
- * @property aria-label - The text alternative for the status icon. By default, `messageType` is:
39
- * - `success`: '完了'
40
- * - `error`: 'エラー'
41
- * - `caution`: '注意'
38
+ * @property aria-label - The text alternative for the status icon.
39
+ * By default, the status icon is decorative and hidden from assistive technology (`aria-hidden`),
40
+ * because the message content itself conveys the status.
41
+ * Pass an `aria-label` only when the icon needs to be announced.
42
42
  */
43
43
  statusIconProps?: Pick<ComponentPropsWithoutRef<'svg'>, 'aria-label'>;
44
44
  /**
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React, { forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
4
4
  import { Dropdown as DropdownIcon, Error } from '@moneyforward/mfui-icons-react';
5
5
  import { ClearButton } from '../shared/ClearButton';
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { memo, useMemo, useCallback } from 'react';
4
4
  import { Current } from '@moneyforward/mfui-icons-react';
5
5
  import { FocusIndicator } from '../FocusIndicator';
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { memo } from 'react';
4
4
  import { Typography } from '../Typography';
5
5
  import { SelectBoxOptionComponent } from './SelectBoxOption';
@@ -9,22 +9,22 @@ export declare const SidePane: import("react").ForwardRefExoticComponent<{
9
9
  title?: string;
10
10
  renderHeading?: (options: import("./SidePane.types").SidePaneRenderOptions) => import("react").ReactNode;
11
11
  actionSlot?: import("react").ReactNode;
12
- position?: import("../../styled-system/recipes").SidePaneSlotRecipeVariant["position"];
12
+ position?: import("../../styled-system/recipes").SidePaneSlotRecipeVariant['position'];
13
13
  closeButtonProps?: {
14
14
  isCollapseIcon?: boolean;
15
- } & Pick<import("react").ComponentPropsWithoutRef<"button">, "aria-label">;
15
+ } & Pick<import("react").ComponentPropsWithoutRef<'button'>, 'aria-label'>;
16
16
  targetDOMNode?: HTMLElement | null;
17
17
  enableModal?: boolean;
18
18
  disableBackdropClose?: boolean;
19
19
  enableAutoUnmount?: boolean;
20
- formFooterProps?: Pick<import("..").FormFooterProps, "optionsSlot" | "actionsSlot"> & {
21
- position?: import("..").FormFooterProps["position"];
20
+ formFooterProps?: Pick<import("..").FormFooterProps, 'optionsSlot' | 'actionsSlot'> & {
21
+ position?: import("..").FormFooterProps['position'];
22
22
  };
23
23
  insideProps?: {
24
24
  className?: string;
25
25
  };
26
26
  open?: boolean;
27
27
  onClose?: import("./SidePane.types").OnCloseFunction;
28
- } & Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "open" | "onClose"> & {
29
- ref?: (import("react").RefObject<HTMLDivElement | null> | import("react").RefCallback<HTMLDivElement | null> | null) | undefined;
28
+ } & Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "onClose" | "open"> & {
29
+ ref?: (import("react").RefCallback<HTMLDivElement | null> | import("react").RefObject<HTMLDivElement | null> | null) | undefined;
30
30
  } & import("react").Attributes>;
@@ -2,8 +2,9 @@ import { type SidebarProps } from './Sidebar.types';
2
2
  import { SidebarServiceMenu } from './SidebarServiceMenu';
3
3
  import { SidebarTenantMenu } from './SidebarTenantMenu';
4
4
  import { SidebarUserMenu } from './SidebarUserMenu';
5
+ import { SidebarTenantMenuHeading, SidebarUserMenuHeading } from './SidebarMenuHeading';
5
6
  import { SidebarNavigationItem } from './SidebarNavigationItem';
6
- declare function SidebarBase({ className, logoSlot, mainNavigationItems, extraNavigationSlot, footerSlot, serviceMenuSlot, footerIconMenuSlot, tenantSlot, userSlot, minWidth, maxWidth, defaultWidth, onWidthChange, mainNavigationLabel, extraNavigationLabel, resizeHandleLabel, }: SidebarProps): import("react/jsx-runtime").JSX.Element;
7
+ declare function SidebarBase({ className, logoSlot, mainNavigationItems, extraNavigationSlot, footerSlot, serviceMenuSlot, footerIconMenuSlot, tenantSlot, userSlot, minWidth, maxWidth, defaultWidth, onWidthChange, enableCollapsible, isCollapsed: controlledIsCollapsed, onCollapsedChange, collapseButtonLabel, expandButtonLabel, mainNavigationLabel, extraNavigationLabel, resizeHandleLabel, }: SidebarProps): import("react/jsx-runtime").JSX.Element;
7
8
  /**
8
9
  * The sidebar component.
9
10
  * A navigation layout component that combines a logo header, a navigation area,
@@ -20,6 +21,10 @@ declare function SidebarBase({ className, logoSlot, mainNavigationItems, extraNa
20
21
  * - `Sidebar.ServiceMenu` — the header service menu trigger, intended to wrap a list of services.
21
22
  * - `Sidebar.TenantMenu` — the footer tenant (business) name and its menu.
22
23
  * - `Sidebar.UserMenu` — the footer user (account) name and its menu.
24
+ * - `Sidebar.TenantMenuHeading` — the heading block inside `Sidebar.TenantMenu`: the tenant name and
25
+ * its identification-number row (optionally copyable to the clipboard).
26
+ * - `Sidebar.UserMenuHeading` — the heading block inside `Sidebar.UserMenu`: the user name and
27
+ * its identification-number row (optionally copyable to the clipboard).
23
28
  *
24
29
  * @example
25
30
  * ```tsx
@@ -59,5 +64,9 @@ export declare const Sidebar: typeof SidebarBase & {
59
64
  TenantMenu: typeof SidebarTenantMenu;
60
65
  /** @see {@link SidebarUserMenu} */
61
66
  UserMenu: typeof SidebarUserMenu;
67
+ /** @see {@link SidebarTenantMenuHeading} */
68
+ TenantMenuHeading: typeof SidebarTenantMenuHeading;
69
+ /** @see {@link SidebarUserMenuHeading} */
70
+ UserMenuHeading: typeof SidebarUserMenuHeading;
62
71
  };
63
72
  export {};
@@ -1,28 +1,50 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { SidebarController } from '@moneyforward/mfui-icons-react';
4
+ import { useId, useMemo, useState } from 'react';
3
5
  import { cx } from '../../styled-system/css';
4
6
  import { sidebarSlotRecipe } from '../../styled-system/recipes';
7
+ import { IconButton } from '../IconButton';
8
+ import { Tooltip } from '../Tooltip';
5
9
  import { SidebarServiceMenu } from './SidebarServiceMenu';
6
10
  import { SidebarTenantMenu } from './SidebarTenantMenu';
7
11
  import { SidebarUserMenu } from './SidebarUserMenu';
12
+ import { SidebarTenantMenuHeading, SidebarUserMenuHeading } from './SidebarMenuHeading';
8
13
  import { SidebarNavigationItem } from './SidebarNavigationItem';
14
+ import { SidebarContext } from './SidebarContext';
9
15
  import { useSidebarResize } from './hooks/useSidebarResize';
10
- function SidebarBase({ className, logoSlot, mainNavigationItems, extraNavigationSlot, footerSlot, serviceMenuSlot, footerIconMenuSlot, tenantSlot, userSlot, minWidth, maxWidth, defaultWidth, onWidthChange, mainNavigationLabel = 'メインナビゲーション', extraNavigationLabel = '追加ナビゲーション', resizeHandleLabel = 'サイドバーの幅を調整', }) {
16
+ function SidebarBase({ className, logoSlot, mainNavigationItems, extraNavigationSlot, footerSlot, serviceMenuSlot, footerIconMenuSlot, tenantSlot, userSlot, minWidth, maxWidth, defaultWidth, onWidthChange, enableCollapsible = false, isCollapsed: controlledIsCollapsed, onCollapsedChange, collapseButtonLabel = 'ナビゲーションを折りたたむ', expandButtonLabel = 'ナビゲーションを拡げる', mainNavigationLabel = 'メインナビゲーション', extraNavigationLabel = '追加ナビゲーション', resizeHandleLabel = 'サイドバーの幅を調整', }) {
17
+ const [localIsCollapsed, setLocalIsCollapsed] = useState(controlledIsCollapsed ?? false);
18
+ const isControlled = controlledIsCollapsed !== undefined;
19
+ const isCollapsed = enableCollapsible ? (isControlled ? controlledIsCollapsed : localIsCollapsed) : false;
20
+ const navId = useId();
11
21
  const classes = sidebarSlotRecipe();
12
22
  const { width, minWidth: resolvedMinWidth, maxWidth: resolvedMaxWidth, rootRef, isDragging, handlePointerDown, handleKeyDown, } = useSidebarResize({
13
23
  minWidth,
14
24
  maxWidth,
15
25
  defaultWidth,
16
26
  onWidthChange,
27
+ isCollapsed,
17
28
  });
18
- return (_jsxs("div", { ref: rootRef, className: cx(classes.root, 'mfui-Sidebar__root', className), style: { width: `${String(width)}px` }, "data-mfui-dragging": isDragging ? true : undefined, children: [_jsx("header", { className: cx(classes.header, 'mfui-Sidebar__header'), children: _jsxs("div", { className: cx(classes.leftPane, 'mfui-Sidebar__leftPane'), children: [serviceMenuSlot ? (_jsx("div", { className: cx(classes.serviceMenu, 'mfui-Sidebar__serviceMenu'), children: serviceMenuSlot })) : null, _jsx("div", { className: cx(classes.logo, 'mfui-Sidebar__logo'), children: logoSlot })] }) }), _jsxs("div", { className: cx(classes.navigationWrapper, 'mfui-Sidebar__navigationWrapper'), children: [mainNavigationItems && mainNavigationItems.length > 0 ? (_jsx("nav", { "aria-label": mainNavigationLabel, className: cx(classes.mainNavigation, 'mfui-Sidebar__mainNavigation'), children: _jsx("ul", { className: cx(classes.navigationList, 'mfui-Sidebar__navigationList'), children: mainNavigationItems.map((item, index) => {
19
- if ('type' in item) {
20
- return (_jsx("li", { "aria-hidden": "true", className: cx(classes.navigationDivider, 'mfui-Sidebar__navigationDivider') }, index));
21
- }
22
- return _jsx(SidebarNavigationItem, { ...item }, index);
23
- }) }) })) : null, extraNavigationSlot ? (_jsx("nav", { "aria-label": extraNavigationLabel, className: cx(classes.extraNavigation, 'mfui-Sidebar__extraNavigation'), children: extraNavigationSlot })) : null] }), _jsxs("div", { className: cx(classes.footer, 'mfui-Sidebar__footer'), children: [footerSlot ? _jsx("div", { className: cx(classes.footerSlot, 'mfui-Sidebar__footerSlot'), children: footerSlot }) : null, _jsx("div", { className: cx(classes.footerIconMenu, 'mfui-Sidebar__footerIconMenu'), children: footerIconMenuSlot }), tenantSlot || userSlot ? (_jsxs("div", { className: cx(classes.footerUserIdMenu, 'mfui-Sidebar__footerUserIdMenu'), children: [tenantSlot ? (_jsx("div", { className: cx(classes.footerUserIdMenuItem, 'mfui-Sidebar__footerUserIdMenuItem'), children: tenantSlot })) : null, userSlot ? (_jsx("div", { className: cx(classes.footerUserIdMenuItem, 'mfui-Sidebar__footerUserIdMenuItem'), children: userSlot })) : null] })) : null] }), _jsx("div", { "data-mfui-handle": true, "data-mfui-dragging": isDragging ? true : undefined, role: "separator", "aria-orientation": "vertical", "aria-label": resizeHandleLabel, "aria-valuenow": width, "aria-valuemin": resolvedMinWidth, "aria-valuemax": resolvedMaxWidth,
24
- // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
25
- tabIndex: 0, className: cx(classes.handle, 'mfui-Sidebar__handle'), onPointerDown: handlePointerDown, onKeyDown: handleKeyDown })] }));
29
+ const hasMainNavigation = !!mainNavigationItems?.length;
30
+ const toggleButtonLabel = isCollapsed ? expandButtonLabel : collapseButtonLabel;
31
+ const contextValue = useMemo(() => ({ isCollapsed }), [isCollapsed]);
32
+ const handleToggle = () => {
33
+ const next = !isCollapsed;
34
+ if (!isControlled) {
35
+ setLocalIsCollapsed(next);
36
+ }
37
+ onCollapsedChange?.(next);
38
+ };
39
+ return (_jsx(SidebarContext.Provider, { value: contextValue, children: _jsxs("div", { ref: rootRef, className: cx(classes.root, 'mfui-Sidebar__root', className), style: { width: `${String(width)}px` }, "data-mfui-collapsed": isCollapsed ? true : undefined, "data-mfui-dragging": isDragging ? true : undefined, children: [_jsxs("header", { className: cx(classes.header, 'mfui-Sidebar__header'), children: [_jsx("div", { className: cx(classes.leftPane, 'mfui-Sidebar__leftPane'), children: !isCollapsed ? (_jsxs(_Fragment, { children: [serviceMenuSlot ? (_jsx("div", { className: cx(classes.serviceMenu, 'mfui-Sidebar__serviceMenu'), children: serviceMenuSlot })) : null, _jsx("div", { className: cx(classes.logo, 'mfui-Sidebar__logo'), children: logoSlot })] })) : null }), enableCollapsible ? (_jsx("div", { className: cx(classes.rightPane, 'mfui-Sidebar__rightPane'), children: _jsx(SidebarToggleButton, { isCollapsed: isCollapsed, ariaLabel: toggleButtonLabel, navId: hasMainNavigation ? navId : undefined, onClick: handleToggle }) })) : null] }), _jsxs("div", { className: cx(classes.navigationWrapper, 'mfui-Sidebar__navigationWrapper'), children: [hasMainNavigation ? (_jsx("nav", { id: navId, "aria-label": mainNavigationLabel, className: cx(classes.mainNavigation, 'mfui-Sidebar__mainNavigation'), children: _jsx("ul", { className: cx(classes.navigationList, 'mfui-Sidebar__navigationList'), children: mainNavigationItems.map((item, index) => {
40
+ if ('type' in item) {
41
+ return (_jsx("li", { "aria-hidden": "true", className: cx(classes.navigationDivider, 'mfui-Sidebar__navigationDivider') }, index));
42
+ }
43
+ return _jsx(SidebarNavigationItem, { ...item }, index);
44
+ }) }) })) : null, extraNavigationSlot ? (_jsx("nav", { "aria-label": extraNavigationLabel, className: cx(classes.extraNavigation, 'mfui-Sidebar__extraNavigation'), children: extraNavigationSlot })) : null] }), _jsxs("div", { className: cx(classes.footer, 'mfui-Sidebar__footer'), children: [footerSlot ? _jsx("div", { className: cx(classes.footerSlot, 'mfui-Sidebar__footerSlot'), children: footerSlot }) : null, _jsx("div", { className: cx(classes.footerIconMenu, 'mfui-Sidebar__footerIconMenu'), children: typeof footerIconMenuSlot === 'function' ? footerIconMenuSlot({ isCollapsed }) : footerIconMenuSlot }), !isCollapsed && (tenantSlot || userSlot) ? (_jsxs("div", { className: cx(classes.footerUserIdMenu, 'mfui-Sidebar__footerUserIdMenu'), children: [tenantSlot ? (_jsx("div", { className: cx(classes.footerUserIdMenuItem, 'mfui-Sidebar__footerUserIdMenuItem'), children: tenantSlot })) : null, userSlot ? (_jsx("div", { className: cx(classes.footerUserIdMenuItem, 'mfui-Sidebar__footerUserIdMenuItem'), children: userSlot })) : null] })) : null] }), _jsx("div", { "data-mfui-handle": true, "data-mfui-dragging": isDragging ? true : undefined, "data-mfui-collapsed": isCollapsed ? true : undefined, role: isCollapsed ? undefined : 'separator', "aria-orientation": isCollapsed ? undefined : 'vertical', "aria-label": isCollapsed ? undefined : resizeHandleLabel, "aria-valuenow": isCollapsed ? undefined : width, "aria-valuemin": isCollapsed ? undefined : resolvedMinWidth, "aria-valuemax": isCollapsed ? undefined : resolvedMaxWidth, tabIndex: isCollapsed ? -1 : 0, className: cx(classes.handle, 'mfui-Sidebar__handle'), onPointerDown: handlePointerDown, onKeyDown: handleKeyDown })] }) }));
45
+ }
46
+ function SidebarToggleButton({ isCollapsed, ariaLabel, navId, onClick, }) {
47
+ return (_jsx(Tooltip, { content: ariaLabel, placement: isCollapsed ? 'right' : 'bottom', children: _jsx(IconButton, { "aria-pressed": !isCollapsed, "aria-controls": navId, "aria-label": ariaLabel, className: "mfui-Sidebar__toggleButton", onClick: onClick, children: _jsx(SidebarController, { "aria-hidden": true }) }) }));
26
48
  }
27
49
  /**
28
50
  * The sidebar component.
@@ -40,6 +62,10 @@ function SidebarBase({ className, logoSlot, mainNavigationItems, extraNavigation
40
62
  * - `Sidebar.ServiceMenu` — the header service menu trigger, intended to wrap a list of services.
41
63
  * - `Sidebar.TenantMenu` — the footer tenant (business) name and its menu.
42
64
  * - `Sidebar.UserMenu` — the footer user (account) name and its menu.
65
+ * - `Sidebar.TenantMenuHeading` — the heading block inside `Sidebar.TenantMenu`: the tenant name and
66
+ * its identification-number row (optionally copyable to the clipboard).
67
+ * - `Sidebar.UserMenuHeading` — the heading block inside `Sidebar.UserMenu`: the user name and
68
+ * its identification-number row (optionally copyable to the clipboard).
43
69
  *
44
70
  * @example
45
71
  * ```tsx
@@ -79,4 +105,8 @@ export const Sidebar = Object.assign(SidebarBase, {
79
105
  TenantMenu: SidebarTenantMenu,
80
106
  /** @see {@link SidebarUserMenu} */
81
107
  UserMenu: SidebarUserMenu,
108
+ /** @see {@link SidebarTenantMenuHeading} */
109
+ TenantMenuHeading: SidebarTenantMenuHeading,
110
+ /** @see {@link SidebarUserMenuHeading} */
111
+ UserMenuHeading: SidebarUserMenuHeading,
82
112
  });
@@ -54,6 +54,12 @@ export type SidebarNavigationDivider = {
54
54
  type: 'divider';
55
55
  };
56
56
  export type SidebarNavigationEntry = SidebarNavigationItem | SidebarNavigationDivider;
57
+ export type SidebarFooterIconMenuSlotState = {
58
+ /**
59
+ * Whether the sidebar is currently collapsed.
60
+ */
61
+ isCollapsed: boolean;
62
+ };
57
63
  export type SidebarProps = {
58
64
  /**
59
65
  * Additional class names to apply to the component.
@@ -85,9 +91,10 @@ export type SidebarProps = {
85
91
  serviceMenuSlot: ReactNode;
86
92
  /**
87
93
  * Elements to be displayed in the footer icon menu area.
88
- * Icon buttons with dropdowns should open upward (placement: top).
94
+ * Pass a function to adapt the content to the current collapsed state, such as switching tooltip placement.
95
+ * The plain node form still works for static content.
89
96
  */
90
- footerIconMenuSlot: ReactNode;
97
+ footerIconMenuSlot: ReactNode | ((state: SidebarFooterIconMenuSlotState) => ReactNode);
91
98
  /**
92
99
  * Elements to be displayed below the footer icon menu.
93
100
  * Intended for the tenant (business) name and its menu.
@@ -121,6 +128,36 @@ export type SidebarProps = {
121
128
  * Use this to persist the value in your own storage (e.g. via an API call).
122
129
  */
123
130
  onWidthChange: (width: number) => void;
131
+ /**
132
+ * Whether the sidebar can be collapsed to an icon-only rail.
133
+ * When `true`, a toggle button is rendered in the header.
134
+ *
135
+ * @default false
136
+ */
137
+ enableCollapsible?: boolean;
138
+ /**
139
+ * The collapsed state of the sidebar (controlled).
140
+ * When provided, the component behaves as a controlled component.
141
+ * Only effective when `enableCollapsible` is `true`.
142
+ */
143
+ isCollapsed?: boolean;
144
+ /**
145
+ * Called when the collapse toggle is clicked with the new collapsed state.
146
+ * Only called when `enableCollapsible` is `true`.
147
+ */
148
+ onCollapsedChange?: (isCollapsed: boolean) => void;
149
+ /**
150
+ * Accessible name for the toggle button when expanded (clicking will collapse).
151
+ *
152
+ * @default 'ナビゲーションを折りたたむ'
153
+ */
154
+ collapseButtonLabel?: string;
155
+ /**
156
+ * Accessible name for the toggle button when collapsed (clicking will expand).
157
+ *
158
+ * @default 'ナビゲーションを拡げる'
159
+ */
160
+ expandButtonLabel?: string;
124
161
  /**
125
162
  * The accessible name for the primary navigation landmark.
126
163
  *
@@ -0,0 +1,3 @@
1
+ export declare const SidebarContext: import("react").Context<{
2
+ isCollapsed: boolean;
3
+ }>;
@@ -0,0 +1,2 @@
1
+ import { createContext } from 'react';
2
+ export const SidebarContext = createContext({ isCollapsed: false });
@@ -0,0 +1,49 @@
1
+ import { type SidebarTenantMenuHeadingProps, type SidebarUserMenuHeadingProps } from './SidebarMenuHeading.types';
2
+ /**
3
+ * The heading block of the Sidebar tenant menu: the tenant name heading and the
4
+ * identification-number row. Place it as the first child of `Sidebar.TenantMenu`.
5
+ *
6
+ * Pass the `copy` prop to make the identification-number row copy its value to the clipboard,
7
+ * with a tooltip and a screen-reader announcement. The feedback after copying reads `コピーしました`
8
+ * by default and can be changed via `copy.copiedLabel`. Without `copy`, the row is static text.
9
+ *
10
+ * @param props - The props of the `Sidebar.TenantMenuHeading` component.
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * <Sidebar.TenantMenu name="株式会社サンプル" tooltipLabel="事業者">
15
+ * <Sidebar.TenantMenuHeading
16
+ * name="株式会社サンプル"
17
+ * identificationNumber="事業者番号:0123-4567"
18
+ * copy={{ text: '0123-4567', label: '事業者番号をコピーする' }}
19
+ * />
20
+ * <DropdownMenu.Divider />
21
+ * <DropdownMenu.Item href="https://example.com">事業者設定</DropdownMenu.Item>
22
+ * </Sidebar.TenantMenu>
23
+ * ```
24
+ */
25
+ export declare function SidebarTenantMenuHeading(props: SidebarTenantMenuHeadingProps): import("react/jsx-runtime").JSX.Element;
26
+ /**
27
+ * The heading block of the Sidebar user menu: the user (account) name heading and the
28
+ * identification-number row. Place it as the first child of `Sidebar.UserMenu`.
29
+ *
30
+ * Pass the `copy` prop to make the identification-number row copy its value to the clipboard,
31
+ * with a tooltip and a screen-reader announcement. The feedback after copying reads `コピーしました`
32
+ * by default and can be changed via `copy.copiedLabel`. Without `copy`, the row is static text.
33
+ *
34
+ * @param props - The props of the `Sidebar.UserMenuHeading` component.
35
+ *
36
+ * @example
37
+ * ```tsx
38
+ * <Sidebar.UserMenu name="山田 太郎" tooltipLabel="アカウント">
39
+ * <Sidebar.UserMenuHeading
40
+ * name="山田 太郎"
41
+ * identificationNumber="従業員番号:1234"
42
+ * copy={{ text: '1234', label: '従業員番号をコピーする' }}
43
+ * />
44
+ * <DropdownMenu.Divider />
45
+ * <DropdownMenu.Item onClick={handleLogout}>ログアウト</DropdownMenu.Item>
46
+ * </Sidebar.UserMenu>
47
+ * ```
48
+ */
49
+ export declare function SidebarUserMenuHeading(props: SidebarUserMenuHeadingProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,113 @@
1
+ 'use client';
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { Copy, Current } from '@moneyforward/mfui-icons-react';
5
+ import { cx } from '../../../styled-system/css';
6
+ import { sidebarMenuHeadingSlotRecipe } from '../../../styled-system/recipes';
7
+ import { DropdownMenu } from '../../DropdownMenu';
8
+ import { Tooltip } from '../../Tooltip';
9
+ import { CLOSE_DELAY } from '../../Tooltip/Tooltip.constants';
10
+ import { Typography } from '../../Typography';
11
+ // How long the "copied" feedback stays while the user remains on the row.
12
+ const COPIED_FEEDBACK_DURATION_MS = 5000;
13
+ // Copy-to-clipboard feedback for the identification-number row: `copied` drives the tooltip text,
14
+ // the icon swap, and the live region; the handlers are spread onto the `DropdownMenu.Item`.
15
+ function useCopyFeedback(text) {
16
+ const [copied, setCopied] = useState(false);
17
+ const timerRef = useRef(undefined);
18
+ useEffect(() => () => {
19
+ window.clearTimeout(timerRef.current);
20
+ }, []);
21
+ // On blur (keyboard focus moved on), dismiss immediately so the tooltip never lingers next to a
22
+ // newly focused item; the copy result is still announced by the `role="status"` live region.
23
+ const onBlur = () => {
24
+ window.clearTimeout(timerRef.current);
25
+ setCopied(false);
26
+ };
27
+ // On mouse leave, dismiss after the same delay as the Tooltip's own close behavior.
28
+ const onMouseLeave = () => {
29
+ window.clearTimeout(timerRef.current);
30
+ timerRef.current = window.setTimeout(() => {
31
+ setCopied(false);
32
+ }, CLOSE_DELAY);
33
+ };
34
+ // Copy the text, keep the menu open, and show the "copied" feedback (tooltip + icon) while the
35
+ // user stays on the row. The feedback only appears once the clipboard write succeeds; on failure
36
+ // the row keeps its copy label so the user can retry.
37
+ const onClick = (event) => {
38
+ event.preventDefault(); // keep the menu open after copying
39
+ navigator.clipboard
40
+ .writeText(text ?? '')
41
+ .then(() => {
42
+ setCopied(true);
43
+ window.clearTimeout(timerRef.current);
44
+ timerRef.current = window.setTimeout(() => {
45
+ setCopied(false);
46
+ }, COPIED_FEEDBACK_DURATION_MS);
47
+ })
48
+ .catch(() => {
49
+ // Swallow the rejection (insecure context, missing clipboard-write permission, etc.) so it
50
+ // does not surface as an unhandled promise rejection.
51
+ });
52
+ };
53
+ return { copied, onClick, onBlur, onMouseLeave };
54
+ }
55
+ function SidebarMenuHeading({ name, identificationNumber, copy }) {
56
+ const classes = sidebarMenuHeadingSlotRecipe();
57
+ const { copied, ...copyHandlers } = useCopyFeedback(copy?.text);
58
+ const copiedLabel = copy?.copiedLabel ?? 'コピーしました';
59
+ const numberRow = (_jsxs(DropdownMenu.Item, { className: cx(classes.numberRow, 'mfui-SidebarMenuHeading__numberRow'), ...(copy ? copyHandlers : {}), children: [_jsx(Typography, { className: cx(classes.numberText, 'mfui-SidebarMenuHeading__numberText'), children: identificationNumber }), copy ? (copied ? (_jsx(Current, { "aria-hidden": true, className: cx(classes.numberIcon, 'mfui-SidebarMenuHeading__numberIcon') })) : (_jsx(Copy, { "aria-hidden": true, className: cx(classes.numberIcon, 'mfui-SidebarMenuHeading__numberIcon') }))) : null] }));
60
+ return (_jsxs(_Fragment, { children: [_jsx(DropdownMenu.Heading, { children: name }), copy ? (_jsxs(_Fragment, { children: [_jsx(Tooltip, { content: copied ? copiedLabel : copy.label, open: copied || undefined, placement: "right", targetDOMNode: typeof document === 'undefined' ? undefined : document.body, className: cx(classes.tooltip, 'mfui-SidebarMenuHeading__tooltip'), children: numberRow }), _jsx("span", { role: "status", className: cx(classes.status, 'mfui-SidebarMenuHeading__status'), children: copied ? copiedLabel : '' })] })) : (numberRow)] }));
61
+ }
62
+ /**
63
+ * The heading block of the Sidebar tenant menu: the tenant name heading and the
64
+ * identification-number row. Place it as the first child of `Sidebar.TenantMenu`.
65
+ *
66
+ * Pass the `copy` prop to make the identification-number row copy its value to the clipboard,
67
+ * with a tooltip and a screen-reader announcement. The feedback after copying reads `コピーしました`
68
+ * by default and can be changed via `copy.copiedLabel`. Without `copy`, the row is static text.
69
+ *
70
+ * @param props - The props of the `Sidebar.TenantMenuHeading` component.
71
+ *
72
+ * @example
73
+ * ```tsx
74
+ * <Sidebar.TenantMenu name="株式会社サンプル" tooltipLabel="事業者">
75
+ * <Sidebar.TenantMenuHeading
76
+ * name="株式会社サンプル"
77
+ * identificationNumber="事業者番号:0123-4567"
78
+ * copy={{ text: '0123-4567', label: '事業者番号をコピーする' }}
79
+ * />
80
+ * <DropdownMenu.Divider />
81
+ * <DropdownMenu.Item href="https://example.com">事業者設定</DropdownMenu.Item>
82
+ * </Sidebar.TenantMenu>
83
+ * ```
84
+ */
85
+ export function SidebarTenantMenuHeading(props) {
86
+ return _jsx(SidebarMenuHeading, { ...props });
87
+ }
88
+ /**
89
+ * The heading block of the Sidebar user menu: the user (account) name heading and the
90
+ * identification-number row. Place it as the first child of `Sidebar.UserMenu`.
91
+ *
92
+ * Pass the `copy` prop to make the identification-number row copy its value to the clipboard,
93
+ * with a tooltip and a screen-reader announcement. The feedback after copying reads `コピーしました`
94
+ * by default and can be changed via `copy.copiedLabel`. Without `copy`, the row is static text.
95
+ *
96
+ * @param props - The props of the `Sidebar.UserMenuHeading` component.
97
+ *
98
+ * @example
99
+ * ```tsx
100
+ * <Sidebar.UserMenu name="山田 太郎" tooltipLabel="アカウント">
101
+ * <Sidebar.UserMenuHeading
102
+ * name="山田 太郎"
103
+ * identificationNumber="従業員番号:1234"
104
+ * copy={{ text: '1234', label: '従業員番号をコピーする' }}
105
+ * />
106
+ * <DropdownMenu.Divider />
107
+ * <DropdownMenu.Item onClick={handleLogout}>ログアウト</DropdownMenu.Item>
108
+ * </Sidebar.UserMenu>
109
+ * ```
110
+ */
111
+ export function SidebarUserMenuHeading(props) {
112
+ return _jsx(SidebarMenuHeading, { ...props });
113
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The copy-to-clipboard settings for the identification-number row.
3
+ */
4
+ export type SidebarMenuHeadingCopy = {
5
+ /**
6
+ * The text written to the clipboard when the row is selected (e.g. `"0123-4567"`).
7
+ */
8
+ text: string;
9
+ /**
10
+ * The tooltip label prompting the copy action (e.g. `"事業者番号をコピーする"`).
11
+ */
12
+ label: string;
13
+ /**
14
+ * The label shown in the tooltip and announced to screen readers right after copying.
15
+ *
16
+ * @default 'コピーしました'
17
+ */
18
+ copiedLabel?: string;
19
+ };
20
+ export type SidebarMenuHeadingProps = {
21
+ /**
22
+ * The tenant (business) or user (account) name shown as the menu heading.
23
+ */
24
+ name: string;
25
+ /**
26
+ * The identification-number row text (e.g. `"事業者番号:0123-4567"`).
27
+ */
28
+ identificationNumber: string;
29
+ /**
30
+ * Enables copy-to-clipboard on the identification-number row: the row becomes a menu item button
31
+ * with a copy icon, a tooltip, and a screen-reader announcement. When omitted, the row is static text.
32
+ */
33
+ copy?: SidebarMenuHeadingCopy;
34
+ };
35
+ export type SidebarTenantMenuHeadingProps = SidebarMenuHeadingProps;
36
+ export type SidebarUserMenuHeadingProps = SidebarMenuHeadingProps;