@moneyforward/mfui-components 3.34.0 → 3.36.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/DataTable/DataTable.d.ts +50 -0
- package/dist/src/DataTable/DataTable.js +44 -0
- package/dist/src/DataTable/DataTableFilterButton/DataTableFilterButton.d.ts +6 -0
- package/dist/src/DataTable/DataTableFilterButton/DataTableFilterButton.js +63 -0
- package/dist/src/DataTable/DataTableFilterButton/DataTableFilterButton.types.d.ts +49 -0
- package/dist/src/DataTable/DataTableFilterButton/DataTableFilterButton.types.js +1 -0
- package/dist/src/DataTable/DataTableFilterButton/index.d.ts +2 -0
- package/dist/src/DataTable/DataTableFilterButton/index.js +1 -0
- package/dist/src/DataTable/DataTableHeaderCell/DataTableHeaderCell.js +6 -2
- package/dist/src/DataTable/DataTableHeaderCell/DataTableHeaderCell.types.d.ts +23 -1
- package/dist/src/DataTable/DataTableSortButton/DataTableSortButton.d.ts +4 -0
- package/dist/src/DataTable/DataTableSortButton/DataTableSortButton.js +17 -0
- package/dist/src/DataTable/DataTableSortButton/DataTableSortButton.types.d.ts +25 -0
- package/dist/src/DataTable/DataTableSortButton/DataTableSortButton.types.js +1 -0
- package/dist/src/DataTable/DataTableSortButton/index.d.ts +2 -0
- package/dist/src/DataTable/DataTableSortButton/index.js +1 -0
- package/dist/src/DataTable/index.d.ts +2 -0
- package/dist/src/DateTimeSelection/DatePicker/DatePickerDateCell/DatePickerDateCell.js +3 -0
- package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerPopover/DateRangePickerCalendar/DateRangePickerDateCell.d.ts +2 -2
- package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerPopover/DateRangePickerCalendar/DateRangePickerDateCell.js +5 -2
- package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerTrigger/DateRangePickerTrigger.js +3 -2
- package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerTrigger/DateRangePickerTrigger.types.d.ts +17 -0
- package/dist/src/DateTimeSelection/FilterDateRangePicker/DateRangePickerContent/DateRangePickerContent.js +3 -1
- package/dist/src/DateTimeSelection/FilterDateRangePicker/FilterDateRangePicker.types.d.ts +2 -1
- package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.types.d.ts +2 -1
- package/dist/src/DateTimeSelection/FilterMonthPicker/MonthPickerPopover/MonthPickerPopover.js +2 -1
- package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerMonthCell/MonthRangePickerMonthCell.d.ts +1 -0
- package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerMonthCell/MonthRangePickerMonthCell.js +11 -8
- package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerMonthCell/constants.d.ts +7 -15
- package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerMonthCell/constants.js +26 -13
- package/dist/src/DateTimeSelection/TimePicker/TimePicker.d.ts +2 -0
- package/dist/src/DateTimeSelection/TimePicker/TimePicker.js +3 -2
- package/dist/src/DateTimeSelection/TimePicker/TimePicker.types.d.ts +27 -0
- package/dist/src/DateTimeSelection/shared/BasePicker/BasePicker.js +4 -1
- package/dist/src/DateTimeSelection/shared/BasePicker/constants.d.ts +1 -1
- package/dist/src/DateTimeSelection/shared/BasePicker/constants.js +2 -0
- 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 +33 -1
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/BaseRangePickerCalendar/BaseRangePickerDateCell.d.ts +2 -2
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/BaseRangePickerCalendar/BaseRangePickerDateCell.js +15 -36
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/BaseRangePickerCalendar/index.d.ts +0 -1
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/BaseRangePickerCalendar/index.js +0 -1
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/BaseRangePickerPopover.js +2 -1
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerTrigger/BaseRangePickerTrigger.js +3 -2
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerTrigger/BaseRangePickerTrigger.types.d.ts +17 -0
- package/dist/src/DateTimeSelection/shared/accessibility/constants.d.ts +7 -16
- package/dist/src/DateTimeSelection/shared/accessibility/constants.js +30 -14
- package/dist/src/DateTimeSelection/shared/accessibility/generateDateLabel.d.ts +6 -1
- package/dist/src/DateTimeSelection/shared/accessibility/generateDateLabel.js +14 -8
- package/dist/src/DeterminateProgressIndicator/DeterminateProgressIndicator.d.ts +18 -0
- package/dist/src/DeterminateProgressIndicator/DeterminateProgressIndicator.js +20 -0
- package/dist/src/DeterminateProgressIndicator/DeterminateProgressIndicator.types.d.ts +41 -0
- package/dist/src/DeterminateProgressIndicator/DeterminateProgressIndicator.types.js +1 -0
- package/dist/src/DeterminateProgressIndicator/index.d.ts +2 -0
- package/dist/src/DeterminateProgressIndicator/index.js +1 -0
- package/dist/src/FilterSelectBox/FilterSelectBox.types.d.ts +1 -1
- package/dist/src/IconButton/IconButton.d.ts +2 -0
- package/dist/src/IconButton/IconButton.js +2 -2
- package/dist/src/IconButton/IconButton.types.d.ts +7 -0
- package/dist/src/MultipleFilterSelectBox/MultipleFilterSelectBox.types.d.ts +1 -1
- package/dist/src/MultipleSelectBox/MultipleSelectBox.d.ts +1 -1
- package/dist/src/MultipleSelectBox/MultipleSelectBox.js +2 -2
- package/dist/src/MultipleSelectBox/MultipleSelectBox.types.d.ts +5 -1
- package/dist/src/MultipleSelectBox/MultipleSelectBoxTrigger/MultipleSelectBoxTrigger.js +2 -2
- package/dist/src/MultipleSelectBox/MultipleSelectBoxTrigger/MultipleSelectBoxTrigger.types.d.ts +39 -1
- package/dist/src/MultipleSelectBox/hooks/useSearchBox.js +14 -3
- package/dist/src/Popover/Popover.js +1 -1
- package/dist/src/Popover/Popover.types.d.ts +1 -1
- package/dist/src/SelectBox/SelectBox.d.ts +1 -1
- package/dist/src/SelectBox/SelectBox.js +11 -4
- package/dist/src/SelectBox/SelectBox.types.d.ts +38 -1
- package/dist/src/SelectBox/hooks/useSearchBox.d.ts +6 -1
- package/dist/src/SelectBox/hooks/useSearchBox.js +23 -4
- package/dist/src/SplitView/SplitView.js +16 -2
- package/dist/src/TextBox/TextBox.d.ts +2 -1
- package/dist/src/TextBox/TextBox.js +4 -3
- package/dist/src/TextBox/TextBox.types.d.ts +26 -0
- package/dist/src/Textarea/Textarea.d.ts +2 -1
- package/dist/src/Textarea/Textarea.js +11 -3
- package/dist/src/Textarea/Textarea.types.d.ts +26 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/styled-system/recipes/base-range-picker-trigger-slot-recipe.d.ts +1 -0
- package/dist/styled-system/recipes/base-range-picker-trigger-slot-recipe.js +5 -0
- package/dist/styled-system/recipes/data-table-filter-button-slot-recipe.d.ts +33 -0
- package/dist/styled-system/recipes/data-table-filter-button-slot-recipe.js +43 -0
- package/dist/styled-system/recipes/data-table-header-cell-slot-recipe.d.ts +3 -2
- package/dist/styled-system/recipes/data-table-header-cell-slot-recipe.js +69 -1
- package/dist/styled-system/recipes/data-table-sort-button-slot-recipe.d.ts +33 -0
- package/dist/styled-system/recipes/data-table-sort-button-slot-recipe.js +35 -0
- package/dist/styled-system/recipes/determinate-progress-indicator-slot-recipe.d.ts +36 -0
- package/dist/styled-system/recipes/determinate-progress-indicator-slot-recipe.js +61 -0
- package/dist/styled-system/recipes/icon-button-slot-recipe.d.ts +2 -1
- package/dist/styled-system/recipes/icon-button-slot-recipe.js +33 -2
- package/dist/styled-system/recipes/index.d.ts +3 -0
- package/dist/styled-system/recipes/index.js +3 -0
- package/dist/styled-system/recipes/text-box-slot-recipe.d.ts +4 -0
- package/dist/styled-system/recipes/text-box-slot-recipe.js +8 -2
- package/dist/styled-system/recipes/textarea-slot-recipe.d.ts +4 -0
- package/dist/styled-system/recipes/textarea-slot-recipe.js +6 -0
- package/dist/styled-system/recipes/time-picker-slot-recipe.d.ts +4 -0
- package/dist/styled-system/recipes/time-picker-slot-recipe.js +8 -2
- package/dist/styles.css +293 -14
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/BaseRangePickerCalendar/constants.d.ts +0 -18
- package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerPopover/BaseRangePickerCalendar/constants.js +0 -18
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type ComponentPropsWithoutRef } from 'react';
|
|
2
|
+
export type DeterminateProgressIndicatorProps = {
|
|
3
|
+
/**
|
|
4
|
+
* The current progress value expressed as a percentage (`0`–`100`).
|
|
5
|
+
*
|
|
6
|
+
* Values outside this range are clamped to `0` or `100`.
|
|
7
|
+
*/
|
|
8
|
+
value: number;
|
|
9
|
+
/**
|
|
10
|
+
* Whether to display the value text (the progress percentage as a number).
|
|
11
|
+
*
|
|
12
|
+
* @default true
|
|
13
|
+
*/
|
|
14
|
+
showValueText?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Where to place the value text relative to the progress indicator.
|
|
17
|
+
*
|
|
18
|
+
* - `'right'`: to the right of the progress indicator, vertically centered.
|
|
19
|
+
* - `'top'`: in the upper-right corner, above the progress indicator.
|
|
20
|
+
*
|
|
21
|
+
* @default 'right'
|
|
22
|
+
*/
|
|
23
|
+
valueTextPosition?: 'right' | 'top';
|
|
24
|
+
/**
|
|
25
|
+
* The unit shown after the value. Set to an empty string to hide the unit.
|
|
26
|
+
*
|
|
27
|
+
* @default '%'
|
|
28
|
+
*/
|
|
29
|
+
unit?: string;
|
|
30
|
+
/**
|
|
31
|
+
* The accessible name describing what is loading.
|
|
32
|
+
*
|
|
33
|
+
* Provide this (or `aria-labelledby`) so assistive technologies can announce
|
|
34
|
+
* the purpose of the progress indicator.
|
|
35
|
+
*/
|
|
36
|
+
'aria-label'?: string;
|
|
37
|
+
/**
|
|
38
|
+
* The additional class name for the root element.
|
|
39
|
+
*/
|
|
40
|
+
className?: string;
|
|
41
|
+
} & Omit<ComponentPropsWithoutRef<'div'>, 'role' | 'children'>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DeterminateProgressIndicator } from './DeterminateProgressIndicator';
|
|
@@ -14,4 +14,4 @@ export type FilterSelectBoxProps = {
|
|
|
14
14
|
* @param open - The new open state of the FilterSelectBox.
|
|
15
15
|
*/
|
|
16
16
|
onOpenStateChanged?: (open: boolean) => void;
|
|
17
|
-
} & Omit<SelectBoxProps, 'renderDisplayValue' | 'placeholder' | 'invalid' | 'triggerProps'>;
|
|
17
|
+
} & Omit<SelectBoxProps, 'renderDisplayValue' | 'placeholder' | 'invalid' | 'caution' | 'triggerProps'>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare const InternalIconButton: import("react").ForwardRefExoticComponent<{
|
|
2
2
|
outlined?: import("../../styled-system/recipes").IconButtonSlotRecipeVariant['outlined'];
|
|
3
|
+
destructive?: import("../../styled-system/recipes").IconButtonSlotRecipeVariant['destructive'];
|
|
3
4
|
children: React.ReactNode;
|
|
4
5
|
disabled?: boolean;
|
|
5
6
|
href?: string;
|
|
@@ -9,6 +10,7 @@ export declare const InternalIconButton: import("react").ForwardRefExoticCompone
|
|
|
9
10
|
} & import("react").RefAttributes<HTMLAnchorElement | HTMLButtonElement>>;
|
|
10
11
|
export declare const IconButton: import("react").ForwardRefExoticComponent<{
|
|
11
12
|
outlined?: import("../../styled-system/recipes").IconButtonSlotRecipeVariant['outlined'];
|
|
13
|
+
destructive?: import("../../styled-system/recipes").IconButtonSlotRecipeVariant['destructive'];
|
|
12
14
|
children: React.ReactNode;
|
|
13
15
|
disabled?: boolean;
|
|
14
16
|
href?: string;
|
|
@@ -20,8 +20,8 @@ import { FocusIndicator } from '../FocusIndicator';
|
|
|
20
20
|
*
|
|
21
21
|
* @param ref - Forwarded ref for the rendered button or anchor element.
|
|
22
22
|
*/
|
|
23
|
-
function IconButtonBase({ children, outlined, size = 'default', className, href, customLinkComponent, disabled = false, type = 'button', ...rest }, ref) {
|
|
24
|
-
const classes = iconButtonSlotRecipe({ outlined, size });
|
|
23
|
+
function IconButtonBase({ children, outlined, destructive, size = 'default', className, href, customLinkComponent, disabled = false, type = 'button', ...rest }, ref) {
|
|
24
|
+
const classes = iconButtonSlotRecipe({ outlined, size, destructive });
|
|
25
25
|
const isButton = disabled || href === undefined;
|
|
26
26
|
const Tag = useMemo(() => {
|
|
27
27
|
// If disabled, always render as a button since the link component doesn't support disabled prop
|
|
@@ -7,6 +7,13 @@ type IconButtonCommonProps = {
|
|
|
7
7
|
* @default false
|
|
8
8
|
*/
|
|
9
9
|
outlined?: IconButtonSlotRecipeVariant['outlined'];
|
|
10
|
+
/**
|
|
11
|
+
* Whether the button is destructive.
|
|
12
|
+
* The style of the button will be changed to indicate that the action is destructive.
|
|
13
|
+
*
|
|
14
|
+
* @default false
|
|
15
|
+
*/
|
|
16
|
+
destructive?: IconButtonSlotRecipeVariant['destructive'];
|
|
10
17
|
/**
|
|
11
18
|
* Icon element to be rendered inside the button.
|
|
12
19
|
* Only one element (typically an SVG icon) is expected.
|
|
@@ -8,4 +8,4 @@ export type MultipleFilterSelectBoxProps<T extends AllowedValueTypes = string, A
|
|
|
8
8
|
* The class name to apply to the component
|
|
9
9
|
*/
|
|
10
10
|
className?: string;
|
|
11
|
-
} & Omit<MultipleSelectBoxProps<T, AdditionalProps>, 'renderDisplayValue' | 'showDisplayValueAsTag' | 'placeholder' | 'invalid' | 'triggerWrapperProps' | 'triggerProps'>;
|
|
11
|
+
} & Omit<MultipleSelectBoxProps<T, AdditionalProps>, 'renderDisplayValue' | 'showDisplayValueAsTag' | 'placeholder' | 'invalid' | 'caution' | 'triggerWrapperProps' | 'triggerProps'>;
|
|
@@ -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, popoverWrapperProps, enableAllOptionsControls, enableApplyControls, showSelectedCount, selectAllButtonProps, clearAllButtonProps, selectedCountProps, cancelButtonProps, applyButtonProps, renderOption, renderPopoverHeader, renderOptionList, renderPopoverFooter, onOpenStateChanged, enableAutoUnmount, onBlur, showGroupOptionDivider, enableVirtualization, virtualizationOptions, infiniteScroll, enableCreateOption, onCreateOption, createOptionProps, }: 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, required, caution, targetDOMNode, name, onChange, value, showDisplayValueAsTag, renderDisplayValue, enableSearchOptions, notFoundMessage, searchBoxProps, loading, onSearchOptions, clearButtonProps, disableClearButton, optionPanelProps, popoverWrapperProps, enableAllOptionsControls, enableApplyControls, showSelectedCount, selectAllButtonProps, clearAllButtonProps, selectedCountProps, cancelButtonProps, applyButtonProps, renderOption, renderPopoverHeader, renderOptionList, renderPopoverFooter, onOpenStateChanged, enableAutoUnmount, onBlur, showGroupOptionDivider, enableVirtualization, virtualizationOptions, infiniteScroll, enableCreateOption, onCreateOption, createOptionProps, }: MultipleSelectBoxProps<T, AdditionalProps>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -36,7 +36,7 @@ const SKELETON_ITEM_COUNT = 4;
|
|
|
36
36
|
*/
|
|
37
37
|
export function MultipleSelectBox({ id, triggerProps, triggerWrapperProps, size, options = [], defaultValue,
|
|
38
38
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
39
|
-
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, showSelectedCount = false, selectAllButtonProps, clearAllButtonProps, selectedCountProps, cancelButtonProps, applyButtonProps, renderOption, renderPopoverHeader, renderOptionList, renderPopoverFooter, onOpenStateChanged, enableAutoUnmount = true, onBlur, showGroupOptionDivider, enableVirtualization = false, virtualizationOptions, infiniteScroll, enableCreateOption = false, onCreateOption, createOptionProps, }) {
|
|
39
|
+
placeholder, emptyMessage, disabled, invalid, required, caution, targetDOMNode, name, onChange, value, showDisplayValueAsTag, renderDisplayValue, enableSearchOptions = false, notFoundMessage, searchBoxProps, loading = false, onSearchOptions, clearButtonProps, disableClearButton = false, optionPanelProps, popoverWrapperProps, enableAllOptionsControls = false, enableApplyControls = false, showSelectedCount = false, selectAllButtonProps, clearAllButtonProps, selectedCountProps, cancelButtonProps, applyButtonProps, renderOption, renderPopoverHeader, renderOptionList, renderPopoverFooter, onOpenStateChanged, enableAutoUnmount = true, onBlur, showGroupOptionDivider, enableVirtualization = false, virtualizationOptions, infiniteScroll, enableCreateOption = false, onCreateOption, createOptionProps, }) {
|
|
40
40
|
const classes = multipleSelectBoxSlotRecipe({ showGroupOptionDivider });
|
|
41
41
|
const triggerRef = useRef(null);
|
|
42
42
|
const listBoxId = useId();
|
|
@@ -350,5 +350,5 @@ placeholder, emptyMessage, disabled, invalid, targetDOMNode, name, onChange, val
|
|
|
350
350
|
`${searchText.trim()}を追加できませんでした。` }) })) : null] })) : null;
|
|
351
351
|
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,
|
|
352
352
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
353
|
-
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 ? (_jsx(VStack, { className: cx(classes.menuHeader, 'mfui-MultipleSelectBox__menuHeader'), gap: "8px", children: renderPopoverHeader ? (renderPopoverHeader({ searchNode, allOptionsControlsNode })) : (_jsxs(_Fragment, { children: [searchNode, allOptionsControlsNode] })) })) : null, renderOptionList ? (renderOptionList({ optionsNode, createOptionAreaNode })) : (_jsxs(_Fragment, { children: [optionsNode, createOptionAreaNode] })), enableApplyControls || showSelectedCount ? (_jsx(HStack, { className: cx(classes.menuFooter, 'mfui-MultipleSelectBox__menuFooter'), justifyContent: "space-between", alignItems: "center", children: renderPopoverFooter ? (renderPopoverFooter({ selectedCountNode, applyControlsNode })) : (_jsxs(_Fragment, { children: [selectedCountNode, applyControlsNode] })) })) : null] })), open: isOptionPanelOpen, targetDOMNode: targetDOMNode, enableAutoUnmount: enableAutoUnmount, onOpenStateChanged: toggleOptionPanel, onBlur: onBlur }));
|
|
353
|
+
placeholder: placeholder, size: size, listBoxId: listBoxId, isOptionPanelOpen: isOptionPanelOpen, invalid: invalid, required: required, caution: caution, 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 ? (_jsx(VStack, { className: cx(classes.menuHeader, 'mfui-MultipleSelectBox__menuHeader'), gap: "8px", children: renderPopoverHeader ? (renderPopoverHeader({ searchNode, allOptionsControlsNode })) : (_jsxs(_Fragment, { children: [searchNode, allOptionsControlsNode] })) })) : null, renderOptionList ? (renderOptionList({ optionsNode, createOptionAreaNode })) : (_jsxs(_Fragment, { children: [optionsNode, createOptionAreaNode] })), enableApplyControls || showSelectedCount ? (_jsx(HStack, { className: cx(classes.menuFooter, 'mfui-MultipleSelectBox__menuFooter'), justifyContent: "space-between", alignItems: "center", children: renderPopoverFooter ? (renderPopoverFooter({ selectedCountNode, applyControlsNode })) : (_jsxs(_Fragment, { children: [selectedCountNode, applyControlsNode] })) })) : null] })), open: isOptionPanelOpen, targetDOMNode: targetDOMNode, enableAutoUnmount: enableAutoUnmount, onOpenStateChanged: toggleOptionPanel, onBlur: onBlur }));
|
|
354
354
|
}
|
|
@@ -414,7 +414,7 @@ export type MultipleSelectBoxProps<T extends AllowedValueTypes = string, Additio
|
|
|
414
414
|
* Pass `aria-label` to override the search input's default
|
|
415
415
|
* accessible name (`検索する`).
|
|
416
416
|
*/
|
|
417
|
-
searchBoxProps?: Pick<SearchBoxProps, 'clearButtonProps' | 'placeholder' | 'invalid' | 'aria-label'>;
|
|
417
|
+
searchBoxProps?: Pick<SearchBoxProps, 'clearButtonProps' | 'placeholder' | 'invalid' | 'caution' | 'aria-label'>;
|
|
418
418
|
/**
|
|
419
419
|
* The properties for the option panel element.
|
|
420
420
|
*
|
|
@@ -446,6 +446,10 @@ export type MultipleSelectBoxProps<T extends AllowedValueTypes = string, Additio
|
|
|
446
446
|
* Handler executed when text is entered in the search box.
|
|
447
447
|
* The handler is debounced by 300ms.
|
|
448
448
|
*
|
|
449
|
+
* If the handler is provided after the user has already typed (e.g.
|
|
450
|
+
* `onSearchOptions={isReady ? handleSearch : undefined}`), it is called
|
|
451
|
+
* once with the current search text so that pending input is not lost.
|
|
452
|
+
*
|
|
449
453
|
* When this prop is provided, client-side filtering is disabled as it's
|
|
450
454
|
* assumed that filtering will be handled by the prop.
|
|
451
455
|
*/
|
|
@@ -14,7 +14,7 @@ import { cx } from '../../../styled-system/css';
|
|
|
14
14
|
*
|
|
15
15
|
* @param ref - The forwarded ref to the trigger button element
|
|
16
16
|
*/
|
|
17
|
-
function MultipleSelectBoxTriggerInner({ wrapperRef, triggerProps, triggerWrapperProps, size, selectedOptions, listBoxId, isOptionPanelOpen, invalid, showDisplayValueAsTag,
|
|
17
|
+
function MultipleSelectBoxTriggerInner({ wrapperRef, triggerProps, triggerWrapperProps, size, selectedOptions, listBoxId, isOptionPanelOpen, invalid, required, caution = false, showDisplayValueAsTag,
|
|
18
18
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
19
19
|
placeholder, renderDisplayValue, updateSelectedValues, clearButtonProps, disableClearButton, onKeyDown, onBlur, name, disabled, ...rest }, ref) {
|
|
20
20
|
const classes = multipleSelectBoxTriggerRecipe({ size });
|
|
@@ -48,6 +48,6 @@ placeholder, renderDisplayValue, updateSelectedValues, clearButtonProps, disable
|
|
|
48
48
|
.map((option) => option.value)
|
|
49
49
|
.filter((v) => v != null)
|
|
50
50
|
.map(String);
|
|
51
|
-
return (_jsx(FocusIndicator, { children: _jsxs("div", { ref: wrapperRef, className: cx(classes.wrapper, 'mfui-MultipleSelectBoxTrigger__wrapper', triggerWrapperProps?.className), children: [_jsx("button", { ref: mergeRefs(ref, triggerRef), type: "button", role: "combobox", "aria-controls": listBoxId, "aria-expanded": isOptionPanelOpen, "aria-haspopup": "listbox", "aria-invalid": invalid, value: selectedValues, disabled: disabled, onKeyDown: onKeyDown, onBlur: onBlur, ...rest, ...triggerProps, className: cx(classes.trigger, 'mfui-MultipleSelectBoxTrigger__trigger', triggerProps?.className) }), _jsx("input", { type: "hidden", name: name, value: selectedValues }), _jsx("div", { className: cx(classes.displayValues, 'mfui-MultipleSelectBoxTrigger__displayValues'), "data-mfui-content": "multiple-select-box-trigger-display-values", children: renderer() }), selectedOptions.length > 0 && !disabled && !disableClearButton ? (_jsx("div", { className: cx(classes.clearButtonContainer, 'mfui-MultipleSelectBoxTrigger__clearButtonContainer'), children: _jsx(ClearButton, { size: size === 'small' ? 'small' : 'default', className: cx(classes.clearButton, 'mfui-MultipleSelectBoxTrigger__clearButton'), "aria-label": clearButtonProps?.['aria-label'] ?? '値をクリアする', "data-mfui-content": "multiple-select-box-clear-button", onClick: handleClearValue }) })) : null, _jsx("div", { className: cx(classes.dropdownIconContainer, 'mfui-MultipleSelectBoxTrigger__dropdownIconContainer'), children: _jsx(DropdownIcon, { "aria-hidden": "true", className: cx(classes.dropdownIcon, 'mfui-MultipleSelectBoxTrigger__dropdownIcon') }) })] }) }));
|
|
51
|
+
return (_jsx(FocusIndicator, { children: _jsxs("div", { ref: wrapperRef, className: cx(classes.wrapper, 'mfui-MultipleSelectBoxTrigger__wrapper', triggerWrapperProps?.className), children: [_jsx("button", { ref: mergeRefs(ref, triggerRef), type: "button", role: "combobox", "aria-controls": listBoxId, "aria-expanded": isOptionPanelOpen, "aria-haspopup": "listbox", "aria-invalid": invalid, "aria-required": required || undefined, "data-mfui-caution": caution && !invalid ? true : undefined, value: selectedValues, disabled: disabled, onKeyDown: onKeyDown, onBlur: onBlur, ...rest, ...triggerProps, className: cx(classes.trigger, 'mfui-MultipleSelectBoxTrigger__trigger', triggerProps?.className) }), _jsx("input", { type: "hidden", name: name, value: selectedValues }), _jsx("div", { className: cx(classes.displayValues, 'mfui-MultipleSelectBoxTrigger__displayValues'), "data-mfui-content": "multiple-select-box-trigger-display-values", children: renderer() }), selectedOptions.length > 0 && !disabled && !disableClearButton ? (_jsx("div", { className: cx(classes.clearButtonContainer, 'mfui-MultipleSelectBoxTrigger__clearButtonContainer'), children: _jsx(ClearButton, { size: size === 'small' ? 'small' : 'default', className: cx(classes.clearButton, 'mfui-MultipleSelectBoxTrigger__clearButton'), "aria-label": clearButtonProps?.['aria-label'] ?? '値をクリアする', "data-mfui-content": "multiple-select-box-clear-button", onClick: handleClearValue }) })) : null, _jsx("div", { className: cx(classes.dropdownIconContainer, 'mfui-MultipleSelectBoxTrigger__dropdownIconContainer'), children: _jsx(DropdownIcon, { "aria-hidden": "true", className: cx(classes.dropdownIcon, 'mfui-MultipleSelectBoxTrigger__dropdownIcon') }) })] }) }));
|
|
52
52
|
}
|
|
53
53
|
export const MultipleSelectBoxTrigger = forwardRef(MultipleSelectBoxTriggerInner);
|
package/dist/src/MultipleSelectBox/MultipleSelectBoxTrigger/MultipleSelectBoxTrigger.types.d.ts
CHANGED
|
@@ -39,16 +39,54 @@ export type PassedProps<T extends AllowedValueTypes = string, AdditionalProps ex
|
|
|
39
39
|
* @default false
|
|
40
40
|
*/
|
|
41
41
|
invalid?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Whether the trigger is required.
|
|
44
|
+
* When true, `aria-required="true"` is forwarded to the trigger so that
|
|
45
|
+
* assistive technologies announce the field as required.
|
|
46
|
+
*
|
|
47
|
+
* @default false
|
|
48
|
+
*/
|
|
49
|
+
required?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Whether the trigger element shows a caution state — the selection is valid but is
|
|
52
|
+
* worth double-checking.
|
|
53
|
+
*
|
|
54
|
+
* Unlike `invalid`, this does not set `aria-invalid` — the state is advisory, not an error.
|
|
55
|
+
* It changes the border color only, so you must also render an adjacent
|
|
56
|
+
* `<HelpMessage messageType="caution">` describing the concern — color alone does not
|
|
57
|
+
* convey the state (WCAG 2.2 1.4.1).
|
|
58
|
+
*
|
|
59
|
+
* Associate that `HelpMessage` with the trigger via `triggerProps['aria-describedby']` so
|
|
60
|
+
* screen readers announce it on focus, and give it `role="status"` so it is also announced
|
|
61
|
+
* the moment it appears while focus is already on the trigger.
|
|
62
|
+
*
|
|
63
|
+
* ```tsx
|
|
64
|
+
* const cautionMessageId = useId();
|
|
65
|
+
*
|
|
66
|
+
* <MultipleSelectBox caution triggerProps={{ 'aria-describedby': cautionMessageId }} />
|
|
67
|
+
* <HelpMessage id={cautionMessageId} role="status" messageType="caution">
|
|
68
|
+
* 通常より多くの項目が選択されています。
|
|
69
|
+
* </HelpMessage>
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* Ignored while `invalid` is `true`, because the error state takes precedence.
|
|
73
|
+
*
|
|
74
|
+
* @default false
|
|
75
|
+
*/
|
|
76
|
+
caution?: boolean;
|
|
42
77
|
/**
|
|
43
78
|
* The properties for the trigger element.
|
|
44
79
|
*
|
|
45
80
|
* @property aria-label - The alternative text for the trigger element. It is overridden by associated label using labelId prop.
|
|
46
81
|
*
|
|
82
|
+
* @property aria-describedby - The id of the element describing the trigger element,
|
|
83
|
+
* such as an adjacent `HelpMessage`.
|
|
84
|
+
*
|
|
47
85
|
* @property className - The class name for the trigger element.
|
|
48
86
|
*
|
|
49
87
|
* @default undefined
|
|
50
88
|
*/
|
|
51
|
-
triggerProps?: Pick<ComponentPropsWithoutRef<'button'>, 'aria-label' | 'className'>;
|
|
89
|
+
triggerProps?: Pick<ComponentPropsWithoutRef<'button'>, 'aria-label' | 'aria-describedby' | 'className'>;
|
|
52
90
|
/**
|
|
53
91
|
* The properties for the wrapper element of the trigger element.
|
|
54
92
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { useLatest } from '../../utilities/state/useLatest';
|
|
3
4
|
import { normalizeFullWidth } from '../../utilities/string/normalizeFullWidth';
|
|
4
5
|
/**
|
|
5
6
|
* Custom hook to handle search functionality within a SelectBox component.
|
|
@@ -18,6 +19,10 @@ export const useSearchBox = (options, enableSearchOptions, onSearchOptions) => {
|
|
|
18
19
|
const [searchText, setSearchText] = useState('');
|
|
19
20
|
const debounceRef = useRef(null);
|
|
20
21
|
const previousSearchTextRef = useRef('');
|
|
22
|
+
// Keep the latest handler in a ref so that a new handler identity (e.g. an
|
|
23
|
+
// inline function re-created on parent re-render) does not re-run the
|
|
24
|
+
// debounce effect and cancel a pending call.
|
|
25
|
+
const onSearchOptionsRef = useLatest(onSearchOptions);
|
|
21
26
|
const filteredOptions = useMemo(() => {
|
|
22
27
|
if (!enableSearchOptions || onSearchOptions) {
|
|
23
28
|
return options;
|
|
@@ -29,8 +34,14 @@ export const useSearchBox = (options, enableSearchOptions, onSearchOptions) => {
|
|
|
29
34
|
const onSearchTextChange = (event) => {
|
|
30
35
|
setSearchText(event.target.value);
|
|
31
36
|
};
|
|
37
|
+
// Track only the presence of the handler (not its identity) so that an
|
|
38
|
+
// undefined → defined transition re-runs the effect and dispatches the
|
|
39
|
+
// pending search text, while identity changes do not cancel pending calls.
|
|
40
|
+
// This preserves the behavior of the previous implementation, where the
|
|
41
|
+
// handler itself was an effect dependency and the transition dispatched.
|
|
42
|
+
const hasSearchHandler = Boolean(onSearchOptions);
|
|
32
43
|
useEffect(() => {
|
|
33
|
-
if (!enableSearchOptions || !
|
|
44
|
+
if (!enableSearchOptions || !hasSearchHandler)
|
|
34
45
|
return;
|
|
35
46
|
// Skip if searchText hasn't changed (prevents initial mount and unnecessary calls)
|
|
36
47
|
if (searchText === previousSearchTextRef.current) {
|
|
@@ -41,14 +52,14 @@ export const useSearchBox = (options, enableSearchOptions, onSearchOptions) => {
|
|
|
41
52
|
clearTimeout(debounceRef.current);
|
|
42
53
|
}
|
|
43
54
|
debounceRef.current = setTimeout(() => {
|
|
44
|
-
|
|
55
|
+
onSearchOptionsRef.current?.(searchText);
|
|
45
56
|
}, 300);
|
|
46
57
|
return () => {
|
|
47
58
|
if (debounceRef.current) {
|
|
48
59
|
clearTimeout(debounceRef.current);
|
|
49
60
|
}
|
|
50
61
|
};
|
|
51
|
-
}, [searchText,
|
|
62
|
+
}, [searchText, enableSearchOptions, hasSearchHandler, onSearchOptionsRef]);
|
|
52
63
|
return {
|
|
53
64
|
onSearchTextChange,
|
|
54
65
|
filteredOptions,
|
|
@@ -253,7 +253,7 @@ export function Popover({ renderTrigger, renderContent, open, onOpenStateChanged
|
|
|
253
253
|
});
|
|
254
254
|
};
|
|
255
255
|
}, [isPopoverOpen, refs.reference, refs.floating]);
|
|
256
|
-
return (_jsxs(_Fragment, { children: [renderTrigger(options), shouldRenderTheContent ? (_jsx(Portal, { targetDOMNode: targetElement, children: _jsx("div", { ref: refs.setFloating, tabIndex: contentProps.tabIndex ?? 0, "data-mfui-content": "popover-portal-content", "data-mfui-popover-depth": nestingDepth, className: cx(classes.root, 'mfui-Popover__root', contentProps.className), style: {
|
|
256
|
+
return (_jsxs(_Fragment, { children: [renderTrigger(options), shouldRenderTheContent ? (_jsx(Portal, { targetDOMNode: targetElement, children: _jsx("div", { ref: refs.setFloating, id: contentProps.id, tabIndex: contentProps.tabIndex ?? 0, "data-mfui-content": "popover-portal-content", "data-mfui-popover-depth": nestingDepth, className: cx(classes.root, 'mfui-Popover__root', contentProps.className), style: {
|
|
257
257
|
...floatingStyles,
|
|
258
258
|
...(!enableAutoUnmount && !isPopoverOpen && { display: 'none' }),
|
|
259
259
|
}, onKeyDown: handleKeyDownPopover, children: _jsx(TargetDomNodeProvider, { targetDomNode: refs.floating.current, children: _jsx(PopoverContextProvider, { children: renderContent(options) }) }) }) })) : null] }));
|
|
@@ -126,7 +126,7 @@ export type PopoverProps = {
|
|
|
126
126
|
*
|
|
127
127
|
* @default {}
|
|
128
128
|
*/
|
|
129
|
-
contentProps?: Pick<ComponentProps<'div'>, 'className'> & {
|
|
129
|
+
contentProps?: Pick<ComponentProps<'div'>, 'className' | 'id'> & {
|
|
130
130
|
/**
|
|
131
131
|
* The tab index of the popover content element.
|
|
132
132
|
* Please note if you set this prop, it may cause the incorrect focus behavior when `enableAutoFocusOnPopover` is true.
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { type BasedAdditionalProps, type SelectBoxProps } from './SelectBox.types';
|
|
3
3
|
/**
|
|
4
4
|
* The general-purpose SelectBox component.
|
|
5
|
-
* This component switches the variants of looks and behaviors depends on the props: size, invalid, disabled.
|
|
5
|
+
* This component switches the variants of looks and behaviors depends on the props: size, invalid, caution, disabled.
|
|
6
6
|
*/
|
|
7
7
|
export declare const SelectBox: (<T extends string | number | undefined = string, AdditionalProps extends BasedAdditionalProps = BasedAdditionalProps>(props: SelectBoxProps<T, AdditionalProps> & {
|
|
8
8
|
ref?: React.ForwardedRef<HTMLInputElement>;
|
|
@@ -33,12 +33,12 @@ const DEFAULT_SCROLL_OPTIONS = {
|
|
|
33
33
|
const SKELETON_ITEM_COUNT = 4;
|
|
34
34
|
/**
|
|
35
35
|
* The general-purpose SelectBox component.
|
|
36
|
-
* This component switches the variants of looks and behaviors depends on the props: size, invalid, disabled.
|
|
36
|
+
* This component switches the variants of looks and behaviors depends on the props: size, invalid, caution, disabled.
|
|
37
37
|
*/
|
|
38
38
|
export const SelectBox = forwardRef((props, ref) => {
|
|
39
39
|
const { id, triggerProps, clearButtonProps, enableClearButton = false, size, options = [], defaultValue,
|
|
40
40
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
41
|
-
placeholder, emptyMessage, disabled, invalid, required, targetDOMNode, name, onChange, onBlur, value, enableSearchOptions = false, notFoundMessage, searchBoxProps, loading = false, onSearchOptions, renderDisplayValue, renderOption, renderPopoverContent, enableAutoScrollToSelectedOption, popoverContentProps, popoverWrapperProps, onOpenStateChanged, showGroupOptionDivider, triggerWrapperProps, enableAutoUnmount = true, enableConstrainedPopoverWidth = false, enableVirtualization = false, virtualizationOptions, infiniteScroll, } = props;
|
|
41
|
+
placeholder, emptyMessage, disabled, invalid, caution = false, required, targetDOMNode, name, onChange, onBlur, value, enableSearchOptions = false, notFoundMessage, searchBoxProps, loading = false, onSearchOptions, renderDisplayValue, renderOption, renderPopoverContent, enableAutoScrollToSelectedOption, popoverContentProps, popoverWrapperProps, onOpenStateChanged, showGroupOptionDivider, triggerWrapperProps, enableAutoUnmount = true, enableConstrainedPopoverWidth = false, enableVirtualization = false, virtualizationOptions, infiniteScroll, } = props;
|
|
42
42
|
const classes = selectBoxSlotRecipe({ size, showGroupOptionDivider });
|
|
43
43
|
const triggerRef = useRef(null);
|
|
44
44
|
const listBoxId = useId();
|
|
@@ -53,7 +53,14 @@ export const SelectBox = forwardRef((props, ref) => {
|
|
|
53
53
|
triggerRef.current?.focus();
|
|
54
54
|
},
|
|
55
55
|
});
|
|
56
|
-
const { searchText, onSearchTextChange, filteredOptions } = useSearchBox(options, enableSearchOptions, onSearchOptions);
|
|
56
|
+
const { searchText, onSearchTextChange, filteredOptions, resetSearchText } = useSearchBox(options, enableSearchOptions, onSearchOptions);
|
|
57
|
+
// Clear the search text whenever the option panel closes so that
|
|
58
|
+
// reopening always starts from an unfiltered option list.
|
|
59
|
+
useUpdateEffect(() => {
|
|
60
|
+
if (!isOptionPanelOpen) {
|
|
61
|
+
resetSearchText();
|
|
62
|
+
}
|
|
63
|
+
}, [isOptionPanelOpen, resetSearchText]);
|
|
57
64
|
const selectedOption = useMemo(() => {
|
|
58
65
|
if (value === undefined)
|
|
59
66
|
return null;
|
|
@@ -331,7 +338,7 @@ export const SelectBox = forwardRef((props, ref) => {
|
|
|
331
338
|
const hasSelectedValue = value !== undefined || localSelectedOption !== null;
|
|
332
339
|
const showClearButton = enableClearButton && hasSelectedValue;
|
|
333
340
|
// Use Popover's centralized blur handling instead of custom logic
|
|
334
|
-
return (_jsx(Popover, { open: isOptionPanelOpen, targetDOMNode: targetDOMNode, minWidth: popoverContentProps?.minWidth, maxHeight: popoverWrapperProps?.maxHeight, allowedPlacements: popoverContentProps?.allowedPlacements, enableConstrainedContentWidth: enableConstrainedPopoverWidth, renderContent: () => (_jsx("div", { ref: optionPanelRef, id: listBoxId, className: cx(classes.optionPanel, 'mfui-SelectBox__optionPanel', popoverContentProps?.className), tabIndex: -1, onKeyDown: handleKeyDownMenu, children: renderPopoverContent ? (renderPopoverContent({ searchNode, optionsNode })) : (_jsxs(_Fragment, { children: [searchNode, optionsNode] })) })), renderTrigger: ({ setTriggerRef, togglePopover, isPopoverOpen, handleTriggerKeyDown, handleTriggerBlur }) => (_jsxs("div", { ref: setTriggerRef, ...triggerWrapperProps, className: cx(classes.triggerWrapper, 'mfui-SelectBox__triggerWrapper', triggerWrapperProps?.className), children: [_jsx(FocusIndicator, { children: _jsxs("button", { ref: triggerRef, id: id, type: "button", role: "combobox", disabled: disabled, "aria-label": triggerProps?.['aria-label'], "aria-controls": listBoxId, "aria-expanded": isPopoverOpen, "aria-haspopup": "listbox", "aria-invalid": invalid, "aria-required": required, className: cx(classes.trigger, 'mfui-SelectBox__trigger', triggerProps?.className), "data-placeholder": !!placeholder && !localSelectedOption, "data-selected": !!localSelectedOption, "data-mfui-has-clear-button": showClearButton, onClick: togglePopover, onKeyDown: (event) => {
|
|
341
|
+
return (_jsx(Popover, { open: isOptionPanelOpen, targetDOMNode: targetDOMNode, minWidth: popoverContentProps?.minWidth, maxHeight: popoverWrapperProps?.maxHeight, allowedPlacements: popoverContentProps?.allowedPlacements, enableConstrainedContentWidth: enableConstrainedPopoverWidth, renderContent: () => (_jsx("div", { ref: optionPanelRef, id: listBoxId, className: cx(classes.optionPanel, 'mfui-SelectBox__optionPanel', popoverContentProps?.className), tabIndex: -1, onKeyDown: handleKeyDownMenu, children: renderPopoverContent ? (renderPopoverContent({ searchNode, optionsNode })) : (_jsxs(_Fragment, { children: [searchNode, optionsNode] })) })), renderTrigger: ({ setTriggerRef, togglePopover, isPopoverOpen, handleTriggerKeyDown, handleTriggerBlur }) => (_jsxs("div", { ref: setTriggerRef, ...triggerWrapperProps, className: cx(classes.triggerWrapper, 'mfui-SelectBox__triggerWrapper', triggerWrapperProps?.className), children: [_jsx(FocusIndicator, { children: _jsxs("button", { ref: triggerRef, id: id, type: "button", role: "combobox", disabled: disabled, "aria-label": triggerProps?.['aria-label'], "aria-controls": listBoxId, "aria-describedby": triggerProps?.['aria-describedby'], "aria-expanded": isPopoverOpen, "aria-haspopup": "listbox", "aria-invalid": invalid, "aria-required": required, className: cx(classes.trigger, 'mfui-SelectBox__trigger', triggerProps?.className), "data-placeholder": !!placeholder && !localSelectedOption, "data-selected": !!localSelectedOption, "data-mfui-has-clear-button": showClearButton, "data-mfui-caution": caution && !invalid ? true : undefined, onClick: togglePopover, onKeyDown: (event) => {
|
|
335
342
|
handleTypeAhead(event.nativeEvent);
|
|
336
343
|
handleTriggerKeyDown(event);
|
|
337
344
|
}, onBlur: handleTriggerBlur, children: [_jsx("span", { "data-mfui-content": "select-box-trigger-display-value", children: renderTriggerLabel() }), _jsx(DropdownIcon, { "aria-hidden": "true", focusable: false }), _jsx("input", { ref: ref, type: "hidden", value: localSelectedOption?.value ?? '', name: name, disabled: disabled })] }) }), showClearButton ? (_jsx("div", { className: cx(classes.clearButtonWrapper, 'mfui-SelectBox__clearButtonWrapper'), children: _jsx(ClearButton, { size: size === 'small' ? 'small' : size === 'large' ? 'large' : 'default', "aria-label": clearButtonProps?.['aria-label'] ?? '値をクリアする', "data-mfui-content": "select-box-clear-button", onClick: (event) => {
|
|
@@ -157,9 +157,12 @@ export type SelectBoxProps<T extends AllowedValueTypes = string, AdditionalProps
|
|
|
157
157
|
*
|
|
158
158
|
* @property aria-label - The alternative text for the trigger element.
|
|
159
159
|
*
|
|
160
|
+
* @property aria-describedby - The id of the element describing the trigger element,
|
|
161
|
+
* such as an adjacent `HelpMessage`.
|
|
162
|
+
*
|
|
160
163
|
* @property className - The class name of the trigger element.
|
|
161
164
|
*/
|
|
162
|
-
triggerProps?: Pick<ComponentPropsWithoutRef<'button'>, 'aria-label' | 'className'>;
|
|
165
|
+
triggerProps?: Pick<ComponentPropsWithoutRef<'button'>, 'aria-label' | 'aria-describedby' | 'className'>;
|
|
163
166
|
/**
|
|
164
167
|
* Whether you let user to clear the content or not.
|
|
165
168
|
* This will add the Clear button if current SelectBox has value.
|
|
@@ -227,6 +230,33 @@ export type SelectBoxProps<T extends AllowedValueTypes = string, AdditionalProps
|
|
|
227
230
|
* Whether the trigger is invalid.
|
|
228
231
|
*/
|
|
229
232
|
invalid?: boolean;
|
|
233
|
+
/**
|
|
234
|
+
* Whether the trigger needs the user's attention while still being submittable.
|
|
235
|
+
* Use it when the selection is valid but is worth double-checking.
|
|
236
|
+
*
|
|
237
|
+
* Unlike `invalid`, this does not set `aria-invalid` — the state is advisory, not an error.
|
|
238
|
+
* It changes the border color only, so you must also render an adjacent
|
|
239
|
+
* `<HelpMessage messageType="caution">` describing the concern — color alone does not
|
|
240
|
+
* convey the state (WCAG 2.2 1.4.1).
|
|
241
|
+
*
|
|
242
|
+
* Associate that `HelpMessage` with the trigger via `triggerProps['aria-describedby']` so
|
|
243
|
+
* screen readers announce it on focus, and give it `role="status"` so it is also announced
|
|
244
|
+
* the moment it appears while focus is already on the trigger.
|
|
245
|
+
*
|
|
246
|
+
* ```tsx
|
|
247
|
+
* const cautionMessageId = useId();
|
|
248
|
+
*
|
|
249
|
+
* <SelectBox caution triggerProps={{ 'aria-describedby': cautionMessageId }} />
|
|
250
|
+
* <HelpMessage id={cautionMessageId} role="status" messageType="caution">
|
|
251
|
+
* 通常とは異なる勘定科目が選択されています。
|
|
252
|
+
* </HelpMessage>
|
|
253
|
+
* ```
|
|
254
|
+
*
|
|
255
|
+
* Ignored while `invalid` is `true`, because the error state takes precedence.
|
|
256
|
+
*
|
|
257
|
+
* @default false
|
|
258
|
+
*/
|
|
259
|
+
caution?: boolean;
|
|
230
260
|
/**
|
|
231
261
|
* Whether the trigger is required.
|
|
232
262
|
* When true, `aria-required="true"` is forwarded to the trigger so that
|
|
@@ -361,6 +391,13 @@ export type SelectBoxProps<T extends AllowedValueTypes = string, AdditionalProps
|
|
|
361
391
|
* Handler executed when text is entered in the search box.
|
|
362
392
|
* The handler is debounced by 300ms.
|
|
363
393
|
*
|
|
394
|
+
* Also called with an empty string when the option panel closes, because the
|
|
395
|
+
* search text is reset on close so that reopening shows the full option list.
|
|
396
|
+
*
|
|
397
|
+
* If the handler is provided after the user has already typed (e.g.
|
|
398
|
+
* `onSearchOptions={isReady ? handleSearch : undefined}`), it is called
|
|
399
|
+
* once with the current search text so that pending input is not lost.
|
|
400
|
+
*
|
|
364
401
|
* When this prop is provided, client-side filtering is disabled as it's
|
|
365
402
|
* assumed that filtering will be handled by the prop.
|
|
366
403
|
*/
|
|
@@ -10,7 +10,8 @@ import { type BasedAdditionalProps, type SelectBoxOption } from '../SelectBox.ty
|
|
|
10
10
|
* provided, client-side filtering is disabled as it's assumed that the caller
|
|
11
11
|
* will handle the filtering.
|
|
12
12
|
*
|
|
13
|
-
* @returns Object containing the filtered options, search text,
|
|
13
|
+
* @returns Object containing the filtered options, search text, handler to update search text,
|
|
14
|
+
* and a callback to reset the search text.
|
|
14
15
|
*/
|
|
15
16
|
export declare const useSearchBox: <T extends string | number | undefined = string, AdditionalProps extends BasedAdditionalProps = BasedAdditionalProps>(options: (SelectBoxOption<T, AdditionalProps> | null | undefined)[], enableSearchOptions: boolean, onSearchOptions?: (text: string) => void) => {
|
|
16
17
|
/**
|
|
@@ -25,4 +26,8 @@ export declare const useSearchBox: <T extends string | number | undefined = stri
|
|
|
25
26
|
* Current search text value.
|
|
26
27
|
*/
|
|
27
28
|
searchText: string;
|
|
29
|
+
/**
|
|
30
|
+
* Resets the search text to an empty string.
|
|
31
|
+
*/
|
|
32
|
+
resetSearchText: () => void;
|
|
28
33
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { useLatest } from '../../utilities/state/useLatest';
|
|
3
4
|
import { normalizeFullWidth } from '../../utilities/string/normalizeFullWidth';
|
|
4
5
|
/**
|
|
5
6
|
* Custom hook to handle search functionality within a SelectBox component.
|
|
@@ -12,12 +13,17 @@ import { normalizeFullWidth } from '../../utilities/string/normalizeFullWidth';
|
|
|
12
13
|
* provided, client-side filtering is disabled as it's assumed that the caller
|
|
13
14
|
* will handle the filtering.
|
|
14
15
|
*
|
|
15
|
-
* @returns Object containing the filtered options, search text,
|
|
16
|
+
* @returns Object containing the filtered options, search text, handler to update search text,
|
|
17
|
+
* and a callback to reset the search text.
|
|
16
18
|
*/
|
|
17
19
|
export const useSearchBox = (options, enableSearchOptions, onSearchOptions) => {
|
|
18
20
|
const [searchText, setSearchText] = useState('');
|
|
19
21
|
const debounceRef = useRef(null);
|
|
20
22
|
const previousSearchTextRef = useRef('');
|
|
23
|
+
// Keep the latest handler in a ref so that a new handler identity (e.g. an
|
|
24
|
+
// inline function re-created on parent re-render) does not re-run the
|
|
25
|
+
// debounce effect and cancel a pending call.
|
|
26
|
+
const onSearchOptionsRef = useLatest(onSearchOptions);
|
|
21
27
|
const filteredOptions = useMemo(() => {
|
|
22
28
|
// If not searchable or if onSearchOptions is provided, return all options without filtering
|
|
23
29
|
if (!enableSearchOptions || onSearchOptions) {
|
|
@@ -49,8 +55,17 @@ export const useSearchBox = (options, enableSearchOptions, onSearchOptions) => {
|
|
|
49
55
|
const handleSearchTextChange = useCallback((event) => {
|
|
50
56
|
setSearchText(event.target.value);
|
|
51
57
|
}, []);
|
|
58
|
+
const resetSearchText = useCallback(() => {
|
|
59
|
+
setSearchText('');
|
|
60
|
+
}, []);
|
|
61
|
+
// Track only the presence of the handler (not its identity) so that an
|
|
62
|
+
// undefined → defined transition re-runs the effect and dispatches the
|
|
63
|
+
// pending search text, while identity changes do not cancel pending calls.
|
|
64
|
+
// This preserves the behavior of the previous implementation, where the
|
|
65
|
+
// handler itself was an effect dependency and the transition dispatched.
|
|
66
|
+
const hasSearchHandler = Boolean(onSearchOptions);
|
|
52
67
|
useEffect(() => {
|
|
53
|
-
if (!enableSearchOptions || !
|
|
68
|
+
if (!enableSearchOptions || !hasSearchHandler)
|
|
54
69
|
return;
|
|
55
70
|
// Skip if searchText hasn't changed (prevents initial mount and unnecessary calls)
|
|
56
71
|
if (searchText === previousSearchTextRef.current) {
|
|
@@ -61,14 +76,14 @@ export const useSearchBox = (options, enableSearchOptions, onSearchOptions) => {
|
|
|
61
76
|
clearTimeout(debounceRef.current);
|
|
62
77
|
}
|
|
63
78
|
debounceRef.current = setTimeout(() => {
|
|
64
|
-
|
|
79
|
+
onSearchOptionsRef.current?.(searchText);
|
|
65
80
|
}, 300);
|
|
66
81
|
return () => {
|
|
67
82
|
if (debounceRef.current) {
|
|
68
83
|
clearTimeout(debounceRef.current);
|
|
69
84
|
}
|
|
70
85
|
};
|
|
71
|
-
}, [searchText,
|
|
86
|
+
}, [searchText, enableSearchOptions, hasSearchHandler, onSearchOptionsRef]);
|
|
72
87
|
return {
|
|
73
88
|
/**
|
|
74
89
|
* Handler for search input change events.
|
|
@@ -82,5 +97,9 @@ export const useSearchBox = (options, enableSearchOptions, onSearchOptions) => {
|
|
|
82
97
|
* Current search text value.
|
|
83
98
|
*/
|
|
84
99
|
searchText,
|
|
100
|
+
/**
|
|
101
|
+
* Resets the search text to an empty string.
|
|
102
|
+
*/
|
|
103
|
+
resetSearchText,
|
|
85
104
|
};
|
|
86
105
|
};
|
|
@@ -195,6 +195,20 @@ export const SplitView = forwardRef(({ className, leftPanelSlot, rightPanelSlot,
|
|
|
195
195
|
isDragging,
|
|
196
196
|
dividerPropsStyle: dividerProps?.style,
|
|
197
197
|
});
|
|
198
|
+
// `dividerProps` is spread after the built-in attributes so that consumers can override ARIA
|
|
199
|
+
// attributes (e.g. aria-label, aria-valuenow). These four entries must be excluded from that
|
|
200
|
+
// spread and re-applied afterwards, otherwise they would silently replace internal wiring:
|
|
201
|
+
// the styling classes/styles and the resize event handlers.
|
|
202
|
+
const { className: dividerClassName, style: _dividerPropsStyle, onPointerDown: dividerOnPointerDown, onKeyDown: dividerOnKeyDown, ...restDividerProps } = dividerProps ?? {};
|
|
203
|
+
// Run the internal resize handler first, then forward the event to the consumer's handler.
|
|
204
|
+
const handleDividerPointerDown = (event) => {
|
|
205
|
+
handleMouseDown(event);
|
|
206
|
+
dividerOnPointerDown?.(event);
|
|
207
|
+
};
|
|
208
|
+
const handleDividerKeyDown = (event) => {
|
|
209
|
+
handleKeyDown(event);
|
|
210
|
+
dividerOnKeyDown?.(event);
|
|
211
|
+
};
|
|
198
212
|
// Apply controlled panel style to the correct panel based on targetPanel.
|
|
199
213
|
// When a panel is the main panel (not controlled), flex: 1 ensures it fills remaining space.
|
|
200
214
|
const computedLeftPanelStyle = resolvedTargetPanel === 'left' ? controlledPanelOuterStyle : leftPanelProps?.style;
|
|
@@ -209,7 +223,7 @@ export const SplitView = forwardRef(({ className, leftPanelSlot, rightPanelSlot,
|
|
|
209
223
|
ref.current = node;
|
|
210
224
|
}
|
|
211
225
|
containerRef.current = node;
|
|
212
|
-
}, className: cx(classes.root, 'mfui-SplitView__root', className), ...props, children: [resolvedTargetPanel === 'left' ? (shouldRenderControlledPanel || isSlideOutAnimating ? (_jsx("div", { ...leftPanelProps, className: cx(classes.leftPanel, 'mfui-SplitView__leftPanel', leftPanelProps?.className), style: computedLeftPanelStyle, children: _jsx("div", { style: controlledPanelInnerStyle, children: enableAutoUnmount ? (leftPanelSlot ?? lastRightPanelRef.current) : leftPanelSlot }) })) : null) : (_jsx("div", { ...leftPanelProps, className: cx(classes.leftPanel, 'mfui-SplitView__leftPanel', leftPanelProps?.className), style: computedLeftPanelStyle, children: leftPanelSlot })), isRightPanelVisible || isSlideOutAnimating ? (_jsx("div", {
|
|
226
|
+
}, className: cx(classes.root, 'mfui-SplitView__root', className), ...props, children: [resolvedTargetPanel === 'left' ? (shouldRenderControlledPanel || isSlideOutAnimating ? (_jsx("div", { ...leftPanelProps, className: cx(classes.leftPanel, 'mfui-SplitView__leftPanel', leftPanelProps?.className), style: computedLeftPanelStyle, children: _jsx("div", { style: controlledPanelInnerStyle, children: enableAutoUnmount ? (leftPanelSlot ?? lastRightPanelRef.current) : leftPanelSlot }) })) : null) : (_jsx("div", { ...leftPanelProps, className: cx(classes.leftPanel, 'mfui-SplitView__leftPanel', leftPanelProps?.className), style: computedLeftPanelStyle, children: leftPanelSlot })), isRightPanelVisible || isSlideOutAnimating ? (_jsx("div", { role: "separator", "aria-orientation": "vertical", "aria-label": dividerProps?.['aria-label'] ?? '左右のパネルを調整', "aria-valuenow": currentControlledPanelSize ?? initialPanelSizeNumber, "aria-valuemin": minPanelSizeNumber, "aria-valuemax": maxPanelSizeNumber,
|
|
213
227
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
214
|
-
tabIndex: 0, style: dividerStyle, onPointerDown:
|
|
228
|
+
tabIndex: 0, ...restDividerProps, className: cx(classes.divider, 'mfui-SplitView__divider', dividerClassName), style: dividerStyle, onPointerDown: handleDividerPointerDown, onKeyDown: handleDividerKeyDown })) : null, resolvedTargetPanel === 'right' ? (shouldRenderControlledPanel || isSlideOutAnimating ? (_jsx("div", { ...rightPanelProps, className: cx(classes.rightPanel, 'mfui-SplitView__rightPanel', rightPanelProps?.className), style: computedRightPanelStyle, children: _jsx("div", { style: controlledPanelInnerStyle, children: enableAutoUnmount ? (rightPanelSlot ?? lastRightPanelRef.current) : rightPanelSlot }) })) : null) : (_jsx("div", { ...rightPanelProps, className: cx(classes.rightPanel, 'mfui-SplitView__rightPanel', rightPanelProps?.className), style: computedRightPanelStyle, children: rightPanelSlot }))] }));
|
|
215
229
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
/**
|
|
3
3
|
* The general-purpose TextBox component.
|
|
4
|
-
* This component switches the variants of looks and behaviors depends on the props: size, invalid, disabled, enableClearButton.
|
|
4
|
+
* This component switches the variants of looks and behaviors depends on the props: size, invalid, caution, disabled, enableClearButton.
|
|
5
5
|
*
|
|
6
6
|
* Also extends the props of `<input>` element
|
|
7
7
|
*
|
|
@@ -11,6 +11,7 @@ export declare const TextBox: React.ForwardRefExoticComponent<{
|
|
|
11
11
|
enableClearButton?: boolean;
|
|
12
12
|
onClear?: import("..").IconButtonProps['onClick'];
|
|
13
13
|
invalid?: import("../../styled-system/recipes").TextBoxSlotRecipeVariant['invalid'];
|
|
14
|
+
caution?: import("../../styled-system/recipes").TextBoxSlotRecipeVariant['caution'];
|
|
14
15
|
textBoxSize?: import("../../styled-system/recipes").TextBoxSlotRecipeVariant['size'];
|
|
15
16
|
disabled?: import("../../styled-system/recipes").TextBoxSlotRecipeVariant['disabled'];
|
|
16
17
|
wrapperProps?: React.ComponentPropsWithRef<'div'>;
|
|
@@ -11,13 +11,13 @@ import { useInputHasValue } from '../utilities/state/useInputHasValue';
|
|
|
11
11
|
import { useIsTouchDevice } from '../utilities/dom/useIsTouchDevice';
|
|
12
12
|
/**
|
|
13
13
|
* The general-purpose TextBox component.
|
|
14
|
-
* This component switches the variants of looks and behaviors depends on the props: size, invalid, disabled, enableClearButton.
|
|
14
|
+
* This component switches the variants of looks and behaviors depends on the props: size, invalid, caution, disabled, enableClearButton.
|
|
15
15
|
*
|
|
16
16
|
* Also extends the props of `<input>` element
|
|
17
17
|
*
|
|
18
18
|
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input
|
|
19
19
|
*/
|
|
20
|
-
export const TextBox = forwardRef(({ enableClearButton = false, onClear, invalid = false, textBoxSize = 'medium', disabled = false, wrapperProps = {}, prefixSlot, suffixSlot, clearButtonProps, touchDeviceReadOnly = false, ...inputProps }, ref) => {
|
|
20
|
+
export const TextBox = forwardRef(({ enableClearButton = false, onClear, invalid = false, caution = false, textBoxSize = 'medium', disabled = false, wrapperProps = {}, prefixSlot, suffixSlot, clearButtonProps, touchDeviceReadOnly = false, ...inputProps }, ref) => {
|
|
21
21
|
const isTouchDevice = useIsTouchDevice();
|
|
22
22
|
const shouldUseTouchDeviceMode = isTouchDevice && touchDeviceReadOnly;
|
|
23
23
|
const inputRef = useRef(null);
|
|
@@ -28,7 +28,8 @@ export const TextBox = forwardRef(({ enableClearButton = false, onClear, invalid
|
|
|
28
28
|
onInput: inputProps.onInput,
|
|
29
29
|
});
|
|
30
30
|
const shouldShowClearButton = enableClearButton && hasValue;
|
|
31
|
-
|
|
31
|
+
// The error state takes precedence over the caution state
|
|
32
|
+
const classes = textBoxSlotRecipe({ invalid, caution: caution && !invalid, disabled, size: textBoxSize });
|
|
32
33
|
const mergedInputRef = mergeRefs(ref, inputRef);
|
|
33
34
|
const onClickClear = useCallback((event) => {
|
|
34
35
|
if (!inputRef.current)
|