@moneyforward/mfui-components 3.1.0 → 3.3.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.d.ts +1 -1
- package/dist/src/DateTimeSelection/shared/BasePicker/BasePicker.js +6 -6
- package/dist/src/DateTimeSelection/shared/BasePicker/BasePicker.types.d.ts +1 -1
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.d.ts +1 -1
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.js +4 -4
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.types.d.ts +8 -0
- 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/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 +49 -11
- package/dist/src/SelectBox/SelectBox.types.d.ts +80 -1
- package/dist/src/Tag/Tag.js +1 -1
- package/dist/src/ToggleSwitch/ToggleSwitch.d.ts +9 -0
- package/dist/src/ToggleSwitch/ToggleSwitch.js +32 -0
- package/dist/src/ToggleSwitch/ToggleSwitch.types.d.ts +6 -0
- package/dist/src/ToggleSwitch/ToggleSwitch.types.js +1 -0
- package/dist/src/ToggleSwitch/index.d.ts +2 -0
- package/dist/src/ToggleSwitch/index.js +2 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/utilities/dom/useFixedColumns.js +36 -10
- package/dist/src/utilities/dom/useInfiniteScroll.d.ts +22 -0
- package/dist/src/utilities/dom/useInfiniteScroll.js +65 -0
- package/dist/styled-system/css/conditions.js +1 -1
- package/dist/styled-system/jsx/is-valid-prop.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/index.d.ts +2 -1
- package/dist/styled-system/recipes/index.js +1 -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 +2 -2
- package/dist/styled-system/recipes/select-box-slot-recipe.js +30 -1
- package/dist/styled-system/recipes/toggle-switch-slot-recipe.d.ts +33 -0
- package/dist/styled-system/recipes/toggle-switch-slot-recipe.js +36 -0
- package/dist/styled-system/types/conditions.d.ts +10 -0
- package/dist/styles.css +298 -34
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -4,4 +4,4 @@ import { type BasePickerProps } from './BasePicker.types';
|
|
|
4
4
|
* This component uses the composition component of the TextBox component. Please refer to the TextBox documentation for their usage.
|
|
5
5
|
* This component extends the props of TextBox component.
|
|
6
6
|
*/
|
|
7
|
-
export declare function BasePicker({ targetDOMNode, enableAutoUnmount, open, onOpenStateChanged, disableAutoOpen, format, baseFormat, customFormatValue, value, defaultValue, onChange, calendarIconButtonProps, renderPopoverContent, onBlur, allowedPlacements, calendarLocale, disabled, ...textBoxProps }: BasePickerProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export declare function BasePicker({ targetDOMNode, enableAutoUnmount, open, onOpenStateChanged, disableAutoOpen, format, baseFormat, customFormatValue, value, defaultValue, onChange, calendarIconButtonProps, renderPopoverContent, onBlur, allowedPlacements, enableViewportConstraint, calendarLocale, disabled, ...textBoxProps }: BasePickerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -18,7 +18,7 @@ import { getBasePickerLabel } from './constants';
|
|
|
18
18
|
* This component uses the composition component of the TextBox component. Please refer to the TextBox documentation for their usage.
|
|
19
19
|
* This component extends the props of TextBox component.
|
|
20
20
|
*/
|
|
21
|
-
export function BasePicker({ targetDOMNode, enableAutoUnmount = true, open = false, onOpenStateChanged, disableAutoOpen = false, format = 'YYYY/MM/DD', baseFormat = 'YYYY-MM-DD', customFormatValue, value, defaultValue, onChange, calendarIconButtonProps, renderPopoverContent, onBlur, allowedPlacements, calendarLocale = 'ja', disabled, ...textBoxProps }) {
|
|
21
|
+
export function BasePicker({ targetDOMNode, enableAutoUnmount = true, open = false, onOpenStateChanged, disableAutoOpen = false, format = 'YYYY/MM/DD', baseFormat = 'YYYY-MM-DD', customFormatValue, value, defaultValue, onChange, calendarIconButtonProps, renderPopoverContent, onBlur, allowedPlacements, enableViewportConstraint, calendarLocale = 'ja', disabled, ...textBoxProps }) {
|
|
22
22
|
const textBoxRef = useRef(null);
|
|
23
23
|
const basePickerPopoverWrapperRef = useRef(null);
|
|
24
24
|
const triggerRef = useRef(null);
|
|
@@ -83,17 +83,17 @@ 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)
|
|
93
93
|
inputValueProps.onBlur(event);
|
|
94
94
|
// Then call the Popover's smart blur detection
|
|
95
95
|
handleTriggerBlur(event);
|
|
96
|
-
} })), open: isBasePickerOpen, targetDOMNode: targetDOMNode, enableAutoUnmount: enableAutoUnmount, minWidth: "min-content", value: value, defaultValue: defaultValue, baseFormat: baseFormat, renderPopoverContent: renderPopoverContent, textBoxRef: textBoxRef, triggerRef: triggerRef, basePickerPopoverWrapperRef: basePickerPopoverWrapperRef, handleOnKeyDown: handleOnKeyDown, pickerPopoverProps: pickerPopoverProps, allowedPlacements: allowedPlacements, onOpenStateChanged: toggleBasePicker, onBlur: onBlur }) }));
|
|
96
|
+
} })), open: isBasePickerOpen, targetDOMNode: targetDOMNode, enableAutoUnmount: enableAutoUnmount, minWidth: "min-content", value: value, defaultValue: defaultValue, baseFormat: baseFormat, renderPopoverContent: renderPopoverContent, textBoxRef: textBoxRef, triggerRef: triggerRef, basePickerPopoverWrapperRef: basePickerPopoverWrapperRef, handleOnKeyDown: handleOnKeyDown, pickerPopoverProps: pickerPopoverProps, allowedPlacements: allowedPlacements, enableViewportConstraint: enableViewportConstraint, onOpenStateChanged: toggleBasePicker, onBlur: onBlur }) }));
|
|
97
97
|
}
|
|
98
98
|
/**
|
|
99
99
|
* Internal Popover component that needs access to BasePickerContext.
|
|
@@ -101,7 +101,7 @@ export function BasePicker({ targetDOMNode, enableAutoUnmount = true, open = fal
|
|
|
101
101
|
* which requires being within a BasePickerProvider. Creating it as an internal
|
|
102
102
|
* component ensures proper context access while keeping the logic encapsulated.
|
|
103
103
|
*/
|
|
104
|
-
function InternalPopover({ value, defaultValue, textBoxRef, triggerRef, baseFormat = 'YYYY-MM-DD', renderPopoverContent, basePickerPopoverWrapperRef, handleOnKeyDown, pickerPopoverProps, onOpenStateChanged, allowedPlacements, ...popoverProps }) {
|
|
104
|
+
function InternalPopover({ value, defaultValue, textBoxRef, triggerRef, baseFormat = 'YYYY-MM-DD', renderPopoverContent, basePickerPopoverWrapperRef, handleOnKeyDown, pickerPopoverProps, onOpenStateChanged, allowedPlacements, enableViewportConstraint, ...popoverProps }) {
|
|
105
105
|
// Always call the hook at the top level to ensure consistent hook order
|
|
106
106
|
const { viewingValue, setViewingValue, setPendingFocusDate } = useBasePickerContext();
|
|
107
107
|
const handlePopoverOpen = useCallback((event) => {
|
|
@@ -129,7 +129,7 @@ function InternalPopover({ value, defaultValue, textBoxRef, triggerRef, baseForm
|
|
|
129
129
|
// Set the pending focus date to ensure proper focus after month change
|
|
130
130
|
setPendingFocusDate(formattedDate);
|
|
131
131
|
}, [textBoxRef, triggerRef, value, defaultValue, baseFormat, setViewingValue, setPendingFocusDate]);
|
|
132
|
-
return (_jsx(Popover, { enableAutomaticPortalTargetResolution: true, enableViewportConstraint: false, enableAutoFocusOnPopover: false, ...popoverProps, renderContent: () => (_jsx("div", { ref: basePickerPopoverWrapperRef, className: "mfui-BasePicker__popoverWrapper", onKeyDown: handleOnKeyDown, children: renderPopoverContent({
|
|
132
|
+
return (_jsx(Popover, { enableAutomaticPortalTargetResolution: true, enableViewportConstraint: enableViewportConstraint ?? false, enableAutoFocusOnPopover: false, ...popoverProps, renderContent: () => (_jsx("div", { ref: basePickerPopoverWrapperRef, className: "mfui-BasePicker__popoverWrapper", onKeyDown: handleOnKeyDown, children: renderPopoverContent({
|
|
133
133
|
viewingValue,
|
|
134
134
|
setViewingValue,
|
|
135
135
|
value: pickerPopoverProps.value,
|
|
@@ -118,4 +118,4 @@ export type BasePickerProps = {
|
|
|
118
118
|
* @see https://en.wikipedia.org/wiki/ISO_8601
|
|
119
119
|
*/
|
|
120
120
|
baseFormat?: string;
|
|
121
|
-
} & Omit<TextBoxProps, 'value' | 'defaultValue' | 'onChange'> & Pick<PopoverProps, 'allowedPlacements'>;
|
|
121
|
+
} & Omit<TextBoxProps, 'value' | 'defaultValue' | 'onChange'> & Pick<PopoverProps, 'allowedPlacements' | 'enableViewportConstraint'>;
|
|
@@ -3,4 +3,4 @@ import { type BaseRangePickerProps } from './BaseRangePicker.types';
|
|
|
3
3
|
* BaseRangePicker component
|
|
4
4
|
* A generic component for selecting a range of dates with configurable format
|
|
5
5
|
*/
|
|
6
|
-
export declare function BaseRangePicker({ value, defaultValue, disabled, invalid, targetDOMNode, onChange, format, onOpenStateChanged, disableAutoOpen, onBlur, allowedPlacements, enableClearButton, clearButtonProps, minDate, maxDate, enableAutoUnmount, minWidth, renderPopoverContent, startInputProps, endInputProps, calendarLocale, }: BaseRangePickerProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export declare function BaseRangePicker({ value, defaultValue, disabled, invalid, targetDOMNode, onChange, format, onOpenStateChanged, disableAutoOpen, onBlur, allowedPlacements, enableViewportConstraint, enableClearButton, clearButtonProps, minDate, maxDate, enableAutoUnmount, minWidth, renderPopoverContent, startInputProps, endInputProps, calendarLocale, }: BaseRangePickerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -9,7 +9,7 @@ import { dayjs } from '../../../utilities/date/dayjs';
|
|
|
9
9
|
/**
|
|
10
10
|
* Internal component that has access to BaseRangePickerContext
|
|
11
11
|
*/
|
|
12
|
-
function InternalBaseRangePicker({ startInputRef, endInputRef, disabled, invalid, format, enableClearButton, clearButtonProps, targetDOMNode, onBlur, isOpen, open, close, onOpenStateChanged, enableAutoUnmount, allowedPlacements, minWidth, renderPopoverContent, startInputProps = {}, endInputProps = {}, calendarLocale = 'ja', }) {
|
|
12
|
+
function InternalBaseRangePicker({ startInputRef, endInputRef, disabled, invalid, format, enableClearButton, clearButtonProps, targetDOMNode, onBlur, isOpen, open, close, onOpenStateChanged, enableAutoUnmount, allowedPlacements, enableViewportConstraint, minWidth, renderPopoverContent, startInputProps = {}, endInputProps = {}, calendarLocale = 'ja', }) {
|
|
13
13
|
const { setPendingFocusDate, temporaryStart, temporaryEnd, viewingMonth, setViewingMonth } = useBaseRangePickerContext();
|
|
14
14
|
// Custom onOpen handler to prevent auto-focus when clicking on input elements
|
|
15
15
|
// and implement smart focus management for calendar dates
|
|
@@ -49,7 +49,7 @@ function InternalBaseRangePicker({ startInputRef, endInputRef, disabled, invalid
|
|
|
49
49
|
setPendingFocusDate(formattedDate);
|
|
50
50
|
open();
|
|
51
51
|
}, [startInputRef, endInputRef, temporaryStart, temporaryEnd, setPendingFocusDate, open, format]);
|
|
52
|
-
return (_jsx(Popover, { enableAutomaticPortalTargetResolution: true, open: isOpen, allowedPlacements: allowedPlacements, minWidth: minWidth, renderTrigger: ({ setTriggerRef, handleTriggerBlur, handleTriggerKeyDown, openPopover }) => (_jsx(BaseRangePickerTrigger, { ref: setTriggerRef, disabled: disabled, invalid: invalid, format: format, enableClearValue: enableClearButton, clearButtonProps: clearButtonProps, startInputRef: startInputRef, endInputRef: endInputRef, startInputProps: startInputProps, endInputProps: endInputProps, popoverOpenFunction: openPopover, calendarLocale: calendarLocale, onBlur: handleTriggerBlur, onKeyDown: handleTriggerKeyDown })), renderContent: () => renderPopoverContent({
|
|
52
|
+
return (_jsx(Popover, { enableAutomaticPortalTargetResolution: true, open: isOpen, allowedPlacements: allowedPlacements, enableViewportConstraint: enableViewportConstraint, minWidth: minWidth, renderTrigger: ({ setTriggerRef, handleTriggerBlur, handleTriggerKeyDown, openPopover }) => (_jsx(BaseRangePickerTrigger, { ref: setTriggerRef, disabled: disabled, invalid: invalid, format: format, enableClearValue: enableClearButton, clearButtonProps: clearButtonProps, startInputRef: startInputRef, endInputRef: endInputRef, startInputProps: startInputProps, endInputProps: endInputProps, popoverOpenFunction: openPopover, calendarLocale: calendarLocale, onBlur: handleTriggerBlur, onKeyDown: handleTriggerKeyDown })), renderContent: () => renderPopoverContent({
|
|
53
53
|
viewingValue: viewingMonth,
|
|
54
54
|
setViewingValue: setViewingMonth,
|
|
55
55
|
value: [temporaryStart, temporaryEnd],
|
|
@@ -61,12 +61,12 @@ function InternalBaseRangePicker({ startInputRef, endInputRef, disabled, invalid
|
|
|
61
61
|
* BaseRangePicker component
|
|
62
62
|
* A generic component for selecting a range of dates with configurable format
|
|
63
63
|
*/
|
|
64
|
-
export function BaseRangePicker({ value, defaultValue, disabled, invalid, targetDOMNode, onChange, format = 'YYYY/MM/DD', onOpenStateChanged, disableAutoOpen = false, onBlur, allowedPlacements, enableClearButton = false, clearButtonProps, minDate, maxDate, enableAutoUnmount = true, minWidth, renderPopoverContent, startInputProps, endInputProps, calendarLocale = 'ja', }) {
|
|
64
|
+
export function BaseRangePicker({ value, defaultValue, disabled, invalid, targetDOMNode, onChange, format = 'YYYY/MM/DD', onOpenStateChanged, disableAutoOpen = false, onBlur, allowedPlacements, enableViewportConstraint, enableClearButton = false, clearButtonProps, minDate, maxDate, enableAutoUnmount = true, minWidth, renderPopoverContent, startInputProps, endInputProps, calendarLocale = 'ja', }) {
|
|
65
65
|
const { isOpen, open, close } = useDisclosure({ value: false });
|
|
66
66
|
const startInputRef = useRef(null);
|
|
67
67
|
const endInputRef = useRef(null);
|
|
68
68
|
// Default renderPopoverContent implementation for backward compatibility
|
|
69
69
|
const defaultRenderPopoverContent = useCallback(() => _jsx(BaseRangePickerPopover, { calendarLocale: calendarLocale }), [calendarLocale]);
|
|
70
70
|
const finalRenderPopoverContent = renderPopoverContent ?? defaultRenderPopoverContent;
|
|
71
|
-
return (_jsx(BaseRangePickerProvider, { value: value, defaultValue: defaultValue, disabled: disabled, format: format, isOpen: isOpen, open: open, close: close, disableAutoOpen: disableAutoOpen, minDate: minDate, maxDate: maxDate, onChange: onChange, children: _jsx(InternalBaseRangePicker, { startInputRef: startInputRef, endInputRef: endInputRef, disabled: disabled, invalid: invalid, format: format, enableClearButton: enableClearButton, clearButtonProps: clearButtonProps, targetDOMNode: targetDOMNode, allowedPlacements: allowedPlacements, enableAutoUnmount: enableAutoUnmount, minWidth: minWidth, startInputProps: startInputProps, endInputProps: endInputProps, isOpen: isOpen, open: open, close: close, renderPopoverContent: finalRenderPopoverContent, calendarLocale: calendarLocale, onBlur: onBlur, onOpenStateChanged: onOpenStateChanged }) }));
|
|
71
|
+
return (_jsx(BaseRangePickerProvider, { value: value, defaultValue: defaultValue, disabled: disabled, format: format, isOpen: isOpen, open: open, close: close, disableAutoOpen: disableAutoOpen, minDate: minDate, maxDate: maxDate, onChange: onChange, children: _jsx(InternalBaseRangePicker, { startInputRef: startInputRef, endInputRef: endInputRef, disabled: disabled, invalid: invalid, format: format, enableClearButton: enableClearButton, clearButtonProps: clearButtonProps, targetDOMNode: targetDOMNode, allowedPlacements: allowedPlacements, enableViewportConstraint: enableViewportConstraint, enableAutoUnmount: enableAutoUnmount, minWidth: minWidth, startInputProps: startInputProps, endInputProps: endInputProps, isOpen: isOpen, open: open, close: close, renderPopoverContent: finalRenderPopoverContent, calendarLocale: calendarLocale, onBlur: onBlur, onOpenStateChanged: onOpenStateChanged }) }));
|
|
72
72
|
}
|
|
@@ -128,6 +128,14 @@ export type BaseRangePickerProps = {
|
|
|
128
128
|
* ```
|
|
129
129
|
*/
|
|
130
130
|
allowedPlacements?: PopoverProps['allowedPlacements'];
|
|
131
|
+
/**
|
|
132
|
+
* Whether to enable viewport constraint for the popover.
|
|
133
|
+
* When true, the popover will be constrained within the visible viewport and a maxHeight is applied.
|
|
134
|
+
* Set to true when the picker is inside a constrained container like SidePane.
|
|
135
|
+
*
|
|
136
|
+
* @default true
|
|
137
|
+
*/
|
|
138
|
+
enableViewportConstraint?: PopoverProps['enableViewportConstraint'];
|
|
131
139
|
/**
|
|
132
140
|
* Whether to enable the clear button functionality.
|
|
133
141
|
* When enabled, a clear button will appear when there are values in the date inputs.
|
|
@@ -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
|
}
|
|
@@ -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
|
}
|
|
@@ -3,8 +3,61 @@ import { type VirtualizerOptions } from '@tanstack/react-virtual';
|
|
|
3
3
|
import { type SearchBoxProps } from '../SearchBox';
|
|
4
4
|
import { type MultipleSelectBoxTriggerProps, type PassedProps } from './MultipleSelectBoxTrigger/MultipleSelectBoxTrigger.types';
|
|
5
5
|
import { type PopoverProps } from '../Popover';
|
|
6
|
+
import { type InfiniteScrollDirection } from '../utilities/dom/useInfiniteScroll';
|
|
6
7
|
export type AllowedValueTypes = string | number | undefined;
|
|
7
8
|
export type VirtualOptionTypes = Pick<VirtualizerOptions<HTMLElement, Element>, 'estimateSize' | 'overscan'>;
|
|
9
|
+
export type InfiniteScrollConfig = {
|
|
10
|
+
/**
|
|
11
|
+
* Enable infinite scroll functionality.
|
|
12
|
+
* When enabled, additional options can be loaded dynamically when scrolling.
|
|
13
|
+
*
|
|
14
|
+
* @default false
|
|
15
|
+
*/
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Callback executed when more options need to be loaded.
|
|
19
|
+
* Called when user scrolls near the top or bottom of the options list.
|
|
20
|
+
*
|
|
21
|
+
* @param direction - The direction of loading ('forward' for bottom, 'backward' for top)
|
|
22
|
+
*/
|
|
23
|
+
onLoadMore?: (direction: InfiniteScrollDirection) => Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Whether there are more options available to load in the forward direction (bottom).
|
|
26
|
+
* Used to determine if infinite scroll should trigger when scrolling down.
|
|
27
|
+
*
|
|
28
|
+
* @default true
|
|
29
|
+
*/
|
|
30
|
+
hasNextPage?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Whether there are more options available to load in the backward direction (top).
|
|
33
|
+
* Used to determine if infinite scroll should trigger when scrolling up.
|
|
34
|
+
*
|
|
35
|
+
* @default true
|
|
36
|
+
*/
|
|
37
|
+
hasPreviousPage?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* The scroll threshold in pixels for triggering infinite scroll.
|
|
40
|
+
* When the scroll position is within this distance from the top or bottom,
|
|
41
|
+
* the onLoadMore callback will be triggered.
|
|
42
|
+
*
|
|
43
|
+
* @default 100
|
|
44
|
+
*/
|
|
45
|
+
threshold?: number;
|
|
46
|
+
/**
|
|
47
|
+
* The error message to display when loading fails.
|
|
48
|
+
* This message supports internationalization.
|
|
49
|
+
*
|
|
50
|
+
* @default "読み込みに失敗しました"
|
|
51
|
+
*/
|
|
52
|
+
errorMessage?: string;
|
|
53
|
+
/**
|
|
54
|
+
* The text for the retry button when loading fails.
|
|
55
|
+
* This message supports internationalization.
|
|
56
|
+
*
|
|
57
|
+
* @default "再読み込み"
|
|
58
|
+
*/
|
|
59
|
+
retryButtonText?: string;
|
|
60
|
+
};
|
|
8
61
|
export type MultipleSelectBoxOption<T extends AllowedValueTypes = string, AdditionalProps extends Record<string, unknown> = Record<string, never>> = ({
|
|
9
62
|
/**
|
|
10
63
|
* The label to be displayed in the list.
|
|
@@ -253,6 +306,14 @@ export type MultipleSelectBoxProps<T extends AllowedValueTypes = string, Additio
|
|
|
253
306
|
minWidth?: PopoverProps['minWidth'];
|
|
254
307
|
allowedPlacements?: PopoverProps['allowedPlacements'];
|
|
255
308
|
};
|
|
309
|
+
/**
|
|
310
|
+
* The properties for the popover wrapper element.
|
|
311
|
+
*
|
|
312
|
+
* @property maxHeight - The maximum height of the popover wrapper element.
|
|
313
|
+
*/
|
|
314
|
+
popoverWrapperProps?: {
|
|
315
|
+
maxHeight?: PopoverProps['maxHeight'];
|
|
316
|
+
};
|
|
256
317
|
/**
|
|
257
318
|
* Flag to indicate loading state of the options.
|
|
258
319
|
* When true, the MultipleSelectBox will display skeleton placeholders
|
|
@@ -334,4 +395,29 @@ export type MultipleSelectBoxProps<T extends AllowedValueTypes = string, Additio
|
|
|
334
395
|
* ```
|
|
335
396
|
*/
|
|
336
397
|
virtualizationOptions?: VirtualOptionTypes;
|
|
398
|
+
/**
|
|
399
|
+
* Configuration for infinite scroll behavior.
|
|
400
|
+
* Allows loading additional options dynamically when scrolling.
|
|
401
|
+
*
|
|
402
|
+
* @example
|
|
403
|
+
* ```tsx
|
|
404
|
+
* <MultipleSelectBox
|
|
405
|
+
* infiniteScroll={{
|
|
406
|
+
* enabled: true,
|
|
407
|
+
* onLoadMore: async (direction) => {
|
|
408
|
+
* // Load more options from API
|
|
409
|
+
* const newOptions = await fetchOptions(direction);
|
|
410
|
+
* setOptions(prev => direction === 'forward' ?
|
|
411
|
+
* [...prev, ...newOptions] :
|
|
412
|
+
* [...newOptions, ...prev]
|
|
413
|
+
* );
|
|
414
|
+
* },
|
|
415
|
+
* hasNextPage: true,
|
|
416
|
+
* threshold: 100
|
|
417
|
+
* }}
|
|
418
|
+
* />
|
|
419
|
+
* ```
|
|
420
|
+
*/
|
|
421
|
+
infiniteScroll?: InfiniteScrollConfig;
|
|
337
422
|
} & Pick<MultipleSelectBoxTriggerProps<T, AdditionalProps>, keyof PassedProps<T, AdditionalProps>>;
|
|
423
|
+
export { type InfiniteScrollDirection } from '../utilities/dom/useInfiniteScroll';
|