@moneyforward/mfui-components 4.3.0 → 4.5.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 (73) hide show
  1. package/dist/src/Accordion/Accordion.d.ts +21 -1
  2. package/dist/src/Accordion/Accordion.js +28 -4
  3. package/dist/src/Accordion/Accordion.types.d.ts +21 -0
  4. package/dist/src/Breadcrumbs/Breadcrumbs.js +1 -1
  5. package/dist/src/CheckboxCard/CheckboxCard.js +3 -1
  6. package/dist/src/DataTable/DataTable.js +9 -2
  7. package/dist/src/DataTable/DataTable.types.d.ts +15 -0
  8. package/dist/src/DataTable/DataTableHeaderRow/DataTableHeaderRow.js +3 -1
  9. package/dist/src/DataTable/DataTableHeaderRow/DataTableHeaderRow.types.d.ts +19 -1
  10. package/dist/src/DataTable/DataTableRow/DataTableRow.js +4 -1
  11. package/dist/src/DataTable/DataTableRow/DataTableRow.types.d.ts +23 -1
  12. package/dist/src/DateTimeSelection/DatePicker/DatePickerDateCell/index.d.ts +1 -1
  13. package/dist/src/DateTimeSelection/DatePicker/DatePickerDateCell/index.js +0 -1
  14. package/dist/src/DateTimeSelection/shared/CalendarGrid/CalendarGrid.js +1 -3
  15. package/dist/src/DateTimeSelection/shared/CalendarGrid/utilities/computeDataForStyling.js +2 -4
  16. package/dist/src/Dialog/Dialog.d.ts +1 -1
  17. package/dist/src/Dialog/Dialog.types.d.ts +1 -1
  18. package/dist/src/FileDropZone/FileDropZone.d.ts +5 -1
  19. package/dist/src/FileDropZone/FileDropZone.js +26 -6
  20. package/dist/src/FileDropZone/FileDropZone.types.d.ts +10 -0
  21. package/dist/src/MainNavigation/BaseMainNavigation.d.ts +1 -1
  22. package/dist/src/MainNavigation/BaseMainNavigation.js +2 -2
  23. package/dist/src/MainNavigation/MainNavigation.js +2 -1
  24. package/dist/src/MainNavigation/MainNavigation.types.d.ts +9 -0
  25. package/dist/src/MainNavigation/NarrowViewportMainNavigation.js +2 -2
  26. package/dist/src/MultipleSelectBox/MultipleSelectBox.js +27 -18
  27. package/dist/src/MultipleSelectBox/hooks/useInitialFocusOnOptionPanelOpen.js +1 -1
  28. package/dist/src/NavigationList/NavigationListItem/NavigationListItem.d.ts +3 -0
  29. package/dist/src/NavigationList/NavigationListItem/NavigationListItem.js +6 -2
  30. package/dist/src/NavigationList/NavigationListItem/NavigationListItem.types.d.ts +2 -1
  31. package/dist/src/NumberBox/NumberBox.d.ts +5 -0
  32. package/dist/src/NumberBox/NumberBox.js +55 -3
  33. package/dist/src/NumberBox/NumberBox.types.d.ts +35 -0
  34. package/dist/src/Pagination/Pagination.js +4 -1
  35. package/dist/src/Popover/Popover.js +3 -3
  36. package/dist/src/RadioButtonCard/RadioButtonCard.js +3 -1
  37. package/dist/src/SelectBox/SelectBox.js +20 -11
  38. package/dist/src/SelectBox/utils/getFlatOptions.js +1 -1
  39. package/dist/src/SidePane/SidePane.js +87 -7
  40. package/dist/src/SidePane/SidePane.types.d.ts +39 -0
  41. package/dist/src/SidePane/hooks/useSidePaneStateController.js +3 -5
  42. package/dist/src/SplitView/utils/parseSize.d.ts +1 -1
  43. package/dist/src/SplitView/utils/parseSize.js +4 -2
  44. package/dist/src/SubNavigation/SubNavigation.d.ts +10 -1
  45. package/dist/src/SubNavigation/SubNavigation.js +14 -3
  46. package/dist/src/SubNavigation/SubNavigation.types.d.ts +27 -1
  47. package/dist/src/Tabs/TabContext.d.ts +3 -1
  48. package/dist/src/Tabs/TabContext.js +1 -0
  49. package/dist/src/Tabs/TabItem/TabItem.js +14 -3
  50. package/dist/src/Tabs/Tabs.js +5 -21
  51. package/dist/src/Tabs/hooks/useTabRegistry.d.ts +24 -0
  52. package/dist/src/Tabs/hooks/useTabRegistry.js +130 -0
  53. package/dist/src/ToggleSwitch/index.d.ts +1 -1
  54. package/dist/src/ToggleSwitch/index.js +0 -1
  55. package/dist/src/Tooltip/hooks/useOpenStateDelayed.js +2 -2
  56. package/dist/src/utilities/dom/containsAcrossShadowRoots.d.ts +21 -0
  57. package/dist/src/utilities/dom/containsAcrossShadowRoots.js +33 -0
  58. package/dist/src/utilities/dom/setInputFiles.d.ts +21 -0
  59. package/dist/src/utilities/dom/setInputFiles.js +41 -0
  60. package/dist/src/utilities/dom/toFileList.d.ts +20 -0
  61. package/dist/src/utilities/dom/toFileList.js +26 -0
  62. package/dist/src/utilities/dom/useFixedColumns.js +4 -2
  63. package/dist/src/utilities/dom/useFocusTrap.js +1 -1
  64. package/dist/src/utilities/dom/useOnClickOutside.js +3 -1
  65. package/dist/src/utilities/dom/useTableEdgeCellMarkers.d.ts +56 -0
  66. package/dist/src/utilities/dom/useTableEdgeCellMarkers.js +223 -0
  67. package/dist/styled-system/recipes/data-table-cell-slot-recipe.js +4 -2
  68. package/dist/styled-system/types/composition.d.ts +1 -1
  69. package/dist/styles.css +20 -4
  70. package/dist/tsconfig.build.tsbuildinfo +1 -1
  71. package/package.json +3 -3
  72. /package/dist/src/{MultipleSelectBox/utils → utilities/dom}/getDeepActiveElement.d.ts +0 -0
  73. /package/dist/src/{MultipleSelectBox/utils → utilities/dom}/getDeepActiveElement.js +0 -0
@@ -1,6 +1,6 @@
1
1
  import { type AccordionProps, type AccordionItemProps } from './Accordion.types';
2
2
  declare function AccordionBase({ children, size, width, defaultOpenItems }: AccordionProps): import("react/jsx-runtime").JSX.Element;
3
- declare function AccordionItem({ children, id, enableAutoUnmount }: AccordionItemProps): import("react/jsx-runtime").JSX.Element;
3
+ declare function AccordionItem({ children, id, enableAutoUnmount, open, onOpenStateChanged }: AccordionItemProps): import("react/jsx-runtime").JSX.Element;
4
4
  /**
5
5
  * The `Accordion` component.
6
6
  *
@@ -24,6 +24,26 @@ declare function AccordionItem({ children, id, enableAutoUnmount }: AccordionIte
24
24
  * </Accordion.Item>
25
25
  * </Accordion>
26
26
  * ```
27
+ *
28
+ * Each `Accordion.Item` can also be controlled with the `open` and `onOpenStateChanged` props, which lets a trigger
29
+ * outside the Accordion tree drive it. In that case you own the `aria-expanded` and `aria-controls` attributes of
30
+ * your own trigger, and `enableAutoUnmount={false}` keeps the collapsed content mounted.
31
+ *
32
+ * @example
33
+ * ```jsx
34
+ * const [isOpen, setIsOpen] = useState(false);
35
+ *
36
+ * <>
37
+ * <button type="button" aria-expanded={isOpen} aria-controls="accordion-1" onClick={() => setIsOpen(!isOpen)}>
38
+ * Toggle
39
+ * </button>
40
+ * <Accordion>
41
+ * <Accordion.Item id="accordion-1" open={isOpen} enableAutoUnmount={false}>
42
+ * <Accordion.Target>Content for section 1</Accordion.Target>
43
+ * </Accordion.Item>
44
+ * </Accordion>
45
+ * </>
46
+ * ```
27
47
  */
28
48
  export declare const Accordion: typeof AccordionBase & {
29
49
  /**
@@ -41,12 +41,16 @@ function AccordionBase({ children, size = 'medium', width, defaultOpenItems = []
41
41
  };
42
42
  return (_jsx("div", { style: containerStyle, children: _jsx(AccordionContext.Provider, { value: contextValue, children: children }) }));
43
43
  }
44
- function AccordionItem({ children, id, enableAutoUnmount = true }) {
44
+ function AccordionItem({ children, id, enableAutoUnmount = true, open, onOpenStateChanged }) {
45
45
  const { openItems, toggleItem } = useAccordionContext();
46
- const isOpen = openItems[id] || false;
46
+ const isControlled = open !== undefined;
47
+ const isOpen = open ?? openItems[id] ?? false;
47
48
  const toggle = useCallback(() => {
48
- toggleItem(id);
49
- }, [toggleItem, id]);
49
+ if (!isControlled) {
50
+ toggleItem(id);
51
+ }
52
+ onOpenStateChanged?.(!isOpen);
53
+ }, [isControlled, toggleItem, id, onOpenStateChanged, isOpen]);
50
54
  const contextValue = useMemo(() => ({
51
55
  isOpen,
52
56
  toggle,
@@ -100,6 +104,26 @@ const AccordionTarget = forwardRef(({ children, className, style, ...props }, re
100
104
  * </Accordion.Item>
101
105
  * </Accordion>
102
106
  * ```
107
+ *
108
+ * Each `Accordion.Item` can also be controlled with the `open` and `onOpenStateChanged` props, which lets a trigger
109
+ * outside the Accordion tree drive it. In that case you own the `aria-expanded` and `aria-controls` attributes of
110
+ * your own trigger, and `enableAutoUnmount={false}` keeps the collapsed content mounted.
111
+ *
112
+ * @example
113
+ * ```jsx
114
+ * const [isOpen, setIsOpen] = useState(false);
115
+ *
116
+ * <>
117
+ * <button type="button" aria-expanded={isOpen} aria-controls="accordion-1" onClick={() => setIsOpen(!isOpen)}>
118
+ * Toggle
119
+ * </button>
120
+ * <Accordion>
121
+ * <Accordion.Item id="accordion-1" open={isOpen} enableAutoUnmount={false}>
122
+ * <Accordion.Target>Content for section 1</Accordion.Target>
123
+ * </Accordion.Item>
124
+ * </Accordion>
125
+ * </>
126
+ * ```
103
127
  */
104
128
  export const Accordion = Object.assign(AccordionBase, {
105
129
  /**
@@ -42,6 +42,27 @@ export type AccordionItemProps = {
42
42
  * @default true
43
43
  */
44
44
  enableAutoUnmount?: boolean;
45
+ /**
46
+ * The open state of this accordion item.
47
+ * You only need to use this prop if you want to control the item programmatically, for example when the trigger
48
+ * lives outside the Accordion tree. While this prop is defined, it takes precedence over the `defaultOpenItems` of
49
+ * the Accordion and over the internal state, so you have to update it yourself in response to
50
+ * `onOpenStateChanged`. Supply it consistently: do not switch an item between controlled and uncontrolled.
51
+ *
52
+ * When you drive the item from your own trigger outside the Accordion, you own its `aria-expanded` and
53
+ * `aria-controls` attributes. `Accordion.Target` renders the item `id` as its DOM `id`, so
54
+ * `aria-controls={id}` points at it. Note that with the default `enableAutoUnmount` the target is unmounted
55
+ * while collapsed, so `aria-controls` refers to an element that does not exist yet; set
56
+ * `enableAutoUnmount={false}` to keep the target in the DOM.
57
+ */
58
+ open?: boolean;
59
+ /**
60
+ * Callback fired when the open state of this accordion item is toggled by `Accordion.Trigger`.
61
+ * It is called in both controlled and uncontrolled mode.
62
+ *
63
+ * @param open - The new open state of the accordion item.
64
+ */
65
+ onOpenStateChanged?: (open: boolean) => void;
45
66
  };
46
67
  /**
47
68
  * Props for the Accordion.Trigger component
@@ -102,7 +102,7 @@ export const Breadcrumbs = forwardRef(({ items, wrap = false, customLinkComponen
102
102
  if (wrap || !listRef.current)
103
103
  return;
104
104
  const observer = new ResizeObserver((entries) => {
105
- const entry = entries[0];
105
+ const [entry] = entries;
106
106
  if (entry) {
107
107
  setContainerWidth(entry.contentRect.width);
108
108
  }
@@ -29,5 +29,7 @@ export const CheckboxCard = forwardRef(({ label, layout = 'vertical', renderDeta
29
29
  disableResponsive,
30
30
  isResponsive: Boolean(responsiveSlot.narrowViewport),
31
31
  });
32
- return (_jsxs("div", { className: cx(classes.root, 'mfui-CheckboxCard__root', className), children: [_jsx(FocusIndicator, { disableNestedFocus: true, children: _jsxs(Typography, { variant: "label", className: cx(classes.inputWrapper, 'mfui-CheckboxCard__inputWrapper'), as: "label", htmlFor: props.id, children: [_jsx(Checkbox, { ref: ref, ...props, className: cx(classes.input, 'mfui-CheckboxCard__input') }), _jsx("span", { className: cx(classes.label, 'mfui-CheckboxCard__label'), children: label })] }) }), responsiveSlot.narrowViewport && disableResponsive ? (_jsxs(_Fragment, { children: [_jsx("div", { "data-mfui-content": "details", className: cx(classes.details, classes.detailsWide, 'mfui-CheckboxCard__details'), children: responsiveSlot.base(checkboxState) }), _jsx("div", { "data-mfui-content": "details", className: cx(classes.details, classes.detailsNarrow, 'mfui-CheckboxCard__details'), children: responsiveSlot.narrowViewport(checkboxState) })] })) : responsiveSlot.narrowViewport ? (_jsxs(_Fragment, { children: [_jsx("div", { "data-mfui-content": "details", className: cx(classes.details, classes.detailsWide, 'mfui-CheckboxCard__details'), children: responsiveSlot.base(checkboxState) }), _jsx("div", { "data-mfui-content": "details", className: cx(classes.details, classes.detailsNarrow, 'mfui-CheckboxCard__details'), children: responsiveSlot.narrowViewport(checkboxState) })] })) : responsiveSlot.base ? (_jsx("div", { "data-mfui-content": "details", className: cx(classes.details, 'mfui-CheckboxCard__details'), children: responsiveSlot.base(checkboxState) })) : null] }));
32
+ return (_jsxs("div", { className: cx(classes.root, 'mfui-CheckboxCard__root', className), children: [_jsx(FocusIndicator, { disableNestedFocus: true, children: _jsxs(Typography, { variant: "label", className: cx(classes.inputWrapper, 'mfui-CheckboxCard__inputWrapper'), as: "label", htmlFor: props.id, children: [_jsx(Checkbox, { ref: ref, ...props, className: cx(classes.input, 'mfui-CheckboxCard__input') }), _jsx("span", { className: cx(classes.label, 'mfui-CheckboxCard__label'), children: label })] }) }), responsiveSlot.narrowViewport ? (_jsxs(_Fragment, { children: [_jsx("div", { "data-mfui-content": "details", className: cx(classes.details, classes.detailsWide, 'mfui-CheckboxCard__details'), children: responsiveSlot.base(checkboxState) }), _jsx("div", { "data-mfui-content": "details", className: cx(classes.details, classes.detailsNarrow, 'mfui-CheckboxCard__details'), children: responsiveSlot.narrowViewport(checkboxState) })] })) : (
33
+ /* For function-based renderDetailsSlot or ResponsiveSlot with only base, use single details element */
34
+ responsiveSlot.base && (_jsx("div", { "data-mfui-content": "details", className: cx(classes.details, 'mfui-CheckboxCard__details'), children: responsiveSlot.base(checkboxState) })))] }));
33
35
  });
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Children, isValidElement, useMemo, forwardRef } from 'react';
3
+ import { Children, isValidElement, useMemo, useRef, forwardRef } from 'react';
4
4
  import { DataTableProvider } from './DataTableProvider';
5
5
  import { dataTableSlotRecipe } from '../../styled-system/recipes';
6
6
  import { DataTableBody } from './DataTableBody';
@@ -13,8 +13,15 @@ import { DataTableRow } from './DataTableRow';
13
13
  import { DataTableSortButton } from './DataTableSortButton';
14
14
  import { cx } from '../../styled-system/css';
15
15
  import { isComponentOrWrapped } from '../utilities/react/isComponentOrWrapped';
16
+ import { mergeRefs } from '../utilities/dom/mergeRefs';
17
+ import { useTableEdgeCellMarkers } from '../utilities/dom/useTableEdgeCellMarkers';
16
18
  const Base = forwardRef(({ className, size = 'medium', enableRowSelection = false, enableRowClickSelection = false, fixedHeader = false, showVerticalBorder = false, leftFixedColumnIndex, rightFixedColumnIndex, layout = 'default', wrapperProps, loading = false, emptyStateSlot, children, ...props }, ref) => {
17
19
  const classes = dataTableSlotRecipe({ layout });
20
+ const tableRef = useRef(null);
21
+ // `:first-child` / `:last-child` cannot tell which grid column a cell renders in, so the
22
+ // table-edge spacing of the cell recipes misfires on the rows a `rowSpan` extends into.
23
+ // This marks the displaced cells so that spacing can be cancelled on them.
24
+ useTableEdgeCellMarkers(tableRef);
18
25
  // Check if DataTable has any rows by looking for DataTable.Body with DataTable.Row children
19
26
  const hasRows = useMemo(() => {
20
27
  if (loading)
@@ -39,7 +46,7 @@ const Base = forwardRef(({ className, size = 'medium', enableRowSelection = fals
39
46
  }, [children, loading]);
40
47
  const shouldShowEmptyState = !hasRows && !!emptyStateSlot && !loading;
41
48
  return (_jsx(DataTableProvider, { size: size, enableRowSelection: enableRowSelection, enableRowClickSelection: enableRowClickSelection, fixedHeader: fixedHeader, showVerticalBorder: showVerticalBorder, leftFixedColumnIndex: leftFixedColumnIndex, rightFixedColumnIndex: rightFixedColumnIndex, loading: loading, layout: layout, children: _jsxs("div", { ...wrapperProps, className: cx(classes.wrapper, 'mfui-DataTable__wrapper',
42
- /* TODO: Remove in v5.0.0 */ 'mfui-DataGrid__wrapper', wrapperProps?.className), children: [_jsx("table", { ref: ref, className: cx(classes.table, 'mfui-DataTable__table',
49
+ /* TODO: Remove in v5.0.0 */ 'mfui-DataGrid__wrapper', wrapperProps?.className), children: [_jsx("table", { ref: mergeRefs(ref, tableRef), className: cx(classes.table, 'mfui-DataTable__table',
43
50
  /* TODO: Remove in v5.0.0 */ 'mfui-DataGrid__table', className), ...props, children: children }), shouldShowEmptyState ? (_jsx("div", { className: cx(classes.emptyState, 'mfui-DataTable__emptyState',
44
51
  /* TODO: Remove in v5.0.0 */ 'mfui-DataGrid__emptyState'), role: "status", "aria-live": "polite", children: emptyStateSlot })) : null] }) }));
45
52
  });
@@ -9,6 +9,15 @@ import { type DataTableSlotRecipeVariant } from '../../styled-system/recipes';
9
9
  * - `amount`: Right-aligned numeric content (amounts, balances, etc.)
10
10
  */
11
11
  export type CellDataType = 'text' | 'amount';
12
+ /**
13
+ * The function that wraps the built-in selection checkbox of `DataTable.HeaderRow` or `DataTable.Row`.
14
+ *
15
+ * It receives the selection checkbox element and returns the content to render in the checkbox cell.
16
+ * The returned content must contain the passed element, because the row owns the checkbox itself.
17
+ *
18
+ * @param checkbox - The built-in selection checkbox element.
19
+ */
20
+ export type DataTableSelectionCheckboxWrapper = (checkbox: ReactNode) => ReactNode;
12
21
  /**
13
22
  * The props for the `DataTable` component.
14
23
  *
@@ -81,6 +90,9 @@ export type DataTableProps = {
81
90
  * If the fixed column width might be changed with any interaction, such as disclosure, removing row or translating,
82
91
  * please add `table-layout: fixed` to the DataTable and specify the width of the each column.
83
92
  *
93
+ * **Not supported together with merged cells.** The index is counted in source order, which a
94
+ * `colSpan` / `rowSpan` shifts, so the fixed column is resolved on the wrong cells.
95
+ *
84
96
  * @default undefined
85
97
  */
86
98
  leftFixedColumnIndex?: number;
@@ -91,6 +103,9 @@ export type DataTableProps = {
91
103
  * If the fixed column width might be changed with any interaction, such as disclosure, removing row or translating,
92
104
  * please add `table-layout: fixed` to the DataTable and specify the width of the each column.
93
105
  *
106
+ * **Not supported together with merged cells.** The index is counted in source order, which a
107
+ * `colSpan` / `rowSpan` shifts, so the fixed column is resolved on the wrong cells.
108
+ *
94
109
  * @default undefined
95
110
  */
96
111
  rightFixedColumnIndex?: number;
@@ -9,13 +9,15 @@ import { isComponentOrWrapped } from '../../utilities/react/isComponentOrWrapped
9
9
  export const DataTableHeaderRow = forwardRef(({ children, checkboxProps, className, ...props }, ref) => {
10
10
  const { enableRowSelection, setNumberColumns } = useDataTableContext();
11
11
  const classes = dataTableHeaderRowSlotRecipe();
12
+ // Building the element before wrapping keeps everything the header row owns on the Checkbox itself.
13
+ const selectionCheckbox = (_jsx(Checkbox, { checked: checkboxProps?.checked, indeterminate: checkboxProps?.indeterminate, "aria-label": checkboxProps?.['aria-label'] ?? 'すべての行を選択', disabled: checkboxProps?.disabled, onChange: checkboxProps?.onChange, onClick: checkboxProps?.onClick }));
12
14
  // Count the number of header cells
13
15
  useEffect(() => {
14
16
  const headerCells = Children.toArray(children).filter((child) => isValidElement(child) && isComponentOrWrapped(child, DataTableHeaderCell));
15
17
  setNumberColumns(headerCells.length);
16
18
  }, [children, enableRowSelection, setNumberColumns]);
17
19
  return (_jsxs("tr", { className: cx(classes.root, 'mfui-DataTableHeaderRow__root',
18
- /* TODO: Remove in v5.0.0 */ 'mfui-DataGridHeaderRow__root', className), ...props, ref: ref, children: [enableRowSelection ? (_jsx(DataTableHeaderCell, { columnIndex: 0, disabled: checkboxProps?.disabled, children: _jsx(Checkbox, { checked: checkboxProps?.checked, indeterminate: checkboxProps?.indeterminate, "aria-label": checkboxProps?.['aria-label'] ?? 'すべての行を選択', disabled: checkboxProps?.disabled, onChange: checkboxProps?.onChange, onClick: checkboxProps?.onClick }) })) : null, Children.map(children, (child, index) => {
20
+ /* TODO: Remove in v5.0.0 */ 'mfui-DataGridHeaderRow__root', className), ...props, ref: ref, children: [enableRowSelection ? (_jsx(DataTableHeaderCell, { columnIndex: 0, disabled: checkboxProps?.disabled, children: checkboxProps?.wrapper ? checkboxProps.wrapper(selectionCheckbox) : selectionCheckbox })) : null, Children.map(children, (child, index) => {
19
21
  if (!isValidElement(child)) {
20
22
  return child;
21
23
  }
@@ -1,5 +1,6 @@
1
1
  import { type ComponentPropsWithRef } from 'react';
2
2
  import { type CheckboxProps } from '../../Checkbox';
3
+ import { type DataTableSelectionCheckboxWrapper } from '../DataTable.types';
3
4
  /**
4
5
  * The props for the `DataTableHeaderRow` component.
5
6
  *
@@ -24,6 +25,23 @@ export type DataTableHeaderRowProps = {
24
25
  * @property onClick - The handler called when the checkbox is clicked (for event propagation control).
25
26
  *
26
27
  * @property disabled - Whether the checkbox is disabled.
28
+ *
29
+ * @property wrapper - The function that wraps the checkbox, for example with the `Tooltip` component.
30
+ * It receives the checkbox element and must render it as it is, because the header row owns the checkbox itself.
31
+ * Keep the `Tooltip` component with its default `trigger` (`['hover', 'focus']`) so that keyboard users can also
32
+ * read the content; a tooltip opened by a click would duplicate the action of the checkbox.
33
+ * When the content restates the accessible name of the checkbox, pass `hideFromScreenReader` to the `Tooltip`
34
+ * component to avoid announcing the same information twice.
35
+ *
36
+ * ```tsx
37
+ * <DataTable.HeaderRow
38
+ * checkboxProps={{
39
+ * wrapper: (checkbox) => <Tooltip content="すべて選択">{checkbox}</Tooltip>,
40
+ * }}
41
+ * >
42
+ * ```
27
43
  */
28
- checkboxProps?: Pick<CheckboxProps, 'aria-label' | 'checked' | 'indeterminate' | 'onChange' | 'disabled' | 'onClick'>;
44
+ checkboxProps?: Pick<CheckboxProps, 'aria-label' | 'checked' | 'indeterminate' | 'onChange' | 'disabled' | 'onClick'> & {
45
+ wrapper?: DataTableSelectionCheckboxWrapper;
46
+ };
29
47
  } & ComponentPropsWithRef<'tr'>;
@@ -62,6 +62,9 @@ export const DataTableRow = forwardRef(({ children, disabled = false, highlighte
62
62
  const toggleSelectionByRowInteraction = () => {
63
63
  selectionCheckboxRef.current?.click();
64
64
  };
65
+ // Building the element before wrapping keeps everything the row owns on the Checkbox itself, so that the
66
+ // ref-based toggle and the recipe selectors keep working with a consumer-supplied wrapper in between.
67
+ const selectionCheckbox = (_jsx(Checkbox, { ref: selectionCheckboxRef, className: cx(classes.selectionCheckbox, 'mfui-DataTableRow__selectionCheckbox'), checked: checkboxProps?.checked, "aria-label": checkboxProps?.['aria-label'] ?? '行を選択', disabled: disabled || checkboxProps?.disabled, tabIndex: isRowClickSelectable ? -1 : undefined, onChange: checkboxProps?.onChange, onClick: checkboxProps?.onClick }));
65
68
  const handleClickRow = (event) => {
66
69
  if (disabled || isClickFromNestedInteractiveElement(event)) {
67
70
  return;
@@ -84,7 +87,7 @@ export const DataTableRow = forwardRef(({ children, disabled = false, highlighte
84
87
  return (_jsxs("tr", { ref: ref, className: cx(classes.root, 'mfui-DataTableRow__root',
85
88
  /* TODO: Remove in v5.0.0 */ 'mfui-DataGridRow__root', className), ...(ariaExpanded !== undefined && { 'aria-expanded': ariaExpanded }), ...props,
86
89
  // When the row is clickable, the role should be 'button' in order to allow screen readers to understand the row is clickable.
87
- role: isInteractive ? 'button' : 'row', tabIndex: isInteractive ? 0 : undefined, ...(disabled && { 'aria-disabled': disabled }), onClick: handleClickRow, onKeyDown: handleKeyDownRow, children: [isSelectable ? (_jsx(DataTableCell, { columnIndex: 0, isRowHighlighted: isRowHighlighted, disabled: disabled, children: _jsx(Checkbox, { ref: selectionCheckboxRef, className: cx(classes.selectionCheckbox, 'mfui-DataTableRow__selectionCheckbox'), checked: checkboxProps?.checked, "aria-label": checkboxProps?.['aria-label'] ?? '行を選択', disabled: disabled || checkboxProps?.disabled, tabIndex: isRowClickSelectable ? -1 : undefined, onChange: checkboxProps?.onChange, onClick: checkboxProps?.onClick }) })) : null, Children.map(children, (child, index) => {
90
+ role: isInteractive ? 'button' : 'row', tabIndex: isInteractive ? 0 : undefined, ...(disabled && { 'aria-disabled': disabled }), onClick: handleClickRow, onKeyDown: handleKeyDownRow, children: [isSelectable ? (_jsx(DataTableCell, { columnIndex: 0, isRowHighlighted: isRowHighlighted, disabled: disabled, children: checkboxProps?.wrapper ? checkboxProps.wrapper(selectionCheckbox) : selectionCheckbox })) : null, Children.map(children, (child, index) => {
88
91
  if (!isValidElement(child)) {
89
92
  return child;
90
93
  }
@@ -1,5 +1,6 @@
1
1
  import { type ComponentPropsWithRef } from 'react';
2
2
  import { type CheckboxProps } from '../../Checkbox';
3
+ import { type DataTableSelectionCheckboxWrapper } from '../DataTable.types';
3
4
  /**
4
5
  * The props for the `DataTableRow` component.
5
6
  *
@@ -38,8 +39,29 @@ export type DataTableRowProps = {
38
39
  * @property onClick - The handler called when the checkbox is clicked (for event propagation control).
39
40
  *
40
41
  * @property disabled - Whether the checkbox is disabled.
42
+ *
43
+ * @property wrapper - The function that wraps the checkbox, for example with the `Tooltip` component.
44
+ * It receives the checkbox element and must render it as it is, because the row owns the checkbox itself.
45
+ * Keep the `Tooltip` component with its default `trigger` (`['hover', 'focus']`) so that keyboard users can also
46
+ * read the content; a tooltip opened by a click would duplicate the action of the checkbox.
47
+ * When the content restates the accessible name of the checkbox, pass `hideFromScreenReader` to the `Tooltip`
48
+ * component to avoid announcing the same information twice.
49
+ *
50
+ * Note that when the `enableRowClickSelection` prop of the `DataTable` component is `true`, the checkbox is removed
51
+ * from the tab order and the focusable element is the row, which is outside the wrapper. A tooltip in the wrapper is
52
+ * then opened by hover only, so do not put information that keyboard users need in it in that case.
53
+ *
54
+ * ```tsx
55
+ * <DataTable.Row
56
+ * checkboxProps={{
57
+ * wrapper: (checkbox) => <Tooltip content="この行を選択">{checkbox}</Tooltip>,
58
+ * }}
59
+ * >
60
+ * ```
41
61
  */
42
- checkboxProps?: Pick<CheckboxProps, 'aria-label' | 'checked' | 'onChange' | 'disabled' | 'onClick'>;
62
+ checkboxProps?: Pick<CheckboxProps, 'aria-label' | 'checked' | 'onChange' | 'disabled' | 'onClick'> & {
63
+ wrapper?: DataTableSelectionCheckboxWrapper;
64
+ };
43
65
  /**
44
66
  * The callback function that is called when the row is clicked.
45
67
  * If provided, the row can be focused and the focus indicator is displayed.
@@ -1,2 +1,2 @@
1
1
  export * from './DatePickerDateCell';
2
- export * from './DatePickerDateCell.types';
2
+ export type * from './DatePickerDateCell.types';
@@ -1,2 +1 @@
1
1
  export * from './DatePickerDateCell';
2
- export * from './DatePickerDateCell.types';
@@ -57,7 +57,7 @@ export function CalendarGrid({ viewingValue, minDate, maxDate, checkDisabledDate
57
57
  let tabbableDateIndex = -1;
58
58
  // First, try to find the selected date if it exists and is not disabled
59
59
  if (value) {
60
- tabbableDateIndex = newDates.findIndex((dateItem) => dateItem && dateItem.selected && !dateItem.disabled);
60
+ tabbableDateIndex = newDates.findIndex((dateItem) => dateItem?.selected && !dateItem.disabled);
61
61
  }
62
62
  // If no selected date or selected date is disabled, try today's date
63
63
  if (tabbableDateIndex === -1) {
@@ -101,8 +101,6 @@ export function CalendarGrid({ viewingValue, minDate, maxDate, checkDisabledDate
101
101
  .map((_, index) => ((weekStart + index) % 7))
102
102
  .map((weekday) => (_jsx(DayCell, { day: weekday }, weekday))), computedPropsForDates.map((dateItem, index) => {
103
103
  // All props are generated internally and guaranteed to have required fields
104
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
105
- // @ts-ignore
106
104
  if (!dateItem) {
107
105
  return (_jsx("div", { className: cx(classes.emptyDateCell, 'mfui-CalendarGrid__emptyDateCell') }, `empty-date-${String(index)}`));
108
106
  }
@@ -14,9 +14,8 @@ export function computeDataForStyling(viewingValue, weekStart) {
14
14
  const firstDateDay = viewingValue.getDay();
15
15
  // The last column of the grid, i.e. the day right before the week starts over
16
16
  const weekEnd = (weekStart + 6) % 7;
17
- const datesHasBorderTopLeftRadius = new Set();
18
17
  // First date of the month has borderTopLeftRadius
19
- datesHasBorderTopLeftRadius.add(1);
18
+ const datesHasBorderTopLeftRadius = new Set([1]);
20
19
  // First date sitting at the start of a row has borderTopLeftRadius
21
20
  const firstRowStartDate = firstDate + ((weekStart - firstDateDay + 7) % 7);
22
21
  datesHasBorderTopLeftRadius.add(firstRowStartDate);
@@ -29,9 +28,8 @@ export function computeDataForStyling(viewingValue, weekStart) {
29
28
  // Last date sitting at the start of a row has borderBottomLeftRadius
30
29
  const lastRowStartDate = lastDate.getDate() - ((lastDate.getDay() - weekStart + 7) % 7);
31
30
  datesHasBorderBottomLeftRadius.add(lastRowStartDate);
32
- const datesHasBorderBottomRightRadius = new Set();
33
31
  // Last date of the month has borderBottomRightRadius
34
- datesHasBorderBottomRightRadius.add(lastDate.getDate());
32
+ const datesHasBorderBottomRightRadius = new Set([lastDate.getDate()]);
35
33
  // Last date sitting at the end of a row has borderBottomRightRadius
36
34
  const lastRowEndDate = lastDate.getDate() - ((lastDate.getDay() - weekEnd + 7) % 7);
37
35
  datesHasBorderBottomRightRadius.add(lastRowEndDate);
@@ -19,6 +19,6 @@ export declare const Dialog: import("react").ForwardRefExoticComponent<{
19
19
  enableAutoUnmount?: boolean;
20
20
  disableBackdropClose?: boolean;
21
21
  customFocusElement?: import("react").RefObject<HTMLElement | null> | HTMLElement | null;
22
- } & Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "onClose" | "open"> & {
22
+ } & Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "onClose" | "open" | "title"> & {
23
23
  ref?: (import("react").RefCallback<HTMLDivElement> | import("react").RefObject<HTMLDivElement | null> | null) | undefined;
24
24
  } & import("react").Attributes>;
@@ -97,4 +97,4 @@ export type DialogProps = {
97
97
  * ```
98
98
  */
99
99
  customFocusElement?: RefObject<HTMLElement | null> | HTMLElement | null;
100
- } & Omit<ComponentPropsWithoutRef<'div'>, 'open' | 'onClose'>;
100
+ } & Omit<ComponentPropsWithoutRef<'div'>, 'open' | 'onClose' | 'title'>;
@@ -15,7 +15,11 @@
15
15
  * - **Controlled/uncontrolled modes**: Proper state management for both patterns
16
16
  * - **Provide 'onClear' (user-initiated only)**: Clear the file selection
17
17
  * - **Provide 'onValueCleared' (external changes only)**: Called when controlled value transitions from non-empty to empty
18
- * - **Drag and drop support**: Visual feedback and enhanced file selection UX
18
+ * - **Drag and drop support**: Visual feedback and enhanced file selection UX.
19
+ * Dropped files are assigned to the underlying `<input type="file">`, so they are included in
20
+ * native form submission (`new FormData(form)`) just like files chosen through the dialog.
21
+ * Without `multiple`, only the first dropped file is kept, matching what the dialog allows.
22
+ * In controlled mode the input is reset after every selection by design, for both paths.
19
23
  *
20
24
  * ## Internationalization
21
25
  * This component supports internationalization through text override props following the MFUI Component Internationalization Strategy.
@@ -8,6 +8,8 @@ import { ClearButton } from '../shared/ClearButton';
8
8
  import { HelpMessage } from '../HelpMessage';
9
9
  import { Button } from '../Button';
10
10
  import { mergeRefs } from '../utilities/dom/mergeRefs';
11
+ import { setInputFiles } from '../utilities/dom/setInputFiles';
12
+ import { toFileList } from '../utilities/dom/toFileList';
11
13
  import { useIsNarrowViewport } from '../utilities/responsive/useIsNarrowViewport';
12
14
  /**
13
15
  * The component for selecting a single file or multiple files for upload by dialog or drag and drop.
@@ -26,7 +28,11 @@ import { useIsNarrowViewport } from '../utilities/responsive/useIsNarrowViewport
26
28
  * - **Controlled/uncontrolled modes**: Proper state management for both patterns
27
29
  * - **Provide 'onClear' (user-initiated only)**: Clear the file selection
28
30
  * - **Provide 'onValueCleared' (external changes only)**: Called when controlled value transitions from non-empty to empty
29
- * - **Drag and drop support**: Visual feedback and enhanced file selection UX
31
+ * - **Drag and drop support**: Visual feedback and enhanced file selection UX.
32
+ * Dropped files are assigned to the underlying `<input type="file">`, so they are included in
33
+ * native form submission (`new FormData(form)`) just like files chosen through the dialog.
34
+ * Without `multiple`, only the first dropped file is kept, matching what the dialog allows.
35
+ * In controlled mode the input is reset after every selection by design, for both paths.
30
36
  *
31
37
  * ## Internationalization
32
38
  * This component supports internationalization through text override props following the MFUI Component Internationalization Strategy.
@@ -160,15 +166,29 @@ fileSelectButtonLabel = 'ファイルを選択', instructionText, fileCountLabel
160
166
  }
161
167
  const { files } = event.dataTransfer;
162
168
  if (files.length > 0) {
163
- // Create a synthetic change event to reuse existing logic
169
+ /*
170
+ * The drop is handled on the container instead of the input element, so the browser
171
+ * applies none of the input's own behaviour to it: it neither assigns the dropped files
172
+ * to the input nor honours the `multiple` attribute. Copying the files over keeps native
173
+ * form submission (`new FormData(form)`) working for the drag and drop path, and keeping
174
+ * only the first file when `multiple` is not set prevents a selection that the file
175
+ * selection dialog could never produce for the same markup.
176
+ */
177
+ const droppedFiles = [...files];
178
+ const acceptedFiles = multiple ? droppedFiles : droppedFiles.slice(0, 1);
179
+ const input = inputRef.current;
180
+ const isSynced = input !== null && setInputFiles(input, acceptedFiles);
181
+ /*
182
+ * Create a synthetic change event to reuse existing logic. When the files could not be
183
+ * copied over, `target` stands in for the input and carries them directly, so a drop
184
+ * still updates the visible selection.
185
+ */
164
186
  const syntheticEvent = {
165
- target: {
166
- files,
167
- },
187
+ target: isSynced ? input : { files: toFileList(acceptedFiles) },
168
188
  };
169
189
  handleChange(syntheticEvent);
170
190
  }
171
- }, [disabled, handleChange]);
191
+ }, [disabled, multiple, handleChange]);
172
192
  const handleClear = useCallback(() => {
173
193
  if (!isControlled) {
174
194
  setInternalValues(undefined);
@@ -98,6 +98,16 @@ export type FileDropZoneProps = {
98
98
  * The callback function to be called when files are selected through dialog or drag-and-drop.
99
99
  * This is the standard HTML input onChange event.
100
100
  *
101
+ * **Note:** For the drag-and-drop path the event is a synthetic object rather than a React event.
102
+ * Its `target` is the underlying `<input type="file">`, which the dropped files are assigned to
103
+ * beforehand, but other event members such as `currentTarget` are not populated. In the rare
104
+ * environments where that assignment is not possible, `target` is a plain `{ files }` object
105
+ * instead and the dropped files do not reach native form submission.
106
+ *
107
+ * **Note:** A drop lands on the container rather than on the input, so the `multiple` attribute
108
+ * is applied by this component instead of the browser. Without `multiple`, only the first
109
+ * dropped file is passed on.
110
+ *
101
111
  * @param event - The change event from the file input element
102
112
  *
103
113
  * @default undefined
@@ -3,4 +3,4 @@ import { type MainNavigationProps } from './MainNavigation.types';
3
3
  * The component for the main navigation on the normal viewport.
4
4
  * This component must only be used in MainNavigation.tsx, do not export it.
5
5
  */
6
- export declare function BaseMainNavigation({ className, featureShortcut, navigationItems, enableCollapsible, nestedNavigationDisplay, width, customLinkComponent, disableResponsive, isCollapsed: controlledIsCollapsed, onCollapsedChange, collapseButtonProps, expandButtonProps, }: MainNavigationProps): import("react/jsx-runtime").JSX.Element;
6
+ export declare function BaseMainNavigation({ className, featureShortcut, navigationItems, navLandmarkLabel, enableCollapsible, nestedNavigationDisplay, width, customLinkComponent, disableResponsive, isCollapsed: controlledIsCollapsed, onCollapsedChange, collapseButtonProps, expandButtonProps, }: MainNavigationProps): import("react/jsx-runtime").JSX.Element;
@@ -13,7 +13,7 @@ import { Popover } from '../Popover';
13
13
  * The component for the main navigation on the normal viewport.
14
14
  * This component must only be used in MainNavigation.tsx, do not export it.
15
15
  */
16
- export function BaseMainNavigation({ className, featureShortcut, navigationItems, enableCollapsible = false, nestedNavigationDisplay = 'accordion', width = 'normal', customLinkComponent, disableResponsive, isCollapsed: controlledIsCollapsed, onCollapsedChange, collapseButtonProps, expandButtonProps, }) {
16
+ export function BaseMainNavigation({ className, featureShortcut, navigationItems, navLandmarkLabel, enableCollapsible = false, nestedNavigationDisplay = 'accordion', width = 'normal', customLinkComponent, disableResponsive, isCollapsed: controlledIsCollapsed, onCollapsedChange, collapseButtonProps, expandButtonProps, }) {
17
17
  const [localIsCollapsed, setLocalIsCollapsed] = useState(controlledIsCollapsed ?? false);
18
18
  const [openPopoverIndex, setOpenPopoverIndex] = useState(null);
19
19
  const isControlled = controlledIsCollapsed !== undefined;
@@ -31,7 +31,7 @@ export function BaseMainNavigation({ className, featureShortcut, navigationItems
31
31
  const toggleButtonLabel = isCollapsed
32
32
  ? (expandButtonProps?.label ?? 'ナビゲーションを拡げる')
33
33
  : (collapseButtonProps?.label ?? 'ナビゲーションを折りたたむ');
34
- return (_jsxs("div", { "data-mfui-collapsed": isCollapsed, className: cx(classes.root, 'mfui-BaseMainNavigation__root', className), children: [featureShortcut ? (_jsx("div", { className: cx(classes.featureShortcut, 'mfui-BaseMainNavigation__featureShortcut'), children: _jsx(FocusIndicator, { children: _jsx(NavigationLink, { tag: InternalLinkTag, isExternal: featureShortcut.isExternal, href: featureShortcut.href, className: cx(classes.featureShortcutAnchor, 'mfui-BaseMainNavigation__featureShortcutAnchor'), children: _jsx(Typography, { variant: "controlLabel", children: featureShortcut.label }) }) }) })) : null, _jsxs("nav", { id: navId, className: cx(classes.nav, 'mfui-BaseMainNavigation__nav'), children: [_jsx("ul", { className: cx(classes.list, 'mfui-BaseMainNavigation__list'), children: navigationItems.map((navigation, index) => {
34
+ return (_jsxs("div", { "data-mfui-collapsed": isCollapsed, className: cx(classes.root, 'mfui-BaseMainNavigation__root', className), children: [featureShortcut ? (_jsx("div", { className: cx(classes.featureShortcut, 'mfui-BaseMainNavigation__featureShortcut'), children: _jsx(FocusIndicator, { children: _jsx(NavigationLink, { tag: InternalLinkTag, isExternal: featureShortcut.isExternal, href: featureShortcut.href, className: cx(classes.featureShortcutAnchor, 'mfui-BaseMainNavigation__featureShortcutAnchor'), children: _jsx(Typography, { variant: "controlLabel", children: featureShortcut.label }) }) }) })) : null, _jsxs("nav", { id: navId, "aria-label": navLandmarkLabel, className: cx(classes.nav, 'mfui-BaseMainNavigation__nav'), children: [_jsx("ul", { className: cx(classes.list, 'mfui-BaseMainNavigation__list'), children: navigationItems.map((navigation, index) => {
35
35
  const isGroupLabel = navigation.children && 'isGroupLabel' in navigation && navigation.isGroupLabel;
36
36
  const isGroupMenu = 'isGroupMenu' in navigation && navigation.isGroupMenu;
37
37
  if (isGroupLabel || isGroupMenu) {
@@ -12,11 +12,12 @@ import { NarrowViewportMainNavigation } from './NarrowViewportMainNavigation';
12
12
  * `MainNavigation` is intended to be paired with `GlobalHeader`. If you need a self-contained
13
13
  * layout that already includes both header and navigation features, use `Sidebar` instead.
14
14
  */
15
- export const MainNavigation = forwardRef(({ className, featureShortcut, navigationItems, disableResponsive = false, width = 'normal', customLinkComponent, enableCollapsible, nestedNavigationDisplay, isCollapsed, onCollapsedChange, collapseButtonProps, expandButtonProps, }, ref) => {
15
+ export const MainNavigation = forwardRef(({ className, featureShortcut, navigationItems, navLandmarkLabel = 'メイン', disableResponsive = false, width = 'normal', customLinkComponent, enableCollapsible, nestedNavigationDisplay, isCollapsed, onCollapsedChange, collapseButtonProps, expandButtonProps, }, ref) => {
16
16
  const commonProps = {
17
17
  className,
18
18
  featureShortcut,
19
19
  navigationItems,
20
+ navLandmarkLabel,
20
21
  width,
21
22
  customLinkComponent,
22
23
  };
@@ -186,6 +186,15 @@ export type MainNavigationProps = {
186
186
  * The navigation items.
187
187
  */
188
188
  navigationItems: NavigationItem[];
189
+ /**
190
+ * The accessible name for the navigation landmark, applied as `aria-label` to both the sidebar `<nav>`
191
+ * and the narrow viewport drawer `<nav>`.
192
+ *
193
+ * Avoid including the role name (e.g. 'ナビゲーション'); assistive technology already announces it.
194
+ *
195
+ * @default 'メイン'
196
+ */
197
+ navLandmarkLabel?: string;
189
198
  /**
190
199
  * Whether the navigation is collapsible.
191
200
  *
@@ -13,7 +13,7 @@ import { Portal } from '../Portal';
13
13
  * The component for the main navigation on the narrow viewport.
14
14
  * This component must only be used in MainNavigation.tsx, do not export it.
15
15
  */
16
- export const NarrowViewportMainNavigation = forwardRef(({ className, featureShortcut, navigationItems, width = 'normal', customLinkComponent }, ref) => {
16
+ export const NarrowViewportMainNavigation = forwardRef(({ className, featureShortcut, navigationItems, navLandmarkLabel, width = 'normal', customLinkComponent }, ref) => {
17
17
  const classes = narrowViewportMainNavigationSlotRecipe({ width });
18
18
  const InternalLinkTag = customLinkComponent ?? 'a';
19
19
  const navId = useId();
@@ -48,7 +48,7 @@ export const NarrowViewportMainNavigation = forwardRef(({ className, featureShor
48
48
  }
49
49
  return (_jsx(Portal, { children: _jsxs("div", { ref: dialogRef, role: "dialog", className: cx(classes.root, 'mfui-NarrowViewportMainNavigation__root', className), tabIndex: -1, onKeyDown: handleOnKeyDown, children: [_jsx("div", { "data-mfui-content": "backdrop", className: cx(classes.backdrop, 'mfui-NarrowViewportMainNavigation__backdrop'), onClick: handleCloseMainNavigation }), _jsxs("div", { "data-mfui-content": "inside", className: cx(classes.container, 'mfui-NarrowViewportMainNavigation__container'), onClick: (event) => {
50
50
  event.stopPropagation();
51
- }, children: [featureShortcut ? (_jsx("div", { className: cx(classes.featureShortcut, 'mfui-NarrowViewportMainNavigation__featureShortcut'), children: _jsx(FocusIndicator, { children: _jsx(NavigationLink, { tag: InternalLinkTag, isExternal: featureShortcut.isExternal, href: featureShortcut.href, className: cx(classes.featureShortcutAnchor, 'mfui-NarrowViewportMainNavigation__featureShortcutAnchor'), onClick: handleCloseMainNavigation, children: _jsx(Typography, { variant: "controlLabel", children: featureShortcut.label }) }) }) })) : null, _jsxs("nav", { className: cx(classes.nav, 'mfui-NarrowViewportMainNavigation__nav'), children: [_jsx("ul", { className: cx(classes.list, 'mfui-NarrowViewportMainNavigation__list'), children: navigationItems.map((navigation, index) => {
51
+ }, children: [featureShortcut ? (_jsx("div", { className: cx(classes.featureShortcut, 'mfui-NarrowViewportMainNavigation__featureShortcut'), children: _jsx(FocusIndicator, { children: _jsx(NavigationLink, { tag: InternalLinkTag, isExternal: featureShortcut.isExternal, href: featureShortcut.href, className: cx(classes.featureShortcutAnchor, 'mfui-NarrowViewportMainNavigation__featureShortcutAnchor'), onClick: handleCloseMainNavigation, children: _jsx(Typography, { variant: "controlLabel", children: featureShortcut.label }) }) }) })) : null, _jsxs("nav", { "aria-label": navLandmarkLabel, className: cx(classes.nav, 'mfui-NarrowViewportMainNavigation__nav'), children: [_jsx("ul", { className: cx(classes.list, 'mfui-NarrowViewportMainNavigation__list'), children: navigationItems.map((navigation, index) => {
52
52
  const isGroupLabel = navigation.children && 'isGroupLabel' in navigation && navigation.isGroupLabel;
53
53
  if (isGroupLabel) {
54
54
  return null;