@moneyforward/mfui-components 3.2.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/DateTimeSelection/shared/BasePicker/BasePicker.js +2 -2
- package/dist/src/DateTimeSelection/shared/CalendarGrid/CalendarGrid.js +3 -1
- package/dist/src/DisplayTable/DisplayTable.d.ts +3 -2
- package/dist/src/DisplayTable/DisplayTable.js +4 -3
- package/dist/src/DisplayTable/DisplayTable.types.d.ts +7 -0
- package/dist/src/DisplayTable/DisplayTableBody/DisplayTableBody.js +15 -0
- package/dist/src/DisplayTable/DisplayTableCell/DisplayTableCell.js +9 -2
- package/dist/src/DisplayTable/DisplayTableHeaderRow/DisplayTableHeaderRow.js +12 -1
- package/dist/src/DisplayTable/DisplayTableProvider.d.ts +8 -4
- package/dist/src/DisplayTable/DisplayTableProvider.js +12 -3
- package/dist/src/DropdownMenu/DropdownMenuItem/DropdownMenuItem.d.ts +1 -1
- package/dist/src/DropdownMenu/DropdownMenuItem/DropdownMenuItem.js +16 -4
- package/dist/src/DropdownMenu/DropdownMenuItem/DropdownMenuItem.types.d.ts +12 -0
- package/dist/src/MainNavigation/BaseMainNavigation.js +1 -1
- package/dist/src/MainNavigation/MainNavigation.types.d.ts +8 -0
- package/dist/src/MainNavigation/NarrowViewportMainNavigation.js +1 -1
- package/dist/src/MultipleSelectBox/MultipleSelectBox.d.ts +1 -1
- package/dist/src/MultipleSelectBox/MultipleSelectBox.js +65 -15
- package/dist/src/MultipleSelectBox/MultipleSelectBox.types.d.ts +86 -0
- package/dist/src/SelectBox/SelectBox.js +4 -4
- package/dist/src/SelectBox/SelectBox.types.d.ts +2 -2
- package/dist/src/Tag/Tag.js +1 -1
- package/dist/styled-system/recipes/display-table-cell-slot-recipe.d.ts +1 -1
- package/dist/styled-system/recipes/display-table-cell-slot-recipe.js +4 -0
- package/dist/styled-system/recipes/multiple-select-box-slot-recipe.d.ts +1 -1
- package/dist/styled-system/recipes/multiple-select-box-slot-recipe.js +24 -0
- package/dist/styled-system/recipes/select-box-slot-recipe.d.ts +1 -1
- package/dist/styled-system/recipes/select-box-slot-recipe.js +8 -0
- package/dist/styles.css +82 -22
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
- /package/dist/src/{SelectBox/hooks → utilities/dom}/useInfiniteScroll.d.ts +0 -0
- /package/dist/src/{SelectBox/hooks → utilities/dom}/useInfiniteScroll.js +0 -0
|
@@ -83,10 +83,10 @@ export function BasePicker({ targetDOMNode, enableAutoUnmount = true, open = fal
|
|
|
83
83
|
textBoxProps.wrapperProps?.onKeyDown?.(event);
|
|
84
84
|
handleTriggerKeyDown(event);
|
|
85
85
|
},
|
|
86
|
-
onClick: !disableAutoOpen ? handleWrapperClick(openPopover) : undefined,
|
|
86
|
+
onClick: !disableAutoOpen && !disabled ? handleWrapperClick(openPopover) : undefined,
|
|
87
87
|
style: {
|
|
88
88
|
...textBoxProps.wrapperProps?.style,
|
|
89
|
-
cursor: 'text',
|
|
89
|
+
cursor: disabled ? 'not-allowed' : 'text',
|
|
90
90
|
},
|
|
91
91
|
}, onBlur: (event) => {
|
|
92
92
|
// First call the original input onBlur handler (contains onChange logic)
|
|
@@ -12,11 +12,13 @@ export function CalendarGrid({ viewingValue, minDate, maxDate, checkDisabledDate
|
|
|
12
12
|
const dates = useDates(viewingValue);
|
|
13
13
|
const computedDataForStyles = useMemo(() => computeDataForStyling(viewingValue), [viewingValue]);
|
|
14
14
|
const computedPropsForDates = useMemo(() => {
|
|
15
|
+
const startOfMinDate = minDate ? new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate()) : undefined;
|
|
16
|
+
const startOfMaxDate = maxDate ? new Date(maxDate.getFullYear(), maxDate.getMonth(), maxDate.getDate()) : undefined;
|
|
15
17
|
const newDates = dates.map((dateNumber) => {
|
|
16
18
|
if (!dateNumber)
|
|
17
19
|
return null;
|
|
18
20
|
const date = new Date(viewingValue.getFullYear(), viewingValue.getMonth(), dateNumber);
|
|
19
|
-
const isDisabledByProps = (
|
|
21
|
+
const isDisabledByProps = (startOfMinDate && date < startOfMinDate) || (startOfMaxDate && date > startOfMaxDate);
|
|
20
22
|
const isDisabledByCallback = checkDisabledDate?.(date);
|
|
21
23
|
const disabled = isDisabledByProps || !!isDisabledByCallback;
|
|
22
24
|
const isSelected = value ? checkIsSameDate(value, date) : false;
|
|
@@ -5,7 +5,7 @@ import { DisplayTableHeader } from './DisplayTableHeader';
|
|
|
5
5
|
import { DisplayTableHeaderCell } from './DisplayTableHeaderCell';
|
|
6
6
|
import { DisplayTableHeaderRow } from './DisplayTableHeaderRow';
|
|
7
7
|
import { DisplayTableRow } from './DisplayTableRow';
|
|
8
|
-
declare function Base({ title, titleTag, lead, className, fixedHeader, leftFixedColumnIndex, rightFixedColumnIndex, wrapperProps, ...props }: DisplayTableProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
declare function Base({ title, titleTag, lead, className, fixedHeader, leftFixedColumnIndex, rightFixedColumnIndex, wrapperProps, loading, ...props }: DisplayTableProps): import("react/jsx-runtime").JSX.Element;
|
|
9
9
|
/**
|
|
10
10
|
* The `DisplayTable` component.
|
|
11
11
|
*
|
|
@@ -17,12 +17,13 @@ declare function Base({ title, titleTag, lead, className, fixedHeader, leftFixed
|
|
|
17
17
|
* @example
|
|
18
18
|
* ```jsx
|
|
19
19
|
* <DisplayTable title="My Table" lead="Table lead text">
|
|
20
|
+
* <DisplayTable.Header>
|
|
20
21
|
* <DisplayTable.HeaderRow>
|
|
21
22
|
* <DisplayTable.HeaderCell>Header 1</DisplayTable.HeaderCell>
|
|
22
23
|
* <DisplayTable.HeaderCell>Header 2</DisplayTable.HeaderCell>
|
|
23
24
|
* </DisplayTable.HeaderRow>
|
|
24
25
|
* </DisplayTable.Header>
|
|
25
|
-
*
|
|
26
|
+
* <DisplayTable.Body>
|
|
26
27
|
* <DisplayTable.Row>
|
|
27
28
|
* <DisplayTable.Cell>Cell 1</DisplayTable.Cell>
|
|
28
29
|
* <DisplayTable.Cell>Cell 2</DisplayTable.Cell>
|
|
@@ -11,9 +11,9 @@ import { DisplayTableRow } from './DisplayTableRow';
|
|
|
11
11
|
import { cx } from '../../styled-system/css';
|
|
12
12
|
import { Heading } from '../Heading';
|
|
13
13
|
import { Typography } from '../Typography';
|
|
14
|
-
function Base({ title, titleTag = 'h4', lead, className, fixedHeader = false, leftFixedColumnIndex, rightFixedColumnIndex, wrapperProps, ...props }) {
|
|
14
|
+
function Base({ title, titleTag = 'h4', lead, className, fixedHeader = false, leftFixedColumnIndex, rightFixedColumnIndex, wrapperProps, loading = false, ...props }) {
|
|
15
15
|
const classes = displayTableSlotRecipe({ fixedHeader });
|
|
16
|
-
return (_jsx(DisplayTableProvider, { fixedHeader: fixedHeader, leftFixedColumnIndex: leftFixedColumnIndex, rightFixedColumnIndex: rightFixedColumnIndex, children: _jsxs("div", { ...wrapperProps, className: cx(classes.wrapper, 'mfui-DisplayTable__wrapper', wrapperProps?.className), children: [_jsxs("div", { className: cx(classes.header, 'mfui-DisplayTable__header'), children: [_jsx(Heading, { variant: "sectionHeading2", tag: titleTag, className: cx(classes.title, 'mfui-DisplayTable__title'), children: title }), lead ? (_jsx(Typography, { tag: "p", className: cx(classes.lead, 'mfui-DisplayTable__lead'), children: lead })) : null] }), _jsx("div", { className: cx(classes.body, 'mfui-DisplayTable__body'), children: _jsx("table", { className: cx(classes.table, 'mfui-DisplayTable__table', className), ...props }) })] }) }));
|
|
16
|
+
return (_jsx(DisplayTableProvider, { fixedHeader: fixedHeader, leftFixedColumnIndex: leftFixedColumnIndex, rightFixedColumnIndex: rightFixedColumnIndex, loading: loading, children: _jsxs("div", { ...wrapperProps, className: cx(classes.wrapper, 'mfui-DisplayTable__wrapper', wrapperProps?.className), children: [_jsxs("div", { className: cx(classes.header, 'mfui-DisplayTable__header'), children: [_jsx(Heading, { variant: "sectionHeading2", tag: titleTag, className: cx(classes.title, 'mfui-DisplayTable__title'), children: title }), lead ? (_jsx(Typography, { tag: "p", className: cx(classes.lead, 'mfui-DisplayTable__lead'), children: lead })) : null] }), _jsx("div", { className: cx(classes.body, 'mfui-DisplayTable__body'), children: _jsx("table", { className: cx(classes.table, 'mfui-DisplayTable__table', className), "aria-live": "polite", "aria-busy": loading, "aria-label": loading ? 'データを読み込み中' : undefined, ...props }) })] }) }));
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* The `DisplayTable` component.
|
|
@@ -26,12 +26,13 @@ function Base({ title, titleTag = 'h4', lead, className, fixedHeader = false, le
|
|
|
26
26
|
* @example
|
|
27
27
|
* ```jsx
|
|
28
28
|
* <DisplayTable title="My Table" lead="Table lead text">
|
|
29
|
+
* <DisplayTable.Header>
|
|
29
30
|
* <DisplayTable.HeaderRow>
|
|
30
31
|
* <DisplayTable.HeaderCell>Header 1</DisplayTable.HeaderCell>
|
|
31
32
|
* <DisplayTable.HeaderCell>Header 2</DisplayTable.HeaderCell>
|
|
32
33
|
* </DisplayTable.HeaderRow>
|
|
33
34
|
* </DisplayTable.Header>
|
|
34
|
-
*
|
|
35
|
+
* <DisplayTable.Body>
|
|
35
36
|
* <DisplayTable.Row>
|
|
36
37
|
* <DisplayTable.Cell>Cell 1</DisplayTable.Cell>
|
|
37
38
|
* <DisplayTable.Cell>Cell 2</DisplayTable.Cell>
|
|
@@ -74,4 +74,11 @@ export type DisplayTableProps = {
|
|
|
74
74
|
* @property className - The class name of the wrapper element.
|
|
75
75
|
*/
|
|
76
76
|
wrapperProps?: Pick<ComponentPropsWithoutRef<'div'>, 'className'>;
|
|
77
|
+
/**
|
|
78
|
+
* Whether the table is loading.
|
|
79
|
+
* If `true`, four rows with the skeleton cell will be shown instead of the table content.
|
|
80
|
+
*
|
|
81
|
+
* @default false
|
|
82
|
+
*/
|
|
83
|
+
loading?: boolean;
|
|
77
84
|
} & ComponentPropsWithoutRef<'table'>;
|
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
2
3
|
import { displayTableBodySlotRecipe } from '../../../styled-system/recipes';
|
|
3
4
|
import { cx } from '../../../styled-system/css';
|
|
5
|
+
import { useDisplayTableContext } from '../DisplayTableProvider';
|
|
6
|
+
import { DisplayTableRow } from '../DisplayTableRow';
|
|
7
|
+
import { DisplayTableCell } from '../DisplayTableCell';
|
|
8
|
+
const NUMBER_OF_LOADING_ROWS = 4;
|
|
4
9
|
export function DisplayTableBody({ className, ...props }) {
|
|
10
|
+
const { numberColumns, loading } = useDisplayTableContext();
|
|
5
11
|
const classes = displayTableBodySlotRecipe();
|
|
12
|
+
// Memoize loading rows to avoid recreating arrays on every render
|
|
13
|
+
const loadingRows = useMemo(() => {
|
|
14
|
+
if (!loading)
|
|
15
|
+
return null;
|
|
16
|
+
return Array.from({ length: NUMBER_OF_LOADING_ROWS }).map((_, rowIndex) => (_jsx(DisplayTableRow, { children: Array.from({ length: numberColumns }).map((_, colIndex) => (_jsx(DisplayTableCell, { columnIndex: colIndex }, colIndex))) }, rowIndex)));
|
|
17
|
+
}, [loading, numberColumns]);
|
|
18
|
+
if (loading) {
|
|
19
|
+
return (_jsx("tbody", { className: cx(classes.root, 'mfui-DisplayTableBody__root', className), ...props, children: loadingRows }));
|
|
20
|
+
}
|
|
6
21
|
return _jsx("tbody", { className: cx(classes.root, 'mfui-DisplayTableBody__root', className), ...props });
|
|
7
22
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { displayTableCellSlotRecipe } from '../../../styled-system/recipes';
|
|
3
3
|
import { Typography } from '../../Typography';
|
|
4
|
+
import { Skeleton } from '../../Skeleton';
|
|
4
5
|
import { useDisplayTableContext } from '../DisplayTableProvider';
|
|
5
6
|
import { cx } from '../../../styled-system/css';
|
|
6
7
|
import { useFixedColumns } from '../../utilities/dom/useFixedColumns';
|
|
7
8
|
export function DisplayTableCell({ children, className, depth = '0', columnIndex, style, type = 'text', isTotal = false, ...props }) {
|
|
8
|
-
const { leftFixedColumnIndex, rightFixedColumnIndex } = useDisplayTableContext();
|
|
9
|
+
const { leftFixedColumnIndex, rightFixedColumnIndex, loading } = useDisplayTableContext();
|
|
9
10
|
const { cellRef, isFixedColumn, isEdgeFixedColumn, fixedPositionStyles } = useFixedColumns({
|
|
10
11
|
columnIndex,
|
|
11
12
|
leftFixedColumnIndex,
|
|
@@ -27,5 +28,11 @@ export function DisplayTableCell({ children, className, depth = '0', columnIndex
|
|
|
27
28
|
// Row headers are fixed columns on the left side
|
|
28
29
|
const isRowHeader = isFixedColumn && isEdgeFixedColumn === 'left';
|
|
29
30
|
const Tag = isRowHeader ? 'th' : 'td';
|
|
30
|
-
|
|
31
|
+
const cellContent = (() => {
|
|
32
|
+
if (loading) {
|
|
33
|
+
return (_jsx("div", { className: cx(classes.skeletonCell, 'mfui-DisplayTableCell__skeletonCell'), children: _jsx(Skeleton, {}) }));
|
|
34
|
+
}
|
|
35
|
+
return _jsx(Typography, { variant: getTypographyVariant(), children: children });
|
|
36
|
+
})();
|
|
37
|
+
return (_jsx(Tag, { ref: cellRef, className: cx(classes.root, 'mfui-DisplayTableCell__root', className), style: { ...style, ...fixedPositionStyles }, ...props, children: cellContent }));
|
|
31
38
|
}
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Children, cloneElement, isValidElement } from 'react';
|
|
2
|
+
import { Children, cloneElement, isValidElement, useMemo, useEffect } from 'react';
|
|
3
3
|
import { displayTableHeaderRowSlotRecipe } from '../../../styled-system/recipes';
|
|
4
4
|
import { DisplayTableHeaderCell } from '../DisplayTableHeaderCell';
|
|
5
5
|
import { cx } from '../../../styled-system/css';
|
|
6
6
|
import { isComponentOrWrapped } from '../../utilities/react/isComponentOrWrapped';
|
|
7
|
+
import { useDisplayTableContext } from '../DisplayTableProvider';
|
|
7
8
|
export function DisplayTableHeaderRow({ children, className, ...props }) {
|
|
9
|
+
const { setNumberColumns } = useDisplayTableContext();
|
|
8
10
|
const classes = displayTableHeaderRowSlotRecipe();
|
|
11
|
+
// Count the number of header cells
|
|
12
|
+
const numberOfColumns = useMemo(() => {
|
|
13
|
+
const headerCells = Children.toArray(children).filter((child) => isValidElement(child) && isComponentOrWrapped(child, DisplayTableHeaderCell));
|
|
14
|
+
return headerCells.length;
|
|
15
|
+
}, [children]);
|
|
16
|
+
// Update context with the number of columns
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
setNumberColumns(numberOfColumns);
|
|
19
|
+
}, [numberOfColumns, setNumberColumns]);
|
|
9
20
|
return (_jsx("tr", { className: cx(classes.root, 'mfui-DisplayTableHeaderRow__root', className), ...props, children: Children.map(children, (child, index) => {
|
|
10
21
|
if (!isValidElement(child)) {
|
|
11
22
|
return child;
|
|
@@ -3,20 +3,24 @@ import { type DisplayTableProps } from './DisplayTable.types';
|
|
|
3
3
|
/**
|
|
4
4
|
* The props for the `DisplayTableContext` component.
|
|
5
5
|
*/
|
|
6
|
-
export type DisplayTableContextProps = Pick<DisplayTableProps, 'fixedHeader' | 'leftFixedColumnIndex' | 'rightFixedColumnIndex'>;
|
|
6
|
+
export type DisplayTableContextProps = Pick<DisplayTableProps, 'fixedHeader' | 'leftFixedColumnIndex' | 'rightFixedColumnIndex' | 'loading'>;
|
|
7
|
+
export type DisplayTableProviderValues = {
|
|
8
|
+
numberColumns: number;
|
|
9
|
+
setNumberColumns: (number: number) => void;
|
|
10
|
+
} & DisplayTableContextProps;
|
|
7
11
|
/**
|
|
8
12
|
* The context for the `DisplayTable` component.
|
|
9
13
|
*
|
|
10
14
|
* This should only be used in DisplayTable component.
|
|
11
15
|
*/
|
|
12
|
-
export declare const DisplayTableContext: import("react").Context<
|
|
16
|
+
export declare const DisplayTableContext: import("react").Context<DisplayTableProviderValues>;
|
|
13
17
|
/**
|
|
14
18
|
* The hook to use the `DisplayTableContext`.
|
|
15
19
|
*/
|
|
16
|
-
export declare const useDisplayTableContext: () =>
|
|
20
|
+
export declare const useDisplayTableContext: () => DisplayTableProviderValues;
|
|
17
21
|
/**
|
|
18
22
|
* The provider for the `DisplayTableContext`.
|
|
19
23
|
*
|
|
20
24
|
* This should only be used in DisplayTable component.
|
|
21
25
|
*/
|
|
22
|
-
export declare function DisplayTableProvider({ children, fixedHeader, leftFixedColumnIndex, rightFixedColumnIndex, }: PropsWithChildren<DisplayTableContextProps>): import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
export declare function DisplayTableProvider({ children, fixedHeader, leftFixedColumnIndex, rightFixedColumnIndex, loading, }: PropsWithChildren<DisplayTableContextProps>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { createContext, useContext, useMemo } from 'react';
|
|
3
|
+
import { createContext, useContext, useMemo, useState } from 'react';
|
|
4
4
|
/**
|
|
5
5
|
* The context for the `DisplayTable` component.
|
|
6
6
|
*
|
|
@@ -10,6 +10,11 @@ export const DisplayTableContext = createContext({
|
|
|
10
10
|
fixedHeader: false,
|
|
11
11
|
leftFixedColumnIndex: undefined,
|
|
12
12
|
rightFixedColumnIndex: undefined,
|
|
13
|
+
loading: false,
|
|
14
|
+
numberColumns: 0,
|
|
15
|
+
setNumberColumns: () => {
|
|
16
|
+
// Do nothing
|
|
17
|
+
},
|
|
13
18
|
});
|
|
14
19
|
/**
|
|
15
20
|
* The hook to use the `DisplayTableContext`.
|
|
@@ -20,11 +25,15 @@ export const useDisplayTableContext = () => useContext(DisplayTableContext);
|
|
|
20
25
|
*
|
|
21
26
|
* This should only be used in DisplayTable component.
|
|
22
27
|
*/
|
|
23
|
-
export function DisplayTableProvider({ children, fixedHeader = false, leftFixedColumnIndex, rightFixedColumnIndex, }) {
|
|
28
|
+
export function DisplayTableProvider({ children, fixedHeader = false, leftFixedColumnIndex, rightFixedColumnIndex, loading = false, }) {
|
|
29
|
+
const [numberColumns, setNumberColumns] = useState(0);
|
|
24
30
|
const contextValue = useMemo(() => ({
|
|
25
31
|
fixedHeader,
|
|
26
32
|
leftFixedColumnIndex,
|
|
27
33
|
rightFixedColumnIndex,
|
|
28
|
-
|
|
34
|
+
loading,
|
|
35
|
+
numberColumns,
|
|
36
|
+
setNumberColumns,
|
|
37
|
+
}), [fixedHeader, leftFixedColumnIndex, rightFixedColumnIndex, loading, numberColumns]);
|
|
29
38
|
return _jsx(DisplayTableContext.Provider, { value: contextValue, children: children });
|
|
30
39
|
}
|
|
@@ -12,4 +12,4 @@ import { type DropdownMenuItemProps } from './DropdownMenuItem.types';
|
|
|
12
12
|
*
|
|
13
13
|
* Otherwise, it will be rendered as a div element.
|
|
14
14
|
*/
|
|
15
|
-
export declare function DropdownMenuItem({ children, className, href, target, customLinkComponent, onClick, 'aria-labelledby': ariaLabelledby, ...rest }: DropdownMenuItemProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export declare function DropdownMenuItem({ children, className, href, target, customLinkComponent, onClick, disabled, 'aria-labelledby': ariaLabelledby, ...rest }: DropdownMenuItemProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -20,7 +20,7 @@ import { useDropdownMenuContext } from '../DropdownMenuContext';
|
|
|
20
20
|
*
|
|
21
21
|
* Otherwise, it will be rendered as a div element.
|
|
22
22
|
*/
|
|
23
|
-
export function DropdownMenuItem({ children, className, href, target, customLinkComponent, onClick, 'aria-labelledby': ariaLabelledby, ...rest }) {
|
|
23
|
+
export function DropdownMenuItem({ children, className, href, target, customLinkComponent, onClick, disabled, 'aria-labelledby': ariaLabelledby, ...rest }) {
|
|
24
24
|
const classes = dropdownMenuItemSlotRecipe();
|
|
25
25
|
const { closeMenuPopover } = useDropdownMenuContext();
|
|
26
26
|
const id = useId();
|
|
@@ -48,11 +48,13 @@ export function DropdownMenuItem({ children, className, href, target, customLink
|
|
|
48
48
|
}
|
|
49
49
|
}, []);
|
|
50
50
|
const handleClick = useCallback((event) => {
|
|
51
|
+
if (disabled)
|
|
52
|
+
return;
|
|
51
53
|
if (onClick) {
|
|
52
54
|
onClick(event);
|
|
53
55
|
closeMenuPopover?.();
|
|
54
56
|
}
|
|
55
|
-
}, [onClick, closeMenuPopover]);
|
|
57
|
+
}, [disabled, onClick, closeMenuPopover]);
|
|
56
58
|
const isButton = useMemo(() => onClick && !href, [onClick, href]);
|
|
57
59
|
const Tag = useMemo(() => {
|
|
58
60
|
if (href) {
|
|
@@ -66,6 +68,16 @@ export function DropdownMenuItem({ children, className, href, target, customLink
|
|
|
66
68
|
// If the href is provided, it should be an anchor element and focusable.
|
|
67
69
|
// If the href is undefined and onClick is provided, it should be a button element and focusable.
|
|
68
70
|
// If both are undefined, it should be a div element and not focusable.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
// If disabled is true, the menu item is not focusable.
|
|
72
|
+
const isFocusable = (!!href || !!onClick) && !disabled;
|
|
73
|
+
// Anchor navigation is suppressed when disabled to prevent unintended navigation.
|
|
74
|
+
const resolvedHref = isButton || disabled ? undefined : href;
|
|
75
|
+
// For button elements, use the native disabled attribute for full browser semantics.
|
|
76
|
+
// For anchor and div elements, use aria-disabled and data-disabled since they have no native disabled support.
|
|
77
|
+
const disabledProps = isButton
|
|
78
|
+
? { disabled }
|
|
79
|
+
: disabled
|
|
80
|
+
? { 'aria-disabled': true, 'data-disabled': '' }
|
|
81
|
+
: {};
|
|
82
|
+
return (_jsx(FocusIndicator, { position: "inside", children: _jsx(Tag, { ...rest, ref: itemRef, ...(ariaLabelledby && { id }), "data-mfui-menu-item": true, role: "menuitem", className: cx(classes.root, 'mfui-DropdownMenuItem__root', className), tabIndex: -1, href: resolvedHref, target: target, "data-mfui-focusable": isFocusable, type: onClick ? 'button' : undefined, "aria-labelledby": ariaLabelledby ? `${ariaLabelledby} ${id}` : undefined, ...disabledProps, onKeyDown: handleKeyDown, onClick: isButton ? handleClick : undefined, children: typeof children === 'string' ? _jsx(Typography, { variant: "body", children: children }) : children }) }));
|
|
71
83
|
}
|
|
@@ -16,6 +16,10 @@ type AnchorProps = {
|
|
|
16
16
|
* This property is ignored if the href property is provided.
|
|
17
17
|
*/
|
|
18
18
|
onClick?: undefined;
|
|
19
|
+
/**
|
|
20
|
+
* When true, the menu item is disabled and not interactive.
|
|
21
|
+
*/
|
|
22
|
+
disabled?: boolean;
|
|
19
23
|
};
|
|
20
24
|
type DivProps = {
|
|
21
25
|
/**
|
|
@@ -34,6 +38,10 @@ type DivProps = {
|
|
|
34
38
|
* If not provided and the href prop is not provided, the menu item will be rendered as a div element.
|
|
35
39
|
*/
|
|
36
40
|
onClick?: undefined;
|
|
41
|
+
/**
|
|
42
|
+
* When true, the menu item is disabled and not interactive.
|
|
43
|
+
*/
|
|
44
|
+
disabled?: boolean;
|
|
37
45
|
};
|
|
38
46
|
type ButtonProps = {
|
|
39
47
|
/**
|
|
@@ -52,6 +60,10 @@ type ButtonProps = {
|
|
|
52
60
|
* The handler to call when the menu item is clicked or the Enter key is pressed.
|
|
53
61
|
*/
|
|
54
62
|
onClick?: ComponentPropsWithoutRef<'button'>['onClick'];
|
|
63
|
+
/**
|
|
64
|
+
* When true, the menu item is disabled and not interactive.
|
|
65
|
+
*/
|
|
66
|
+
disabled?: boolean;
|
|
55
67
|
};
|
|
56
68
|
/**
|
|
57
69
|
* The props for the MenuItem component.
|
|
@@ -37,7 +37,7 @@ export function BaseMainNavigation({ className, featureShortcut, navigationItems
|
|
|
37
37
|
else {
|
|
38
38
|
setOpenPopoverIndex(null);
|
|
39
39
|
}
|
|
40
|
-
} })) : (_jsxs("details", { open: hasCurrentNavigationInChildren(navigation.children), className: cx(classes.parentDetails, 'mfui-BaseMainNavigation__parentDetails'), children: [_jsx(FocusIndicator, { position: "inside", children: _jsxs("summary", { className: cx(classes.parentSummary, 'mfui-BaseMainNavigation__parentSummary'), children: [_jsx("div", { className: cx(classes.parentSummaryIcon, 'mfui-BaseMainNavigation__parentSummaryIcon'), children: navigation.icon }), _jsx(Typography, { variant: "controlLabel", children: navigation.label }), _jsx(DisclosureBasicCollapsed, { className: "mfui-BaseMainNavigation__icon_collapsed", "aria-hidden": "true", "data-mfui-content": "main-navigation-icon-collapsed" }), _jsx(DisclosureBasicExpanded, { className: "mfui-BaseMainNavigation__icon_expanded", "aria-hidden": "true", "data-mfui-content": "main-navigation-icon-expanded" })] }) }), _jsx("ul", { className: cx(classes.childrenList, 'mfui-BaseMainNavigation__childrenList'), children: navigation.children.map((child, childIndex) => (_jsx("li", { className: cx(classes.childrenListItem, 'mfui-BaseMainNavigation__childrenListItem'), children: _jsx(FocusIndicator, { position: "inside", children: _jsx(NavigationLink, { tag: InternalLinkTag, isExternal: child.isExternal, href: child.href, className: cx(classes.childrenListItemAnchor, 'mfui-BaseMainNavigation__childrenListItemAnchor'), "aria-current": child.isCurrent ? 'page' : undefined, children: _jsx(Typography, { variant: "controlLabel", children: child.label }) }) }) }, childIndex))) })] }))) : (_jsx(Tooltip, { className: cx(classes.listItemTooltip, 'mfui-BaseMainNavigation__listItemTooltip'), content: navigation.label, disabled: !isCollapsed, placement: "right", children: _jsx(FocusIndicator, { position: "inside", children: _jsx(NavigationLink, { tag: InternalLinkTag, isExternal: navigation.isExternal, href: navigation.href, className: cx(classes.listItemAnchor, 'mfui-BaseMainNavigation__listItemAnchor'), "aria-label": isCollapsed ? navigation.label : undefined, "aria-current": navigation.isCurrent ? 'page' : undefined, children: _jsxs("div", { className: cx(classes.labelGroup, 'mfui-BaseMainNavigation__labelGroup'), children: [_jsxs("div", { className: cx(classes.iconAndLabel, 'mfui-BaseMainNavigation__iconAndLabel'), children: [_jsx("div", { className: cx(classes.listItemAnchorIcon, 'mfui-BaseMainNavigation__listItemAnchorIcon'), children: navigation.icon }), isCollapsed ? null : _jsx(Typography, { variant: "controlLabel", children: navigation.label })] }), navigation.locked ? (_jsx(Lock, { className: cx(classes.lockIcon, 'mfui-BaseMainNavigation__lockIcon'), "aria-label": navigation.lockIconProps?.['aria-label'] ?? 'ロックされています' })) : null, !isCollapsed && navigation.statusSlot && navigation.locked !== true
|
|
40
|
+
} })) : (_jsxs("details", { open: navigation.isOpenByDefault || hasCurrentNavigationInChildren(navigation.children), className: cx(classes.parentDetails, 'mfui-BaseMainNavigation__parentDetails'), children: [_jsx(FocusIndicator, { position: "inside", children: _jsxs("summary", { className: cx(classes.parentSummary, 'mfui-BaseMainNavigation__parentSummary'), children: [_jsx("div", { className: cx(classes.parentSummaryIcon, 'mfui-BaseMainNavigation__parentSummaryIcon'), children: navigation.icon }), _jsx(Typography, { variant: "controlLabel", children: navigation.label }), _jsx(DisclosureBasicCollapsed, { className: "mfui-BaseMainNavigation__icon_collapsed", "aria-hidden": "true", "data-mfui-content": "main-navigation-icon-collapsed" }), _jsx(DisclosureBasicExpanded, { className: "mfui-BaseMainNavigation__icon_expanded", "aria-hidden": "true", "data-mfui-content": "main-navigation-icon-expanded" })] }) }), _jsx("ul", { className: cx(classes.childrenList, 'mfui-BaseMainNavigation__childrenList'), children: navigation.children.map((child, childIndex) => (_jsx("li", { className: cx(classes.childrenListItem, 'mfui-BaseMainNavigation__childrenListItem'), children: _jsx(FocusIndicator, { position: "inside", children: _jsx(NavigationLink, { tag: InternalLinkTag, isExternal: child.isExternal, href: child.href, className: cx(classes.childrenListItemAnchor, 'mfui-BaseMainNavigation__childrenListItemAnchor'), "aria-current": child.isCurrent ? 'page' : undefined, children: _jsx(Typography, { variant: "controlLabel", children: child.label }) }) }) }, childIndex))) })] }))) : (_jsx(Tooltip, { className: cx(classes.listItemTooltip, 'mfui-BaseMainNavigation__listItemTooltip'), content: navigation.label, disabled: !isCollapsed, placement: "right", children: _jsx(FocusIndicator, { position: "inside", children: _jsx(NavigationLink, { tag: InternalLinkTag, isExternal: navigation.isExternal, href: navigation.href, className: cx(classes.listItemAnchor, 'mfui-BaseMainNavigation__listItemAnchor'), "aria-label": isCollapsed ? navigation.label : undefined, "aria-current": navigation.isCurrent ? 'page' : undefined, children: _jsxs("div", { className: cx(classes.labelGroup, 'mfui-BaseMainNavigation__labelGroup'), children: [_jsxs("div", { className: cx(classes.iconAndLabel, 'mfui-BaseMainNavigation__iconAndLabel'), children: [_jsx("div", { className: cx(classes.listItemAnchorIcon, 'mfui-BaseMainNavigation__listItemAnchorIcon'), children: navigation.icon }), isCollapsed ? null : _jsx(Typography, { variant: "controlLabel", children: navigation.label })] }), navigation.locked ? (_jsx(Lock, { className: cx(classes.lockIcon, 'mfui-BaseMainNavigation__lockIcon'), "aria-label": navigation.lockIconProps?.['aria-label'] ?? 'ロックされています' })) : null, !isCollapsed && navigation.statusSlot && navigation.locked !== true
|
|
41
41
|
? navigation.statusSlot
|
|
42
42
|
: null] }) }) }) })) }, index))) }) }), enableCollapsible ? (_jsx("div", { className: cx(classes.footer, 'mfui-BaseMainNavigation__footer'), children: _jsx(Tooltip, { content: toggleButtonLabel, placement: "right", children: _jsx(ToggleButton, { className: cx(classes.toggleButton, 'mfui-BaseMainNavigation__toggleButton'), iconClassName: cx(classes.toggleButtonIcon, 'mfui-BaseMainNavigation__toggleButtonIcon'), isCollapsed: isCollapsed, "aria-label": toggleButtonLabel, targetId: navId, handleClickToggleButton: handleClickToggleButton }) }) })) : null] }));
|
|
43
43
|
}
|
|
@@ -81,6 +81,14 @@ type ParentNavigationItem = {
|
|
|
81
81
|
* This property is not allowed for the parent navigation items.
|
|
82
82
|
*/
|
|
83
83
|
isCurrent?: undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Whether the nested navigation is open by default.
|
|
86
|
+
* When set to true, the parent navigation item is expanded on initial render,
|
|
87
|
+
* regardless of whether any child has `isCurrent: true`.
|
|
88
|
+
*
|
|
89
|
+
* @default false
|
|
90
|
+
*/
|
|
91
|
+
isOpenByDefault?: boolean;
|
|
84
92
|
/**
|
|
85
93
|
* Nested navigation items.
|
|
86
94
|
*
|
|
@@ -47,5 +47,5 @@ export const NarrowViewportMainNavigation = forwardRef(({ className, featureShor
|
|
|
47
47
|
}
|
|
48
48
|
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) => {
|
|
49
49
|
event.stopPropagation();
|
|
50
|
-
}, 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, _jsx("nav", { className: cx(classes.nav, 'mfui-NarrowViewportMainNavigation__nav'), children: _jsx("ul", { className: cx(classes.list, 'mfui-NarrowViewportMainNavigation__list'), children: navigationItems.map((navigation, index) => (_jsx("li", { className: cx(classes.listItem, 'mfui-NarrowViewportMainNavigation__listItem'), children: navigation.children ? (_jsxs("details", { open: hasCurrentNavigationInChildren(navigation.children), className: cx(classes.parentDetails, 'mfui-NarrowViewportMainNavigation__parentDetails'), children: [_jsx(FocusIndicator, { position: "inside", children: _jsxs("summary", { className: cx(classes.parentSummary, 'mfui-NarrowViewportMainNavigation__parentSummary'), children: [_jsx("div", { className: cx(classes.parentSummaryIcon, 'mfui-NarrowViewportMainNavigation__parentSummaryIcon'), children: navigation.icon }), _jsx(Typography, { variant: "controlLabel", children: navigation.label }), _jsx(DisclosureBasicCollapsed, { className: "mfui-NarrowViewportMainNavigation__icon_collapsed", "aria-hidden": "true", "data-mfui-content": "main-navigation-icon-collapsed" }), _jsx(DisclosureBasicExpanded, { className: "mfui-NarrowViewportMainNavigation__icon_expanded", "aria-hidden": "true", "data-mfui-content": "main-navigation-icon-expanded" })] }) }), _jsx("ul", { className: cx(classes.childrenList, 'mfui-NarrowViewportMainNavigation__childrenList'), children: navigation.children.map((child, childIndex) => (_jsx("li", { className: cx(classes.childrenListItem, 'mfui-NarrowViewportMainNavigation__childrenListItem'), children: _jsx(FocusIndicator, { position: "inside", children: _jsx(NavigationLink, { tag: InternalLinkTag, isExternal: child.isExternal, href: child.href, className: cx(classes.childrenListItemAnchor, 'mfui-NarrowViewportMainNavigation__childrenListItemAnchor'), "aria-current": child.isCurrent ? 'page' : undefined, onClick: handleCloseMainNavigation, children: _jsx(Typography, { variant: "controlLabel", children: child.label }) }) }) }, childIndex))) })] })) : (_jsx(FocusIndicator, { position: "inside", children: _jsx(NavigationLink, { tag: InternalLinkTag, isExternal: navigation.isExternal, href: navigation.href, className: cx(classes.listItemAnchor, 'mfui-NarrowViewportMainNavigation__listItemAnchor'), "aria-current": navigation.isCurrent ? 'page' : undefined, children: _jsxs("div", { className: cx(classes.labelGroup, 'mfui-NarrowViewportMainNavigation__labelGroup'), children: [_jsxs("div", { className: cx(classes.iconAndLabel, 'mfui-NarrowViewportMainNavigation__iconAndLabel'), children: [_jsx("div", { className: cx(classes.listItemAnchorIcon, 'mfui-NarrowViewportMainNavigation__listItemAnchorIcon'), children: navigation.icon }), _jsx(Typography, { variant: "controlLabel", children: navigation.label })] }), navigation.locked ? (_jsx(Lock, { className: cx(classes.lockIcon, 'mfui-NarrowViewportMainNavigation__lockIcon'), "aria-label": navigation.lockIconProps?.['aria-label'] ?? 'ロックされています' })) : null] }) }) })) }, index))) }) })] })] }) }));
|
|
50
|
+
}, 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, _jsx("nav", { className: cx(classes.nav, 'mfui-NarrowViewportMainNavigation__nav'), children: _jsx("ul", { className: cx(classes.list, 'mfui-NarrowViewportMainNavigation__list'), children: navigationItems.map((navigation, index) => (_jsx("li", { className: cx(classes.listItem, 'mfui-NarrowViewportMainNavigation__listItem'), children: navigation.children ? (_jsxs("details", { open: navigation.isOpenByDefault || hasCurrentNavigationInChildren(navigation.children), className: cx(classes.parentDetails, 'mfui-NarrowViewportMainNavigation__parentDetails'), children: [_jsx(FocusIndicator, { position: "inside", children: _jsxs("summary", { className: cx(classes.parentSummary, 'mfui-NarrowViewportMainNavigation__parentSummary'), children: [_jsx("div", { className: cx(classes.parentSummaryIcon, 'mfui-NarrowViewportMainNavigation__parentSummaryIcon'), children: navigation.icon }), _jsx(Typography, { variant: "controlLabel", children: navigation.label }), _jsx(DisclosureBasicCollapsed, { className: "mfui-NarrowViewportMainNavigation__icon_collapsed", "aria-hidden": "true", "data-mfui-content": "main-navigation-icon-collapsed" }), _jsx(DisclosureBasicExpanded, { className: "mfui-NarrowViewportMainNavigation__icon_expanded", "aria-hidden": "true", "data-mfui-content": "main-navigation-icon-expanded" })] }) }), _jsx("ul", { className: cx(classes.childrenList, 'mfui-NarrowViewportMainNavigation__childrenList'), children: navigation.children.map((child, childIndex) => (_jsx("li", { className: cx(classes.childrenListItem, 'mfui-NarrowViewportMainNavigation__childrenListItem'), children: _jsx(FocusIndicator, { position: "inside", children: _jsx(NavigationLink, { tag: InternalLinkTag, isExternal: child.isExternal, href: child.href, className: cx(classes.childrenListItemAnchor, 'mfui-NarrowViewportMainNavigation__childrenListItemAnchor'), "aria-current": child.isCurrent ? 'page' : undefined, onClick: handleCloseMainNavigation, children: _jsx(Typography, { variant: "controlLabel", children: child.label }) }) }) }, childIndex))) })] })) : (_jsx(FocusIndicator, { position: "inside", children: _jsx(NavigationLink, { tag: InternalLinkTag, isExternal: navigation.isExternal, href: navigation.href, className: cx(classes.listItemAnchor, 'mfui-NarrowViewportMainNavigation__listItemAnchor'), "aria-current": navigation.isCurrent ? 'page' : undefined, children: _jsxs("div", { className: cx(classes.labelGroup, 'mfui-NarrowViewportMainNavigation__labelGroup'), children: [_jsxs("div", { className: cx(classes.iconAndLabel, 'mfui-NarrowViewportMainNavigation__iconAndLabel'), children: [_jsx("div", { className: cx(classes.listItemAnchorIcon, 'mfui-NarrowViewportMainNavigation__listItemAnchorIcon'), children: navigation.icon }), _jsx(Typography, { variant: "controlLabel", children: navigation.label })] }), navigation.locked ? (_jsx(Lock, { className: cx(classes.lockIcon, 'mfui-NarrowViewportMainNavigation__lockIcon'), "aria-label": navigation.lockIconProps?.['aria-label'] ?? 'ロックされています' })) : null] }) }) })) }, index))) }) })] })] }) }));
|
|
51
51
|
});
|
|
@@ -4,4 +4,4 @@ import { type MultipleSelectBoxProps, type AllowedValueTypes } from './MultipleS
|
|
|
4
4
|
* This component is separated from the SelectBox component because it has a different behavior.
|
|
5
5
|
* This component switches the variants of looks and behaviors depends on the props: size, invalid, disabled.
|
|
6
6
|
*/
|
|
7
|
-
export declare function MultipleSelectBox<T extends AllowedValueTypes = string, AdditionalProps extends Record<string, unknown> = Record<string, never>>({ id, triggerProps, triggerWrapperProps, size, options, defaultValue, placeholder, emptyMessage, disabled, invalid, targetDOMNode, name, onChange, value, showDisplayValueAsTag, renderDisplayValue, enableSearchOptions, notFoundMessage, searchBoxProps, loading, onSearchOptions, clearButtonProps, disableClearButton, optionPanelProps, enableAllOptionsControls, enableApplyControls, selectAllButtonProps, clearAllButtonProps, selectedCountProps, cancelButtonProps, applyButtonProps, renderOption, onOpenStateChanged, enableAutoUnmount, onBlur, showGroupOptionDivider, enableVirtualization, virtualizationOptions, }: MultipleSelectBoxProps<T, AdditionalProps>): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export declare function MultipleSelectBox<T extends AllowedValueTypes = string, AdditionalProps extends Record<string, unknown> = Record<string, never>>({ id, triggerProps, triggerWrapperProps, size, options, defaultValue, placeholder, emptyMessage, disabled, invalid, targetDOMNode, name, onChange, value, showDisplayValueAsTag, renderDisplayValue, enableSearchOptions, notFoundMessage, searchBoxProps, loading, onSearchOptions, clearButtonProps, disableClearButton, optionPanelProps, popoverWrapperProps, enableAllOptionsControls, enableApplyControls, selectAllButtonProps, clearAllButtonProps, selectedCountProps, cancelButtonProps, applyButtonProps, renderOption, onOpenStateChanged, enableAutoUnmount, onBlur, showGroupOptionDivider, enableVirtualization, virtualizationOptions, infiniteScroll, }: MultipleSelectBoxProps<T, AdditionalProps>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useId, useMemo, useRef } from 'react';
|
|
4
|
-
import { CheckboxChecked, CheckboxUnchecked } from '@moneyforward/mfui-icons-react';
|
|
4
|
+
import { CheckboxChecked, CheckboxUnchecked, Error } from '@moneyforward/mfui-icons-react';
|
|
5
5
|
import { FocusIndicator } from '../FocusIndicator';
|
|
6
6
|
import { Typography } from '../Typography';
|
|
7
7
|
import { useFocusTrap } from '../utilities/dom/useFocusTrap';
|
|
@@ -13,6 +13,7 @@ import { SearchBox } from '../SearchBox';
|
|
|
13
13
|
import { Button } from '../Button';
|
|
14
14
|
import { HStack, VStack } from '../Stack';
|
|
15
15
|
import { Skeleton } from '../Skeleton';
|
|
16
|
+
import { ProgressIndicator } from '../ProgressIndicator';
|
|
16
17
|
import { useInitialFocusOnOptionPanelOpen } from './hooks/useInitialFocusOnOptionPanelOpen';
|
|
17
18
|
import { useSelectedValues } from './hooks/useSelectedValues';
|
|
18
19
|
import { useOptionKeyboardNavigation } from './hooks/useOptionKeyboardNavigation';
|
|
@@ -23,6 +24,7 @@ import { cx } from '../../styled-system/css';
|
|
|
23
24
|
import { flattenOptions } from './utils/flattenOptions';
|
|
24
25
|
import { isSelectableOption, isOptionDisabled, isOptionSelected } from './utils/isSelectableOption';
|
|
25
26
|
import { useVirtualizedMultipleSelectBoxOptions } from './hooks/useVirtualizedMultipleSelectBoxOptions';
|
|
27
|
+
import { useInfiniteScroll } from '../utilities/dom/useInfiniteScroll';
|
|
26
28
|
// How many skeleton items to render while the component is loading.
|
|
27
29
|
const SKELETON_ITEM_COUNT = 4;
|
|
28
30
|
/**
|
|
@@ -32,12 +34,13 @@ const SKELETON_ITEM_COUNT = 4;
|
|
|
32
34
|
*/
|
|
33
35
|
export function MultipleSelectBox({ id, triggerProps, triggerWrapperProps, size, options = [], defaultValue,
|
|
34
36
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
35
|
-
placeholder, emptyMessage, disabled, invalid, targetDOMNode, name, onChange, value, showDisplayValueAsTag, renderDisplayValue, enableSearchOptions = false, notFoundMessage, searchBoxProps, loading = false, onSearchOptions, clearButtonProps, disableClearButton = false, optionPanelProps, enableAllOptionsControls = false, enableApplyControls = false, selectAllButtonProps, clearAllButtonProps, selectedCountProps, cancelButtonProps, applyButtonProps, renderOption, onOpenStateChanged, enableAutoUnmount = true, onBlur, showGroupOptionDivider, enableVirtualization = false, virtualizationOptions, }) {
|
|
37
|
+
placeholder, emptyMessage, disabled, invalid, targetDOMNode, name, onChange, value, showDisplayValueAsTag, renderDisplayValue, enableSearchOptions = false, notFoundMessage, searchBoxProps, loading = false, onSearchOptions, clearButtonProps, disableClearButton = false, optionPanelProps, popoverWrapperProps, enableAllOptionsControls = false, enableApplyControls = false, selectAllButtonProps, clearAllButtonProps, selectedCountProps, cancelButtonProps, applyButtonProps, renderOption, onOpenStateChanged, enableAutoUnmount = true, onBlur, showGroupOptionDivider, enableVirtualization = false, virtualizationOptions, infiniteScroll, }) {
|
|
36
38
|
const classes = multipleSelectBoxSlotRecipe({ showGroupOptionDivider });
|
|
37
39
|
const triggerRef = useRef(null);
|
|
38
40
|
const listBoxId = useId();
|
|
39
41
|
const optionPanelRef = useRef(null);
|
|
40
42
|
const listBoxRef = useRef(null);
|
|
43
|
+
const scrollWrapperRef = useRef(null);
|
|
41
44
|
const searchInputRef = useRef(null);
|
|
42
45
|
const selectAllButtonRef = useRef(null);
|
|
43
46
|
const clearAllButtonRef = useRef(null);
|
|
@@ -71,11 +74,30 @@ placeholder, emptyMessage, disabled, invalid, targetDOMNode, name, onChange, val
|
|
|
71
74
|
// Initialize virtualization
|
|
72
75
|
const { virtualizer, virtualItems, totalSize, isVirtualized } = useVirtualizedMultipleSelectBoxOptions({
|
|
73
76
|
enabled: shouldVirtualize,
|
|
74
|
-
parentRef:
|
|
77
|
+
parentRef: scrollWrapperRef,
|
|
75
78
|
flatOptions: filteredOptions.filter((option) => 'value' in option),
|
|
76
79
|
estimateSize: virtualizationOptions?.estimateSize,
|
|
77
80
|
overscan: virtualizationOptions?.overscan,
|
|
78
81
|
});
|
|
82
|
+
// Extract infinite scroll configuration with defaults
|
|
83
|
+
const baseEnabledInfiniteScroll = infiniteScroll?.enabled ?? false;
|
|
84
|
+
const onLoadMore = infiniteScroll?.onLoadMore;
|
|
85
|
+
const hasNextPage = infiniteScroll?.hasNextPage ?? true;
|
|
86
|
+
const hasPreviousPage = infiniteScroll?.hasPreviousPage ?? true;
|
|
87
|
+
const infiniteScrollThreshold = infiniteScroll?.threshold ?? 100;
|
|
88
|
+
const infiniteScrollErrorMessage = infiniteScroll?.errorMessage ?? '読み込みに失敗しました';
|
|
89
|
+
const infiniteScrollRetryButtonText = infiniteScroll?.retryButtonText ?? '再読み込み';
|
|
90
|
+
// Initialize infinite scroll
|
|
91
|
+
const { isLoading: isInfiniteScrollLoading, error: infiniteScrollError, handleScroll: handleInfiniteScroll, retryLoad: retryInfiniteScroll, } = useInfiniteScroll({
|
|
92
|
+
onLoadMore,
|
|
93
|
+
hasNextPage,
|
|
94
|
+
hasPreviousPage,
|
|
95
|
+
}, {
|
|
96
|
+
enabled: baseEnabledInfiniteScroll,
|
|
97
|
+
threshold: infiniteScrollThreshold,
|
|
98
|
+
});
|
|
99
|
+
// Disable infinite scroll when there's an error
|
|
100
|
+
const enableInfiniteScroll = baseEnabledInfiniteScroll && !infiniteScrollError;
|
|
79
101
|
const { temporaryValues, updateTemporaryValues, initializeTemporaryValues, handleCancelButtonClick, handleApplyButtonClick, } = useApplyControls({
|
|
80
102
|
onValuesChange: updateSelectedValues,
|
|
81
103
|
closeOptionPanel,
|
|
@@ -262,18 +284,46 @@ placeholder, emptyMessage, disabled, invalid, targetDOMNode, name, onChange, val
|
|
|
262
284
|
handleOptionFocus,
|
|
263
285
|
tabbableOptionIndex,
|
|
264
286
|
]);
|
|
287
|
+
// Render infinite scroll error message with retry button
|
|
288
|
+
const renderInfiniteScrollError = useCallback(() => infiniteScrollError ? (_jsxs("li", { className: cx(classes.infiniteScrollError, 'mfui-MultipleSelectBox__infiniteScrollError'), role: "alert", children: [_jsxs("div", { className: cx(classes.infiniteScrollErrorMessage, 'mfui-MultipleSelectBox__infiniteScrollErrorMessage'), "aria-live": "polite", children: [_jsx(Error, { "aria-hidden": true, className: cx(classes.infiniteScrollErrorIcon, 'mfui-MultipleSelectBox__infiniteScrollErrorIcon') }), _jsx(Typography, { variant: "body", children: infiniteScrollErrorMessage })] }), _jsx("div", { className: cx(classes.infiniteScrollErrorButton, 'mfui-MultipleSelectBox__infiniteScrollErrorButton'), children: _jsx(Button, { size: "small", onClick: (event) => {
|
|
289
|
+
event.stopPropagation();
|
|
290
|
+
retryInfiniteScroll();
|
|
291
|
+
}, children: infiniteScrollRetryButtonText }) })] })) : null, [
|
|
292
|
+
infiniteScrollError,
|
|
293
|
+
classes.infiniteScrollError,
|
|
294
|
+
classes.infiniteScrollErrorIcon,
|
|
295
|
+
classes.infiniteScrollErrorMessage,
|
|
296
|
+
classes.infiniteScrollErrorButton,
|
|
297
|
+
retryInfiniteScroll,
|
|
298
|
+
infiniteScrollErrorMessage,
|
|
299
|
+
infiniteScrollRetryButtonText,
|
|
300
|
+
]);
|
|
301
|
+
// Render infinite scroll loading indicator
|
|
302
|
+
const renderInfiniteScrollLoading = useCallback(() => isInfiniteScrollLoading && enableInfiniteScroll ? (_jsx("div", { className: cx(classes.infiniteScrollLoading, 'mfui-MultipleSelectBox__infiniteScrollLoading'), children: _jsx(ProgressIndicator, {}) })) : null, [isInfiniteScrollLoading, enableInfiniteScroll, classes.infiniteScrollLoading]);
|
|
265
303
|
return (_jsx(Popover, { renderTrigger: ({ setTriggerRef, togglePopover, handleTriggerKeyDown, handleTriggerBlur }) => (_jsx(MultipleSelectBoxTrigger, { ref: triggerRef, wrapperRef: setTriggerRef, selectedOptions: localSelectedOptions, id: id, disabled: disabled, triggerProps: triggerProps, triggerWrapperProps: triggerWrapperProps, name: name,
|
|
266
304
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
267
|
-
placeholder: placeholder, size: size, listBoxId: listBoxId, isOptionPanelOpen: isOptionPanelOpen, invalid: invalid, showDisplayValueAsTag: showDisplayValueAsTag, renderDisplayValue: renderDisplayValue, clearButtonProps: clearButtonProps, disableClearButton: disableClearButton, updateSelectedValues: updateSelectedValues, onClick: togglePopover, onKeyDown: handleTriggerKeyDown, onBlur: handleTriggerBlur })), contentProps: { className: classes.popover }, minWidth: optionPanelProps?.minWidth, allowedPlacements: optionPanelProps?.allowedPlacements, renderContent: () => (_jsxs("div", { ref: optionPanelRef, className: cx(classes.optionPanel, 'mfui-MultipleSelectBox__optionPanel', optionPanelProps?.className), tabIndex: -1, onKeyDown: handleKeyDownMenu, children: [enableSearchOptions || enableAllOptionsControls ? (_jsxs(VStack, { className: cx(classes.menuHeader, 'mfui-MultipleSelectBox__menuHeader'), gap: "8px", children: [enableSearchOptions ? (_jsx(SearchBox, { ref: searchInputRef, enableClearButton: true, value: searchText, textBoxSize: "small", autoComplete: "off", onChange: onSearchTextChange, ...searchBoxProps })) : null, enableAllOptionsControls ? (_jsxs(HStack, { justifyContent: "space-between", children: [_jsx(Button, { ref: selectAllButtonRef, size: "small", onClick: handleSelectAll, children: selectAllButtonProps?.label ?? 'すべて選択' }), _jsx(Button, { ref: clearAllButtonRef, size: "small", onClick: handleClearAll, children: clearAllButtonProps?.label ?? 'すべて解除' })] })) : null] })) : null, _jsx("ul", { ref: listBoxRef, role: "listbox", id: listBoxId, className: cx(classes.listBox, 'mfui-MultipleSelectBox__listBox'), tabIndex: -1, style: isVirtualized && totalSize > 0
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
?
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
305
|
+
placeholder: placeholder, size: size, listBoxId: listBoxId, isOptionPanelOpen: isOptionPanelOpen, invalid: invalid, showDisplayValueAsTag: showDisplayValueAsTag, renderDisplayValue: renderDisplayValue, clearButtonProps: clearButtonProps, disableClearButton: disableClearButton, updateSelectedValues: updateSelectedValues, onClick: togglePopover, onKeyDown: handleTriggerKeyDown, onBlur: handleTriggerBlur })), contentProps: { className: classes.popover }, minWidth: optionPanelProps?.minWidth, maxHeight: popoverWrapperProps?.maxHeight, allowedPlacements: optionPanelProps?.allowedPlacements, renderContent: () => (_jsxs("div", { ref: optionPanelRef, className: cx(classes.optionPanel, 'mfui-MultipleSelectBox__optionPanel', optionPanelProps?.className), tabIndex: -1, onKeyDown: handleKeyDownMenu, children: [enableSearchOptions || enableAllOptionsControls ? (_jsxs(VStack, { className: cx(classes.menuHeader, 'mfui-MultipleSelectBox__menuHeader'), gap: "8px", children: [enableSearchOptions ? (_jsx(SearchBox, { ref: searchInputRef, enableClearButton: true, value: searchText, textBoxSize: "small", autoComplete: "off", onChange: onSearchTextChange, ...searchBoxProps })) : null, enableAllOptionsControls ? (_jsxs(HStack, { justifyContent: "space-between", children: [_jsx(Button, { ref: selectAllButtonRef, size: "small", onClick: handleSelectAll, children: selectAllButtonProps?.label ?? 'すべて選択' }), _jsx(Button, { ref: clearAllButtonRef, size: "small", onClick: handleClearAll, children: clearAllButtonProps?.label ?? 'すべて解除' })] })) : null] })) : null, _jsx("div", { ref: scrollWrapperRef, className: cx(classes.scrollWrapper, 'mfui-MultipleSelectBox__scrollWrapper'), onScroll: enableInfiniteScroll ? handleInfiniteScroll : undefined, children: _jsx("ul", { ref: listBoxRef, role: "listbox", id: listBoxId, className: cx(classes.listBox, 'mfui-MultipleSelectBox__listBox'), tabIndex: -1, style: isVirtualized && totalSize > 0
|
|
306
|
+
? {
|
|
307
|
+
height: `${String(totalSize)}px`,
|
|
308
|
+
position: 'relative',
|
|
309
|
+
}
|
|
310
|
+
: undefined, children: loading
|
|
311
|
+
? Array.from({ length: SKELETON_ITEM_COUNT }).map((_, index) => (_jsx("li", { className: cx(classes.skeletonItem, 'mfui-MultipleSelectBox__skeletonItem'), children: _jsx(Skeleton, {}) }, index)))
|
|
312
|
+
: isVirtualized && virtualItems.length > 0
|
|
313
|
+
? // Virtualized rendering with group support
|
|
314
|
+
[...renderVirtualizedItems(), renderInfiniteScrollLoading(), renderInfiniteScrollError()].filter(Boolean)
|
|
315
|
+
: filteredOptions.length > 0
|
|
316
|
+
? [
|
|
317
|
+
...renderNonVirtualizedItems(),
|
|
318
|
+
renderInfiniteScrollLoading(),
|
|
319
|
+
renderInfiniteScrollError(),
|
|
320
|
+
].filter(Boolean)
|
|
321
|
+
: [
|
|
322
|
+
_jsx("li", { className: cx(classes.emptyMessage, 'mfui-MultipleSelectBox__emptyMessage'), children: _jsx(Typography, { variant: "body", children: enableSearchOptions && searchText && notFoundMessage
|
|
323
|
+
? notFoundMessage
|
|
324
|
+
: options.length > 0
|
|
325
|
+
? notFoundMessage
|
|
326
|
+
: emptyMessage }) }, "empty"),
|
|
327
|
+
renderInfiniteScrollError(),
|
|
328
|
+
].filter(Boolean) }) }), enableApplyControls ? (_jsxs(HStack, { className: cx(classes.menuFooter, 'mfui-MultipleSelectBox__menuFooter'), justifyContent: "space-between", alignItems: "center", children: [_jsx(Typography, { variant: "body", color: "neutral.600", children: selectedCountProps?.render?.(temporaryValues.size) ?? `${String(temporaryValues.size)}件選択中` }), _jsxs(HStack, { gap: "horizontal.0-1of2", children: [_jsx(Button, { ref: cancelButtonRef, size: "small", priority: "secondary", onClick: handleCancelButtonClick, children: cancelButtonProps?.label ?? 'キャンセル' }), _jsx(Button, { ref: applyButtonRef, size: "small", priority: "primary", onClick: handleApplyButtonClick, children: applyButtonProps?.label ?? '適用' })] })] })) : null] })), open: isOptionPanelOpen, targetDOMNode: targetDOMNode, enableAutoUnmount: enableAutoUnmount, onOpenStateChanged: toggleOptionPanel, onBlur: onBlur }));
|
|
279
329
|
}
|