@moneyforward/mfui-components 3.30.0 → 3.31.1
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.js +2 -2
- package/dist/src/DataTable/DataTable.types.d.ts +9 -0
- package/dist/src/DataTable/DataTableCell/DataTableCell.js +2 -1
- package/dist/src/DataTable/DataTableHeaderCell/DataTableHeaderCell.js +2 -1
- package/dist/src/DataTable/DataTableProvider.d.ts +2 -2
- package/dist/src/DataTable/DataTableProvider.js +4 -1
- package/dist/src/DataTable/DataTableRow/DataTableRow.js +34 -3
- 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/YearSelector/YearSelector.js +7 -1
- package/dist/src/DropdownMenu/DropdownMenu.js +26 -2
- package/dist/src/DropdownMenu/DropdownMenuButtonTrigger.d.ts +2 -2
- package/dist/src/DropdownMenu/DropdownMenuContainer/DropdownMenuContainer.js +28 -3
- package/dist/src/DropdownMenu/DropdownMenuContainer/DropdownMenuContainer.types.d.ts +5 -2
- package/dist/src/DropdownMenu/DropdownMenuItem/DropdownMenuItem.d.ts +1 -1
- package/dist/src/DropdownMenu/DropdownMenuItem/DropdownMenuItem.js +18 -3
- package/dist/src/DropdownMenu/DropdownMenuItem/DropdownMenuItem.types.d.ts +6 -0
- package/dist/src/MainNavigation/BaseMainNavigation.d.ts +1 -1
- package/dist/src/MainNavigation/BaseMainNavigation.js +4 -2
- package/dist/src/MainNavigation/MainNavigation.js +2 -2
- package/dist/src/MainNavigation/MainNavigation.types.d.ts +18 -0
- package/dist/src/NumberBox/NumberBox.js +68 -21
- package/dist/src/SearchBox/SearchBox.js +18 -4
- package/dist/src/SidePane/SidePane.d.ts +3 -1
- package/dist/src/SidePane/SidePane.js +8 -4
- package/dist/src/SidePane/hooks/useSidePaneStateController.d.ts +7 -2
- package/dist/src/SidePane/hooks/useSidePaneStateController.js +11 -5
- package/dist/src/Tag/Tag.d.ts +1 -1
- package/dist/src/Tag/Tag.js +2 -2
- package/dist/src/Tag/Tag.types.d.ts +7 -0
- package/dist/styled-system/recipes/data-table-cell-slot-recipe.d.ts +1 -0
- package/dist/styled-system/recipes/data-table-cell-slot-recipe.js +4 -0
- package/dist/styled-system/recipes/data-table-header-cell-slot-recipe.d.ts +1 -0
- package/dist/styled-system/recipes/data-table-header-cell-slot-recipe.js +4 -0
- package/dist/styled-system/recipes/dropdown-menu-item-slot-recipe.d.ts +1 -1
- package/dist/styled-system/recipes/dropdown-menu-item-slot-recipe.js +12 -0
- package/dist/styles.css +76 -12
- package/package.json +1 -1
|
@@ -11,7 +11,7 @@ import { DataTableHeaderRow } from './DataTableHeaderRow';
|
|
|
11
11
|
import { DataTableRow } from './DataTableRow';
|
|
12
12
|
import { cx } from '../../styled-system/css';
|
|
13
13
|
import { isComponentOrWrapped } from '../utilities/react/isComponentOrWrapped';
|
|
14
|
-
const Base = forwardRef(({ className, size = 'medium', enableRowSelection = false, fixedHeader = false, leftFixedColumnIndex, rightFixedColumnIndex, layout = 'default', wrapperProps, loading = false, emptyStateSlot, children, ...props }, ref) => {
|
|
14
|
+
const Base = forwardRef(({ className, size = 'medium', enableRowSelection = false, fixedHeader = false, showVerticalBorder = false, leftFixedColumnIndex, rightFixedColumnIndex, layout = 'default', wrapperProps, loading = false, emptyStateSlot, children, ...props }, ref) => {
|
|
15
15
|
const classes = dataTableSlotRecipe({ layout });
|
|
16
16
|
// Check if DataTable has any rows by looking for DataTable.Body with DataTable.Row children
|
|
17
17
|
const hasRows = useMemo(() => {
|
|
@@ -36,7 +36,7 @@ const Base = forwardRef(({ className, size = 'medium', enableRowSelection = fals
|
|
|
36
36
|
return false;
|
|
37
37
|
}, [children, loading]);
|
|
38
38
|
const shouldShowEmptyState = !hasRows && !!emptyStateSlot && !loading;
|
|
39
|
-
return (_jsx(DataTableProvider, { size: size, enableRowSelection: enableRowSelection, fixedHeader: fixedHeader, leftFixedColumnIndex: leftFixedColumnIndex, rightFixedColumnIndex: rightFixedColumnIndex, loading: loading, layout: layout, children: _jsxs("div", { ...wrapperProps, className: cx(classes.wrapper, 'mfui-DataTable__wrapper',
|
|
39
|
+
return (_jsx(DataTableProvider, { size: size, enableRowSelection: enableRowSelection, fixedHeader: fixedHeader, showVerticalBorder: showVerticalBorder, leftFixedColumnIndex: leftFixedColumnIndex, rightFixedColumnIndex: rightFixedColumnIndex, loading: loading, layout: layout, children: _jsxs("div", { ...wrapperProps, className: cx(classes.wrapper, 'mfui-DataTable__wrapper',
|
|
40
40
|
/* TODO: Remove in v5.0.0 */ 'mfui-DataGrid__wrapper', wrapperProps?.className), children: [_jsx("table", { ref: ref, className: cx(classes.table, 'mfui-DataTable__table',
|
|
41
41
|
/* TODO: Remove in v5.0.0 */ 'mfui-DataGrid__table', className), ...props, children: children }), shouldShowEmptyState ? (_jsx("div", { className: cx(classes.emptyState, 'mfui-DataTable__emptyState',
|
|
42
42
|
/* TODO: Remove in v5.0.0 */ 'mfui-DataGrid__emptyState'), role: "status", "aria-live": "polite", children: emptyStateSlot })) : null] }) }));
|
|
@@ -38,6 +38,15 @@ export type DataTableProps = {
|
|
|
38
38
|
* @default false
|
|
39
39
|
*/
|
|
40
40
|
fixedHeader?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Whether to show vertical borders between columns.
|
|
43
|
+
* If `true`, a vertical border will be drawn between each column of the header and body.
|
|
44
|
+
*
|
|
45
|
+
* Enable this when using `colSpan` / `rowSpan` on cells to clarify the cell boundaries.
|
|
46
|
+
*
|
|
47
|
+
* @default false
|
|
48
|
+
*/
|
|
49
|
+
showVerticalBorder?: boolean;
|
|
41
50
|
/**
|
|
42
51
|
* The layout of the data table.
|
|
43
52
|
* It accepts `default` or `edge-to-edge`.
|
|
@@ -11,7 +11,7 @@ import { useFixedColumns } from '../../utilities/dom/useFixedColumns';
|
|
|
11
11
|
import { handleCheckboxCellClick } from '../utils/handleCheckbox';
|
|
12
12
|
import { mergeRefs } from '../../utilities/dom/mergeRefs';
|
|
13
13
|
export const DataTableCell = forwardRef(({ onClickCell, children, type = 'text', className, isRowHighlighted, disabled = false, depth = 0, columnIndex, disclosureIconProps: nestingIconProps, style, ...props }, ref) => {
|
|
14
|
-
const { size, leftFixedColumnIndex, rightFixedColumnIndex, loading, enableRowSelection, layout } = useDataTableContext();
|
|
14
|
+
const { size, leftFixedColumnIndex, rightFixedColumnIndex, loading, enableRowSelection, showVerticalBorder, layout, } = useDataTableContext();
|
|
15
15
|
const { cellRef, isFixedColumn, isEdgeFixedColumn, fixedPositionStyles } = useFixedColumns({
|
|
16
16
|
enableRowSelection,
|
|
17
17
|
columnIndex,
|
|
@@ -25,6 +25,7 @@ export const DataTableCell = forwardRef(({ onClickCell, children, type = 'text',
|
|
|
25
25
|
fixedColumn: isFixedColumn,
|
|
26
26
|
edgeFixedColumn: isEdgeFixedColumn,
|
|
27
27
|
isCheckboxCell: enableRowSelection && columnIndex === 0,
|
|
28
|
+
verticalBorder: showVerticalBorder,
|
|
28
29
|
layout,
|
|
29
30
|
});
|
|
30
31
|
const handleClick = (event) => {
|
|
@@ -10,7 +10,7 @@ import { useFixedColumns } from '../../utilities/dom/useFixedColumns';
|
|
|
10
10
|
import { handleCheckboxCellClick } from '../utils/handleCheckbox';
|
|
11
11
|
import { mergeRefs } from '../../utilities/dom/mergeRefs';
|
|
12
12
|
export const DataTableHeaderCell = forwardRef(({ children, type = 'text', sortButtonProps, sortOrder, ascendingIconProps, descendingIconProps, unsortedIconProps, className, columnIndex, disabled = false, ...props }, ref) => {
|
|
13
|
-
const { size, leftFixedColumnIndex, rightFixedColumnIndex, enableRowSelection } = useDataTableContext();
|
|
13
|
+
const { size, leftFixedColumnIndex, rightFixedColumnIndex, enableRowSelection, showVerticalBorder } = useDataTableContext();
|
|
14
14
|
const { cellRef, isFixedColumn, isEdgeFixedColumn, fixedPositionStyles } = useFixedColumns({
|
|
15
15
|
enableRowSelection,
|
|
16
16
|
columnIndex,
|
|
@@ -23,6 +23,7 @@ export const DataTableHeaderCell = forwardRef(({ children, type = 'text', sortBu
|
|
|
23
23
|
fixedColumn: isFixedColumn,
|
|
24
24
|
edgeFixedColumn: isEdgeFixedColumn,
|
|
25
25
|
isCheckboxCell: enableRowSelection && columnIndex === 0,
|
|
26
|
+
verticalBorder: showVerticalBorder,
|
|
26
27
|
});
|
|
27
28
|
const isCheckboxCell = enableRowSelection && columnIndex === 0;
|
|
28
29
|
const isStringChild = typeof children === 'string';
|
|
@@ -4,7 +4,7 @@ export type DataTableSize = 'small' | 'medium';
|
|
|
4
4
|
/**
|
|
5
5
|
* The props for the `DataTableContext` component.
|
|
6
6
|
*/
|
|
7
|
-
export type DataTableContextProps = Pick<DataTableProps, 'size' | 'enableRowSelection' | 'fixedHeader' | 'leftFixedColumnIndex' | 'rightFixedColumnIndex' | 'loading' | 'layout'>;
|
|
7
|
+
export type DataTableContextProps = Pick<DataTableProps, 'size' | 'enableRowSelection' | 'fixedHeader' | 'showVerticalBorder' | 'leftFixedColumnIndex' | 'rightFixedColumnIndex' | 'loading' | 'layout'>;
|
|
8
8
|
export type DataTableProviderValues = {
|
|
9
9
|
numberColumns: number;
|
|
10
10
|
setNumberColumns: (number: number) => void;
|
|
@@ -24,4 +24,4 @@ export declare const useDataTableContext: () => DataTableProviderValues;
|
|
|
24
24
|
*
|
|
25
25
|
* This should only be used in DataTable component.
|
|
26
26
|
*/
|
|
27
|
-
export declare function DataTableProvider({ children, size, enableRowSelection, fixedHeader, leftFixedColumnIndex, rightFixedColumnIndex, loading, layout, }: PropsWithChildren<DataTableContextProps>): import("react/jsx-runtime").JSX.Element;
|
|
27
|
+
export declare function DataTableProvider({ children, size, enableRowSelection, fixedHeader, showVerticalBorder, leftFixedColumnIndex, rightFixedColumnIndex, loading, layout, }: PropsWithChildren<DataTableContextProps>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -10,6 +10,7 @@ export const DataTableContext = createContext({
|
|
|
10
10
|
size: 'medium',
|
|
11
11
|
enableRowSelection: false,
|
|
12
12
|
fixedHeader: false,
|
|
13
|
+
showVerticalBorder: false,
|
|
13
14
|
leftFixedColumnIndex: undefined,
|
|
14
15
|
rightFixedColumnIndex: undefined,
|
|
15
16
|
layout: 'default',
|
|
@@ -27,12 +28,13 @@ export const useDataTableContext = () => useContext(DataTableContext);
|
|
|
27
28
|
*
|
|
28
29
|
* This should only be used in DataTable component.
|
|
29
30
|
*/
|
|
30
|
-
export function DataTableProvider({ children, size = 'medium', enableRowSelection = false, fixedHeader = false, leftFixedColumnIndex, rightFixedColumnIndex, loading, layout = 'default', }) {
|
|
31
|
+
export function DataTableProvider({ children, size = 'medium', enableRowSelection = false, fixedHeader = false, showVerticalBorder = false, leftFixedColumnIndex, rightFixedColumnIndex, loading, layout = 'default', }) {
|
|
31
32
|
const [numberColumns, setNumberColumns] = useState(0);
|
|
32
33
|
const contextValue = useMemo(() => ({
|
|
33
34
|
size,
|
|
34
35
|
enableRowSelection,
|
|
35
36
|
fixedHeader,
|
|
37
|
+
showVerticalBorder,
|
|
36
38
|
leftFixedColumnIndex,
|
|
37
39
|
rightFixedColumnIndex,
|
|
38
40
|
loading,
|
|
@@ -43,6 +45,7 @@ export function DataTableProvider({ children, size = 'medium', enableRowSelectio
|
|
|
43
45
|
size,
|
|
44
46
|
enableRowSelection,
|
|
45
47
|
fixedHeader,
|
|
48
|
+
showVerticalBorder,
|
|
46
49
|
leftFixedColumnIndex,
|
|
47
50
|
rightFixedColumnIndex,
|
|
48
51
|
loading,
|
|
@@ -6,6 +6,36 @@ import { Checkbox } from '../../Checkbox';
|
|
|
6
6
|
import { useDataTableContext } from '../DataTableProvider';
|
|
7
7
|
import { cx } from '../../../styled-system/css';
|
|
8
8
|
import { isComponentOrWrapped } from '../../utilities/react/isComponentOrWrapped';
|
|
9
|
+
const INTERACTIVE_ELEMENT_SELECTOR = [
|
|
10
|
+
'a[href]',
|
|
11
|
+
'button',
|
|
12
|
+
'input',
|
|
13
|
+
'select',
|
|
14
|
+
'textarea',
|
|
15
|
+
'label',
|
|
16
|
+
'summary',
|
|
17
|
+
'[role="button"]',
|
|
18
|
+
'[role="link"]',
|
|
19
|
+
'[role="checkbox"]',
|
|
20
|
+
'[role="menuitem"]',
|
|
21
|
+
'[role="menuitemcheckbox"]',
|
|
22
|
+
'[role="menuitemradio"]',
|
|
23
|
+
'[role="option"]',
|
|
24
|
+
'[role="radio"]',
|
|
25
|
+
'[role="switch"]',
|
|
26
|
+
'[role="tab"]',
|
|
27
|
+
'[role="textbox"]',
|
|
28
|
+
'[contenteditable=""]',
|
|
29
|
+
'[contenteditable="true"]',
|
|
30
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
31
|
+
].join(',');
|
|
32
|
+
function isClickFromNestedInteractiveElement(event) {
|
|
33
|
+
if (!(event.target instanceof Element)) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const interactiveElement = event.target.closest(INTERACTIVE_ELEMENT_SELECTOR);
|
|
37
|
+
return interactiveElement !== null && interactiveElement !== event.currentTarget;
|
|
38
|
+
}
|
|
9
39
|
export const DataTableRow = forwardRef(({ children, disabled = false, highlighted, checkboxProps, className, onClickRow, selectable, ...props }, ref) => {
|
|
10
40
|
const { enableRowSelection: contextEnableRowSelection, layout } = useDataTableContext();
|
|
11
41
|
const isSelectable = selectable ?? contextEnableRowSelection;
|
|
@@ -24,12 +54,13 @@ export const DataTableRow = forwardRef(({ children, disabled = false, highlighte
|
|
|
24
54
|
.find((cell) => cell.props.disclosureIconProps && !cell.props.onClickCell)?.props.disclosureIconProps;
|
|
25
55
|
const ariaExpanded = isInteractive && disclosureIconProps ? (disclosureIconProps.isOpen ?? false) : undefined;
|
|
26
56
|
const handleClickRow = (event) => {
|
|
27
|
-
if (
|
|
28
|
-
|
|
57
|
+
if (disabled || isClickFromNestedInteractiveElement(event)) {
|
|
58
|
+
return;
|
|
29
59
|
}
|
|
60
|
+
onClickRow?.(event);
|
|
30
61
|
};
|
|
31
62
|
const handleKeyDownRow = (event) => {
|
|
32
|
-
if (!disabled && (event.key === 'Enter' || event.key === ' ')) {
|
|
63
|
+
if (!disabled && event.target === event.currentTarget && (event.key === 'Enter' || event.key === ' ')) {
|
|
33
64
|
onClickRow?.(event);
|
|
34
65
|
}
|
|
35
66
|
};
|
|
@@ -8,7 +8,7 @@ export declare const LABEL_SELECT_MONTH = "\u6708\u3092\u9078\u629E";
|
|
|
8
8
|
export declare const LABEL_OPEN_CALENDAR = "\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u958B\u304F";
|
|
9
9
|
export declare const LABEL_CLOSE_CALENDAR = "\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u9589\u3058\u308B";
|
|
10
10
|
export declare const LABEL_TODAY = "\u4ECA\u65E5";
|
|
11
|
-
type LabelKey = 'GO_TO_PREV_MONTH' | 'GO_TO_NEXT_MONTH' | 'GO_TO_PREV_YEAR' | 'GO_TO_NEXT_YEAR' | 'SELECT_MONTH' | 'OPEN_CALENDAR' | 'CLOSE_CALENDAR' | 'TODAY';
|
|
11
|
+
type LabelKey = 'GO_TO_PREV_MONTH' | 'GO_TO_NEXT_MONTH' | 'GO_TO_PREV_YEAR' | 'GO_TO_NEXT_YEAR' | 'SELECT_MONTH' | 'SELECT_YEAR' | 'OPEN_CALENDAR' | 'CLOSE_CALENDAR' | 'TODAY';
|
|
12
12
|
export declare const BASE_PICKER_LABELS: Record<NonNullable<BasePickerProps['calendarLocale']>, Record<LabelKey, string>>;
|
|
13
13
|
export declare const DAY_ABBREVIATIONS: Record<NonNullable<BasePickerProps['calendarLocale']>, string[]>;
|
|
14
14
|
export declare const DAY_FULL_NAMES: Record<NonNullable<BasePickerProps['calendarLocale']>, string[]>;
|
|
@@ -14,6 +14,7 @@ export const BASE_PICKER_LABELS = {
|
|
|
14
14
|
GO_TO_PREV_YEAR: '前の年',
|
|
15
15
|
GO_TO_NEXT_YEAR: '次の年',
|
|
16
16
|
SELECT_MONTH: '月を選択',
|
|
17
|
+
SELECT_YEAR: '年を選択',
|
|
17
18
|
OPEN_CALENDAR: 'カレンダーを開く',
|
|
18
19
|
CLOSE_CALENDAR: 'カレンダーを閉じる',
|
|
19
20
|
TODAY: '今日',
|
|
@@ -24,6 +25,7 @@ export const BASE_PICKER_LABELS = {
|
|
|
24
25
|
GO_TO_PREV_YEAR: 'Previous year',
|
|
25
26
|
GO_TO_NEXT_YEAR: 'Next year',
|
|
26
27
|
SELECT_MONTH: 'Select month',
|
|
28
|
+
SELECT_YEAR: 'Select year',
|
|
27
29
|
OPEN_CALENDAR: 'Open calendar',
|
|
28
30
|
CLOSE_CALENDAR: 'Close calendar',
|
|
29
31
|
TODAY: 'Today',
|
|
@@ -3,6 +3,7 @@ import { useCallback, useMemo } from 'react';
|
|
|
3
3
|
import { SelectBox } from '../../../SelectBox';
|
|
4
4
|
import { yearSelectorSlotRecipe } from '../../../../styled-system/recipes';
|
|
5
5
|
import { MAXIMUM_VIEWABLE_YEAR, MINIMUM_VIEWABLE_YEAR } from './constants';
|
|
6
|
+
import { getBasePickerLabel } from '../BasePicker/constants';
|
|
6
7
|
import { cx } from '../../../../styled-system/css';
|
|
7
8
|
import { useCalendarLocale } from '../CalendarLocale/CalendarLocaleContext';
|
|
8
9
|
import { useDisplayJapaneseCalendar } from '../CalendarLocale/DisplayJapaneseCalendarContext';
|
|
@@ -34,5 +35,10 @@ export function YearSelector({ value, onChange, filterYear, triggerProps, trigge
|
|
|
34
35
|
}
|
|
35
36
|
}, [onChange]);
|
|
36
37
|
const classes = yearSelectorSlotRecipe();
|
|
37
|
-
return (_jsx(SelectBox, { value: selectedOption, options: options, size: "small", targetDOMNode: targetDOMNode, enableAutoScrollToSelectedOption: { block: 'center', behavior: 'instant' }, popoverContentProps: { className: cx(classes.optionPanel, 'mfui-YearSelector__optionPanel') }, triggerProps:
|
|
38
|
+
return (_jsx(SelectBox, { value: selectedOption, options: options, size: "small", targetDOMNode: targetDOMNode, enableAutoScrollToSelectedOption: { block: 'center', behavior: 'instant' }, popoverContentProps: { className: cx(classes.optionPanel, 'mfui-YearSelector__optionPanel') }, triggerProps: {
|
|
39
|
+
// The trigger has role="combobox", which does not support naming from its
|
|
40
|
+
// content, so an explicit accessible name is required.
|
|
41
|
+
'aria-label': getBasePickerLabel('SELECT_YEAR', calendarLocale),
|
|
42
|
+
...triggerProps,
|
|
43
|
+
}, triggerWrapperProps: triggerWrapperProps, onChange: handleChange }));
|
|
38
44
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback, useState } from 'react';
|
|
3
|
+
import { useCallback, useRef, useState, } from 'react';
|
|
4
4
|
import { DropdownMenuButtonTrigger } from './DropdownMenuButtonTrigger';
|
|
5
5
|
import { DropdownMenuContainer } from './DropdownMenuContainer';
|
|
6
6
|
import { DropdownMenuDivider } from './DropdownMenuDivider';
|
|
@@ -29,7 +29,15 @@ import { DropdownMenuHeading } from './DropdownMenuHeading';
|
|
|
29
29
|
*/
|
|
30
30
|
export function DropdownMenu({ icon, label, children, triggerProps, tooltipLabel, tooltipPlacement, triggerMiddleTruncate, triggerTypographyVariant, menuPopoverProps, disabled, targetDOMNode, enableAutoUnmount, onOpenStateChanged, onBlur, }) {
|
|
31
31
|
const [menuDefaultFocusPosition, setMenuDefaultFocusPosition] = useState('first');
|
|
32
|
+
// Tracks keyboard activation (Enter / Space) of the trigger. The resulting click event usually
|
|
33
|
+
// has `detail === 0`, but Safari reports keyboard-triggered clicks with `detail === 1`, so the
|
|
34
|
+
// keydown itself is recorded as the reliable signal.
|
|
35
|
+
const isTriggerActivatedByKeyboardRef = useRef(false);
|
|
32
36
|
const handleTriggerKeyDownLocal = useCallback((event) => {
|
|
37
|
+
// The flag only survives from an Enter / Space keydown to its follow-up click; any other key
|
|
38
|
+
// invalidates the pending keyboard activation so the flag cannot linger when the click never
|
|
39
|
+
// fires.
|
|
40
|
+
isTriggerActivatedByKeyboardRef.current = event.key === 'Enter' || event.key === ' ';
|
|
33
41
|
switch (event.key) {
|
|
34
42
|
case 'ArrowDown':
|
|
35
43
|
event.stopPropagation();
|
|
@@ -49,7 +57,23 @@ export function DropdownMenu({ icon, label, children, triggerProps, tooltipLabel
|
|
|
49
57
|
handleTriggerKeyDownLocal(event);
|
|
50
58
|
handleTriggerKeyDown(event);
|
|
51
59
|
};
|
|
52
|
-
|
|
60
|
+
const handleBlurCommon = (event) => {
|
|
61
|
+
// Focus leaving the trigger invalidates any pending keyboard activation (e.g. a Space
|
|
62
|
+
// keydown whose follow-up click never fired), so a later mouse click cannot be
|
|
63
|
+
// misclassified as keyboard activation.
|
|
64
|
+
isTriggerActivatedByKeyboardRef.current = false;
|
|
65
|
+
handleTriggerBlur(event);
|
|
66
|
+
};
|
|
67
|
+
const handleTriggerClick = (event) => {
|
|
68
|
+
// Opening with the keyboard (Enter / Space) focuses the first item, following the
|
|
69
|
+
// WAI-ARIA menu button pattern. Opening with the mouse focuses no item; the menu
|
|
70
|
+
// container receives focus instead so that arrow keys still work.
|
|
71
|
+
const isKeyboardActivation = isTriggerActivatedByKeyboardRef.current || event.detail === 0;
|
|
72
|
+
isTriggerActivatedByKeyboardRef.current = false;
|
|
73
|
+
setMenuDefaultFocusPosition(isKeyboardActivation ? 'first' : 'none');
|
|
74
|
+
togglePopover(event);
|
|
75
|
+
};
|
|
76
|
+
return icon ? (_jsx(IconButton, { ref: setTriggerRef, "aria-expanded": isPopoverOpen, "aria-haspopup": "menu", disabled: disabled, onKeyDown: handleKeyDownCommon, onBlur: handleBlurCommon, onClick: handleTriggerClick, ...triggerProps, "aria-label": label, children: icon })) : (_jsx(DropdownMenuButtonTrigger, { label: label, tooltipLabel: tooltipLabel, tooltipPlacement: tooltipPlacement, triggerMiddleTruncate: triggerMiddleTruncate, triggerTypographyVariant: triggerTypographyVariant, triggerProps: triggerProps, disabled: disabled, isPopoverOpen: isPopoverOpen, setTriggerRef: setTriggerRef, togglePopover: handleTriggerClick, handleTriggerKeyDown: handleKeyDownCommon, handleTriggerBlur: handleBlurCommon }));
|
|
53
77
|
}, renderContent: ({ closePopover }) => (_jsx(DropdownMenuProvider, { value: { closeMenuPopover: closePopover }, children: _jsx(DropdownMenuContainer, { defaultFocusItem: menuDefaultFocusPosition, onClose: closePopover, children: children }) })), enableAutoFocusOnPopover: false, enableAutoUnmount: enableAutoUnmount, contentProps: {
|
|
54
78
|
className: cx(classes.popover, menuPopoverProps?.className),
|
|
55
79
|
}, onBlur: onBlur, onOpenStateChanged: onOpenStateChanged }));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type FocusEventHandler, type KeyboardEventHandler, type
|
|
1
|
+
import { type FocusEventHandler, type KeyboardEventHandler, type MouseEventHandler } from 'react';
|
|
2
2
|
import { type ButtonProps } from '../Button';
|
|
3
3
|
import { type DropdownMenuButtonTriggerProps } from './DropdownMenu.types';
|
|
4
4
|
type DropdownMenuButtonTriggerComponentProps = DropdownMenuButtonTriggerProps & {
|
|
@@ -25,7 +25,7 @@ type DropdownMenuButtonTriggerComponentProps = DropdownMenuButtonTriggerProps &
|
|
|
25
25
|
/**
|
|
26
26
|
* Toggles the open state of the dropdown menu.
|
|
27
27
|
*/
|
|
28
|
-
togglePopover:
|
|
28
|
+
togglePopover: MouseEventHandler<HTMLElement>;
|
|
29
29
|
/**
|
|
30
30
|
* Keyboard handler for the trigger (ArrowUp / ArrowDown focus behavior already merged in).
|
|
31
31
|
*/
|
|
@@ -11,14 +11,21 @@ import { dropdownMenuContainerSlotRecipe } from '../../../styled-system/recipes'
|
|
|
11
11
|
export const DropdownMenuContainer = forwardRef(({ children, className, defaultFocusItem: defaultFocus = 'first', style, onClose }, ref) => {
|
|
12
12
|
const classes = dropdownMenuContainerSlotRecipe();
|
|
13
13
|
const menuContainerRef = useRef(null);
|
|
14
|
+
const getFocusableItems = useCallback(() => menuContainerRef.current?.querySelectorAll('[data-mfui-menu-item]:is([data-mfui-focusable=true])'), []);
|
|
14
15
|
useEffect(() => {
|
|
15
|
-
|
|
16
|
+
if (defaultFocus === 'none') {
|
|
17
|
+
// No item should appear focused (e.g. the menu was opened with the mouse). Focus the
|
|
18
|
+
// container itself so keyboard interactions (Escape / ArrowDown / ArrowUp) keep working.
|
|
19
|
+
menuContainerRef.current?.focus({ preventScroll: true });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const focusableItems = getFocusableItems();
|
|
16
23
|
if (!focusableItems) {
|
|
17
24
|
return;
|
|
18
25
|
}
|
|
19
26
|
const defaultFocusIndex = defaultFocus === 'first' ? 0 : focusableItems.length - 1;
|
|
20
27
|
focusableItems[defaultFocusIndex]?.focus({ preventScroll: true });
|
|
21
|
-
}, [defaultFocus]);
|
|
28
|
+
}, [defaultFocus, getFocusableItems]);
|
|
22
29
|
const handleKeyDown = useCallback((event) => {
|
|
23
30
|
switch (event.key) {
|
|
24
31
|
case 'Escape':
|
|
@@ -27,7 +34,25 @@ export const DropdownMenuContainer = forwardRef(({ children, className, defaultF
|
|
|
27
34
|
event.preventDefault();
|
|
28
35
|
onClose();
|
|
29
36
|
break;
|
|
37
|
+
case 'ArrowDown':
|
|
38
|
+
case 'ArrowUp': {
|
|
39
|
+
// Focused items handle arrow navigation themselves and stop propagation, so this
|
|
40
|
+
// branch only runs while the container itself is focused (defaultFocusItem 'none'):
|
|
41
|
+
// ArrowDown moves focus to the first item and ArrowUp to the last one.
|
|
42
|
+
if (event.target !== event.currentTarget) {
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
event.stopPropagation();
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
const focusableItems = getFocusableItems();
|
|
48
|
+
if (!focusableItems || focusableItems.length === 0) {
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
const focusIndex = event.key === 'ArrowDown' ? 0 : focusableItems.length - 1;
|
|
52
|
+
focusableItems[focusIndex]?.focus();
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
30
55
|
}
|
|
31
|
-
}, [onClose]);
|
|
56
|
+
}, [onClose, getFocusableItems]);
|
|
32
57
|
return (_jsx("div", { ref: mergeRefs(ref, menuContainerRef), "data-mfui-menu-container": true, role: "menu", className: cx(classes.root, 'mfui-DropdownMenuContainer__root', className), tabIndex: -1, style: style, onKeyDown: handleKeyDown, children: children }));
|
|
33
58
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The default focus position of the menu.
|
|
3
3
|
*/
|
|
4
|
-
export type DropdownMenuDefaultFocusItem = 'first' | 'last';
|
|
4
|
+
export type DropdownMenuDefaultFocusItem = 'first' | 'last' | 'none';
|
|
5
5
|
export type DropdownMenuContainerProps = {
|
|
6
6
|
/**
|
|
7
7
|
* The content of the component.
|
|
@@ -16,10 +16,13 @@ export type DropdownMenuContainerProps = {
|
|
|
16
16
|
/**
|
|
17
17
|
* The default focus position of the dropdown menu.
|
|
18
18
|
*
|
|
19
|
-
* It should be either 'first' or '
|
|
19
|
+
* It should be either 'first', 'last' or 'none'.
|
|
20
20
|
*
|
|
21
21
|
* If 'first', the first focusable item will be focused.
|
|
22
22
|
* If 'last', the last focusable item will be focused.
|
|
23
|
+
* If 'none', no item will be focused; the menu container itself receives focus instead so that
|
|
24
|
+
* keyboard interactions (Escape / ArrowDown / ArrowUp) keep working. Use this when the menu is
|
|
25
|
+
* opened with a pointer, where visually focusing an item is not expected.
|
|
23
26
|
*
|
|
24
27
|
* @default 'first'
|
|
25
28
|
*/
|
|
@@ -12,4 +12,4 @@ import { type DropdownMenuItemProps } from './DropdownMenuItem.types';
|
|
|
12
12
|
*
|
|
13
13
|
* Otherwise, it will be rendered as a div element.
|
|
14
14
|
*/
|
|
15
|
-
export declare function DropdownMenuItem({ children, className, href, target, customLinkComponent, onClick, disabled, 'aria-labelledby': ariaLabelledby, ...rest }: DropdownMenuItemProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export declare function DropdownMenuItem({ children, className, href, target, customLinkComponent, onClick, disabled, disabledMessage, 'aria-labelledby': ariaLabelledby, 'aria-describedby': ariaDescribedby, ...rest }: DropdownMenuItemProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useId, useMemo, useRef } from 'react';
|
|
4
|
+
import { Info } from '@moneyforward/mfui-icons-react';
|
|
4
5
|
import { cx } from '../../../styled-system/css';
|
|
5
6
|
import { FocusIndicator } from '../../FocusIndicator';
|
|
6
7
|
import { dropdownMenuItemSlotRecipe } from '../../../styled-system/recipes';
|
|
@@ -19,7 +20,7 @@ import { useDropdownMenuContext } from '../DropdownMenuContext';
|
|
|
19
20
|
*
|
|
20
21
|
* Otherwise, it will be rendered as a div element.
|
|
21
22
|
*/
|
|
22
|
-
export function DropdownMenuItem({ children, className, href, target, customLinkComponent, onClick, disabled, 'aria-labelledby': ariaLabelledby, ...rest }) {
|
|
23
|
+
export function DropdownMenuItem({ children, className, href, target, customLinkComponent, onClick, disabled, disabledMessage, 'aria-labelledby': ariaLabelledby, 'aria-describedby': ariaDescribedby, ...rest }) {
|
|
23
24
|
const classes = dropdownMenuItemSlotRecipe();
|
|
24
25
|
const { closeMenuPopover } = useDropdownMenuContext();
|
|
25
26
|
const id = useId();
|
|
@@ -87,5 +88,19 @@ export function DropdownMenuItem({ children, className, href, target, customLink
|
|
|
87
88
|
: disabled
|
|
88
89
|
? { 'aria-disabled': true, 'data-disabled': '' }
|
|
89
90
|
: {};
|
|
90
|
-
|
|
91
|
+
const showMessage = Boolean(disabled && disabledMessage);
|
|
92
|
+
const labelId = `${id}-label`;
|
|
93
|
+
const messageId = `${id}-message`;
|
|
94
|
+
const label = typeof children === 'string' ? _jsx(Typography, { variant: "body", children: children }) : children;
|
|
95
|
+
// With a message, the accessible name must reference only the label span so the message
|
|
96
|
+
// stays a description; without one, keep the existing self-reference via the item's own id.
|
|
97
|
+
const resolvedAriaLabelledby = showMessage
|
|
98
|
+
? [ariaLabelledby, labelId].filter(Boolean).join(' ')
|
|
99
|
+
: ariaLabelledby
|
|
100
|
+
? `${ariaLabelledby} ${id}`
|
|
101
|
+
: undefined;
|
|
102
|
+
const resolvedAriaDescribedby = showMessage
|
|
103
|
+
? [ariaDescribedby, messageId].filter(Boolean).join(' ')
|
|
104
|
+
: ariaDescribedby;
|
|
105
|
+
return (_jsx(FocusIndicator, { position: "inside", children: _jsx(Tag, { ...rest, ref: itemRef, ...(ariaLabelledby && { id }), "data-mfui-menu-item": true, role: "menuitem", className: cx(classes.root, 'mfui-DropdownMenuItem__root', className), tabIndex: -1, href: resolvedHref, target: target, "data-mfui-focusable": isFocusable, type: onClick ? 'button' : undefined, "aria-labelledby": resolvedAriaLabelledby, "aria-describedby": resolvedAriaDescribedby, ...disabledProps, onKeyDown: handleKeyDown, onClick: isButton ? handleClick : undefined, children: showMessage ? (_jsxs("span", { className: cx(classes.content, 'mfui-DropdownMenuItem__content'), children: [_jsx("span", { id: labelId, children: label }), _jsxs("span", { id: messageId, className: cx(classes.disabledMessage, 'mfui-DropdownMenuItem__disabledMessage'), children: [_jsx(Info, { "aria-hidden": true, className: cx(classes.disabledMessageIcon, 'mfui-DropdownMenuItem__disabledMessageIcon') }), _jsx(Typography, { variant: "helpMessage", children: disabledMessage })] })] })) : (label) }) }));
|
|
91
106
|
}
|
|
@@ -77,6 +77,12 @@ export type DropdownMenuItemProps = {
|
|
|
77
77
|
* The class name to apply to the component.
|
|
78
78
|
*/
|
|
79
79
|
className?: string;
|
|
80
|
+
/**
|
|
81
|
+
* An explanatory message displayed inline below the item label when the item is disabled.
|
|
82
|
+
* Rendered with an info icon. Ignored unless `disabled` is true.
|
|
83
|
+
* Exposed to screen readers as the item's accessible description (aria-describedby).
|
|
84
|
+
*/
|
|
85
|
+
disabledMessage?: React.ReactNode;
|
|
80
86
|
/**
|
|
81
87
|
* The aria-labelledby attribute for accessibility.
|
|
82
88
|
*
|
|
@@ -3,4 +3,4 @@ import { type MainNavigationProps } from './MainNavigation.types';
|
|
|
3
3
|
* The component for the main navigation on the normal viewport.
|
|
4
4
|
* This component must only be used in MainNavigation.tsx, do not export it.
|
|
5
5
|
*/
|
|
6
|
-
export declare function BaseMainNavigation({ className, featureShortcut, navigationItems, enableCollapsible, width, customLinkComponent, disableResponsive, isCollapsed: controlledIsCollapsed, onCollapsedChange, }: MainNavigationProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export declare function BaseMainNavigation({ className, featureShortcut, navigationItems, enableCollapsible, width, customLinkComponent, disableResponsive, isCollapsed: controlledIsCollapsed, onCollapsedChange, collapseButtonProps, expandButtonProps, }: MainNavigationProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -13,7 +13,7 @@ import { Popover } from '../Popover';
|
|
|
13
13
|
* The component for the main navigation on the normal viewport.
|
|
14
14
|
* This component must only be used in MainNavigation.tsx, do not export it.
|
|
15
15
|
*/
|
|
16
|
-
export function BaseMainNavigation({ className, featureShortcut, navigationItems, enableCollapsible = false, width = 'normal', customLinkComponent, disableResponsive, isCollapsed: controlledIsCollapsed, onCollapsedChange, }) {
|
|
16
|
+
export function BaseMainNavigation({ className, featureShortcut, navigationItems, enableCollapsible = false, width = 'normal', customLinkComponent, disableResponsive, isCollapsed: controlledIsCollapsed, onCollapsedChange, collapseButtonProps, expandButtonProps, }) {
|
|
17
17
|
const [localIsCollapsed, setLocalIsCollapsed] = useState(controlledIsCollapsed ?? false);
|
|
18
18
|
const [openPopoverIndex, setOpenPopoverIndex] = useState(null);
|
|
19
19
|
const isControlled = controlledIsCollapsed !== undefined;
|
|
@@ -28,7 +28,9 @@ export function BaseMainNavigation({ className, featureShortcut, navigationItems
|
|
|
28
28
|
};
|
|
29
29
|
const classes = baseMainNavigationSlotRecipe({ width, disableResponsive });
|
|
30
30
|
const InternalLinkTag = customLinkComponent ?? 'a';
|
|
31
|
-
const toggleButtonLabel = isCollapsed
|
|
31
|
+
const toggleButtonLabel = isCollapsed
|
|
32
|
+
? (expandButtonProps?.label ?? 'ナビゲーションを拡げる')
|
|
33
|
+
: (collapseButtonProps?.label ?? 'ナビゲーションを折りたたむ');
|
|
32
34
|
return (_jsxs("div", { "data-mfui-collapsed": isCollapsed, className: cx(classes.root, 'mfui-BaseMainNavigation__root', className), children: [featureShortcut ? (_jsx("div", { className: cx(classes.featureShortcut, 'mfui-BaseMainNavigation__featureShortcut'), children: _jsx(FocusIndicator, { children: _jsx(NavigationLink, { tag: InternalLinkTag, isExternal: featureShortcut.isExternal, href: featureShortcut.href, className: cx(classes.featureShortcutAnchor, 'mfui-BaseMainNavigation__featureShortcutAnchor'), children: _jsx(Typography, { variant: "controlLabel", children: featureShortcut.label }) }) }) })) : null, _jsxs("nav", { id: navId, className: cx(classes.nav, 'mfui-BaseMainNavigation__nav'), children: [_jsx("ul", { className: cx(classes.list, 'mfui-BaseMainNavigation__list'), children: navigationItems.map((navigation, index) => {
|
|
33
35
|
const isGroupLabel = navigation.children && 'isGroupLabel' in navigation && navigation.isGroupLabel;
|
|
34
36
|
const isGroupMenu = 'isGroupMenu' in navigation && navigation.isGroupMenu;
|
|
@@ -9,7 +9,7 @@ import { NarrowViewportMainNavigation } from './NarrowViewportMainNavigation';
|
|
|
9
9
|
* When the viewport is narrow, the navigation will be displayed as a drawer.
|
|
10
10
|
* When the viewport is not narrow, the navigation will be displayed as a sidebar.
|
|
11
11
|
*/
|
|
12
|
-
export const MainNavigation = forwardRef(({ className, featureShortcut, navigationItems, disableResponsive = false, width = 'normal', customLinkComponent, enableCollapsible, isCollapsed, onCollapsedChange, }, ref) => {
|
|
12
|
+
export const MainNavigation = forwardRef(({ className, featureShortcut, navigationItems, disableResponsive = false, width = 'normal', customLinkComponent, enableCollapsible, isCollapsed, onCollapsedChange, collapseButtonProps, expandButtonProps, }, ref) => {
|
|
13
13
|
const commonProps = {
|
|
14
14
|
className,
|
|
15
15
|
featureShortcut,
|
|
@@ -17,5 +17,5 @@ export const MainNavigation = forwardRef(({ className, featureShortcut, navigati
|
|
|
17
17
|
width,
|
|
18
18
|
customLinkComponent,
|
|
19
19
|
};
|
|
20
|
-
return (_jsxs(_Fragment, { children: [_jsx(BaseMainNavigation, { ...commonProps, enableCollapsible: enableCollapsible, disableResponsive: disableResponsive, isCollapsed: isCollapsed, onCollapsedChange: onCollapsedChange }), !disableResponsive && _jsx(NarrowViewportMainNavigation, { ...commonProps, ref: ref })] }));
|
|
20
|
+
return (_jsxs(_Fragment, { children: [_jsx(BaseMainNavigation, { ...commonProps, enableCollapsible: enableCollapsible, disableResponsive: disableResponsive, isCollapsed: isCollapsed, onCollapsedChange: onCollapsedChange, collapseButtonProps: collapseButtonProps, expandButtonProps: expandButtonProps }), !disableResponsive && _jsx(NarrowViewportMainNavigation, { ...commonProps, ref: ref })] }));
|
|
21
21
|
});
|
|
@@ -230,6 +230,24 @@ export type MainNavigationProps = {
|
|
|
230
230
|
* This prop is only effective when `enableCollapsible` is `true`.
|
|
231
231
|
*/
|
|
232
232
|
isCollapsed?: boolean;
|
|
233
|
+
/**
|
|
234
|
+
* The properties for the toggle button when the navigation is expanded (clicking will collapse).
|
|
235
|
+
* This prop is only effective when `enableCollapsible` is `true`.
|
|
236
|
+
*
|
|
237
|
+
* @property label - Accessible name for the toggle button. It is used for both the tooltip content and the `aria-label` of the toggle button. default is 'ナビゲーションを折りたたむ'.
|
|
238
|
+
*/
|
|
239
|
+
collapseButtonProps?: {
|
|
240
|
+
label?: string;
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* The properties for the toggle button when the navigation is collapsed (clicking will expand).
|
|
244
|
+
* This prop is only effective when `enableCollapsible` is `true`.
|
|
245
|
+
*
|
|
246
|
+
* @property label - Accessible name for the toggle button. It is used for both the tooltip content and the `aria-label` of the toggle button. default is 'ナビゲーションを拡げる'.
|
|
247
|
+
*/
|
|
248
|
+
expandButtonProps?: {
|
|
249
|
+
label?: string;
|
|
250
|
+
};
|
|
233
251
|
/**
|
|
234
252
|
* Callback function called when the collapsed state changes.
|
|
235
253
|
* This is triggered when the toggle button is clicked.
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { forwardRef, useCallback, useLayoutEffect, useRef, useState
|
|
3
|
+
import { forwardRef, useCallback, useEffect, useLayoutEffect, useReducer, useRef, useState } from 'react';
|
|
4
4
|
import { cx } from '../../styled-system/css';
|
|
5
5
|
import { mergeRefs } from '../utilities/dom/mergeRefs';
|
|
6
6
|
import { TextBox } from '../TextBox';
|
|
7
7
|
/**
|
|
8
|
-
* Convert full-width digits, minus
|
|
8
|
+
* Convert full-width digits, minus signs, and decimal points to half-width.
|
|
9
|
+
* Kana-mode IMEs can emit prolonged sound marks for the minus key.
|
|
9
10
|
*
|
|
10
11
|
* @param value - The string to convert.
|
|
11
12
|
*/
|
|
12
|
-
const toHalfWidth = (value) => value
|
|
13
|
+
const toHalfWidth = (value) => value
|
|
14
|
+
.replaceAll(/[\uFF10-\uFF19\uFF0D\uFF0E]/g, (char) => String.fromCodePoint((char.codePointAt(0) ?? 0) - 0xfe_e0))
|
|
15
|
+
.replaceAll(/[\u3002\uFF61]/g, '.')
|
|
16
|
+
.replaceAll(/[\u2212\u30FC\uFF70\u2015]/g, '-');
|
|
13
17
|
/**
|
|
14
18
|
* Sanitize input: convert full-width characters, then keep only valid numeric characters.
|
|
15
19
|
* Allows: digits 0-9, a single leading minus, a single decimal point after a digit.
|
|
@@ -69,6 +73,35 @@ const formatWithCommas = (raw) => {
|
|
|
69
73
|
* @param display - The formatted display string.
|
|
70
74
|
*/
|
|
71
75
|
const stripFormatting = (display) => display.replaceAll(',', '');
|
|
76
|
+
/**
|
|
77
|
+
* Map a display cursor position to the equivalent index in the raw numeric string.
|
|
78
|
+
*
|
|
79
|
+
* @param display - The formatted display string.
|
|
80
|
+
* @param displayCursor - The cursor position in the display string.
|
|
81
|
+
*/
|
|
82
|
+
const mapDisplayCursorToRaw = (display, displayCursor) => {
|
|
83
|
+
const commasBefore = (display.slice(0, displayCursor).match(/,/g) ?? []).length;
|
|
84
|
+
return displayCursor - commasBefore;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Map a raw numeric cursor position to the equivalent index in the formatted display string.
|
|
88
|
+
*
|
|
89
|
+
* @param formatted - The formatted display string.
|
|
90
|
+
* @param rawCursor - The cursor position in the raw numeric string.
|
|
91
|
+
*/
|
|
92
|
+
const mapRawCursorToDisplay = (formatted, rawCursor) => {
|
|
93
|
+
let rawCount = 0;
|
|
94
|
+
let index = 0;
|
|
95
|
+
for (const char of formatted) {
|
|
96
|
+
if (rawCount >= rawCursor) {
|
|
97
|
+
return index;
|
|
98
|
+
}
|
|
99
|
+
if (char !== ',')
|
|
100
|
+
rawCount += 1;
|
|
101
|
+
index += 1;
|
|
102
|
+
}
|
|
103
|
+
return formatted.length;
|
|
104
|
+
};
|
|
72
105
|
/**
|
|
73
106
|
* NumberBox is a numeric input component that wraps TextBox.
|
|
74
107
|
* It provides real-time comma formatting, full-width digit conversion,
|
|
@@ -79,6 +112,7 @@ const stripFormatting = (display) => display.replaceAll(',', '');
|
|
|
79
112
|
export const NumberBox = forwardRef(({ onChange, value, defaultValue, textAlign = 'right', style, enableClearButton, ...rest }, ref) => {
|
|
80
113
|
const inputRef = useRef(null);
|
|
81
114
|
const pendingCursorRef = useRef(null);
|
|
115
|
+
const [cursorRestoreToken, requestCursorRestore] = useReducer((count) => count + 1, 0);
|
|
82
116
|
const [displayValue, setDisplayValue] = useState(() => formatWithCommas(sanitizeInput(String(value ?? defaultValue ?? ''))));
|
|
83
117
|
// Sync with controlled value
|
|
84
118
|
useEffect(() => {
|
|
@@ -89,16 +123,22 @@ export const NumberBox = forwardRef(({ onChange, value, defaultValue, textAlign
|
|
|
89
123
|
// Restore cursor position after display value update
|
|
90
124
|
useLayoutEffect(() => {
|
|
91
125
|
if (pendingCursorRef.current !== null && inputRef.current) {
|
|
92
|
-
inputRef.current
|
|
126
|
+
const input = inputRef.current;
|
|
127
|
+
const cursor = pendingCursorRef.current;
|
|
93
128
|
pendingCursorRef.current = null;
|
|
129
|
+
if (input.value !== displayValue) {
|
|
130
|
+
input.value = displayValue;
|
|
131
|
+
}
|
|
132
|
+
input.setSelectionRange(cursor, cursor);
|
|
94
133
|
}
|
|
95
|
-
}, [displayValue]);
|
|
134
|
+
}, [displayValue, cursorRestoreToken]);
|
|
96
135
|
const handleChange = useCallback((event) => {
|
|
136
|
+
const inputDisplay = event.target.value;
|
|
97
137
|
const cursorPos = event.target.selectionStart ?? 0;
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
let rawCursor = cursorPos
|
|
138
|
+
const prevSanitized = stripFormatting(displayValue);
|
|
139
|
+
const rawInput = stripFormatting(toHalfWidth(inputDisplay));
|
|
140
|
+
// Use the current input value so comma positions match the cursor.
|
|
141
|
+
let rawCursor = mapDisplayCursorToRaw(inputDisplay, cursorPos);
|
|
102
142
|
// When two dots appear, the user is moving the decimal point.
|
|
103
143
|
// Keep the newly typed dot (at rawCursor - 1) and remove the old one.
|
|
104
144
|
let processedRawInput = rawInput;
|
|
@@ -114,22 +154,29 @@ export const NumberBox = forwardRef(({ onChange, value, defaultValue, textAlign
|
|
|
114
154
|
rawCursor -= 1;
|
|
115
155
|
}
|
|
116
156
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
break;
|
|
157
|
+
let sanitized = sanitizeInput(processedRawInput);
|
|
158
|
+
// Backspace right after a comma deletes the digit before the comma.
|
|
159
|
+
const isBackspaceOnComma = sanitized === prevSanitized && cursorPos < displayValue.length && displayValue[cursorPos] === ',';
|
|
160
|
+
if (isBackspaceOnComma) {
|
|
161
|
+
const rawPos = mapDisplayCursorToRaw(displayValue, cursorPos);
|
|
162
|
+
if (rawPos > 0) {
|
|
163
|
+
sanitized = prevSanitized.slice(0, rawPos - 1) + prevSanitized.slice(rawPos);
|
|
164
|
+
rawCursor = rawPos - 1;
|
|
126
165
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
166
|
+
}
|
|
167
|
+
const formatted = formatWithCommas(sanitized);
|
|
168
|
+
let newCursor = mapRawCursorToDisplay(formatted, rawCursor);
|
|
169
|
+
// Rejected characters keep the cursor before the ignored input.
|
|
170
|
+
const rejectedCharCount = rawInput.length - sanitized.length;
|
|
171
|
+
if (sanitized === prevSanitized && !isBackspaceOnComma && rejectedCharCount > 0) {
|
|
172
|
+
const rawBeforeCursor = mapDisplayCursorToRaw(inputDisplay, cursorPos);
|
|
173
|
+
newCursor = mapRawCursorToDisplay(formatted, rawBeforeCursor - rejectedCharCount);
|
|
130
174
|
}
|
|
131
175
|
pendingCursorRef.current = newCursor;
|
|
132
176
|
setDisplayValue(formatted);
|
|
177
|
+
if (formatted === displayValue) {
|
|
178
|
+
requestCursorRestore();
|
|
179
|
+
}
|
|
133
180
|
onChange?.(sanitized);
|
|
134
181
|
}, [displayValue, onChange]);
|
|
135
182
|
return (_jsx(TextBox, { ref: mergeRefs(ref, inputRef), ...rest, className: cx('mfui-NumberBox__root', rest.className), enableClearButton: Boolean(enableClearButton) && displayValue !== '', style: { textAlign, ...style }, value: displayValue, onChange: handleChange }));
|
|
@@ -14,10 +14,24 @@ import { normalizeFullWidth } from '../utilities/string/normalizeFullWidth';
|
|
|
14
14
|
export const SearchBox = forwardRef(({ className, normalizeFullWidth: shouldNormalizeFullWidth = false, onChange, ...rest }, ref) => {
|
|
15
15
|
const handleChange = useCallback((event) => {
|
|
16
16
|
if (shouldNormalizeFullWidth) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
//
|
|
20
|
-
|
|
17
|
+
const { value, selectionStart } = event.target;
|
|
18
|
+
const normalizedValue = normalizeFullWidth(value);
|
|
19
|
+
// Only rewrite the native input when normalization actually changes the value.
|
|
20
|
+
// Assigning to `value` resets the caret to the end even when the string is
|
|
21
|
+
// unchanged, so skipping the no-op assignment keeps the caret in place while
|
|
22
|
+
// the user edits mid-string.
|
|
23
|
+
if (normalizedValue !== value) {
|
|
24
|
+
// Map the caret by normalizing only the text before it. This keeps the caret
|
|
25
|
+
// correct even when normalization changes the string length (e.g. `パ` -> `パ`).
|
|
26
|
+
const caretPosition = selectionStart === null
|
|
27
|
+
? normalizedValue.length
|
|
28
|
+
: normalizeFullWidth(value.slice(0, selectionStart)).length;
|
|
29
|
+
// Rewrite the native input so the displayed value reflects the normalization
|
|
30
|
+
// and the consumer's onChange sees the canonical half-width / composed-kana form.
|
|
31
|
+
// eslint-disable-next-line no-param-reassign
|
|
32
|
+
event.target.value = normalizedValue;
|
|
33
|
+
event.target.setSelectionRange(caretPosition, caretPosition);
|
|
34
|
+
}
|
|
21
35
|
}
|
|
22
36
|
onChange?.(event);
|
|
23
37
|
}, [shouldNormalizeFullWidth, onChange]);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The general purpose SidePane component.
|
|
3
|
-
* This component extends the props of `<div>` element
|
|
3
|
+
* This component extends the props of `<div>` element. The extended props and `ref` are
|
|
4
|
+
* applied to the root overlay element covering the viewport, while `role="dialog"` (and
|
|
5
|
+
* `aria-modal="true"` when `enableModal` is set) are set on the inner pane window element.
|
|
4
6
|
*
|
|
5
7
|
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog
|
|
6
8
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback, useMemo } from 'react';
|
|
3
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
4
4
|
import { Close, PaneExpansionToRight, PaneExpansionToLeft } from '@moneyforward/mfui-icons-react';
|
|
5
5
|
import { cx } from '../../styled-system/css';
|
|
6
6
|
import { Heading } from '../Heading';
|
|
@@ -13,16 +13,20 @@ import { Portal, TargetDomNodeProvider, useAutomaticTargetDomNode } from '../Por
|
|
|
13
13
|
import { FormFooter } from '../FormFooter';
|
|
14
14
|
/**
|
|
15
15
|
* The general purpose SidePane component.
|
|
16
|
-
* This component extends the props of `<div>` element
|
|
16
|
+
* This component extends the props of `<div>` element. The extended props and `ref` are
|
|
17
|
+
* applied to the root overlay element covering the viewport, while `role="dialog"` (and
|
|
18
|
+
* `aria-modal="true"` when `enableModal` is set) are set on the inner pane window element.
|
|
17
19
|
*
|
|
18
20
|
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog
|
|
19
21
|
*/
|
|
20
22
|
export const SidePane = compatibleForwardRef(({ open, title, renderHeading, onClose, className, children, actionSlot, formFooterProps, position = 'right', enableModal = false, enableAutoUnmount = true, disableBackdropClose = false, closeButtonProps, targetDOMNode, insideProps, ...props }, ref) => {
|
|
21
23
|
const classes = sidePaneSlotRecipe({ position, nonModal: !enableModal });
|
|
24
|
+
const [insideElement, setInsideElement] = useState(null);
|
|
22
25
|
const { localOpen, handleCloseSidePane, handleStopCloseSidePane, sidePaneElement, setSidePaneElement } = useSidePaneStateController({
|
|
23
26
|
open,
|
|
24
27
|
onClose,
|
|
25
28
|
ref,
|
|
29
|
+
insideElement,
|
|
26
30
|
});
|
|
27
31
|
const handleBackdropClick = useCallback(() => {
|
|
28
32
|
if (!disableBackdropClose) {
|
|
@@ -56,8 +60,8 @@ export const SidePane = compatibleForwardRef(({ open, title, renderHeading, onCl
|
|
|
56
60
|
const shouldRenderContent = enableAutoUnmount ? localOpen : true;
|
|
57
61
|
const resolvedFormFooterPosition = formFooterProps?.position ?? 'sticky';
|
|
58
62
|
const headingNode = title ? (_jsx(Heading, { variant: "pageHeading2", tag: "h2", children: title })) : null;
|
|
59
|
-
return (_jsx(TargetDomNodeProvider, { targetDomNode: sidePaneElement, children: shouldRenderContent ? (_jsx(Portal, { targetDOMNode: targetElement, children: _jsxs("div", { ref: setSidePaneElement,
|
|
63
|
+
return (_jsx(TargetDomNodeProvider, { targetDomNode: sidePaneElement, children: shouldRenderContent ? (_jsx(Portal, { targetDOMNode: targetElement, children: _jsxs("div", { ref: setSidePaneElement, ...props, className: cx(classes.root, 'mfui-SidePane__root', className), style: {
|
|
60
64
|
...props.style,
|
|
61
65
|
...(!enableAutoUnmount && !localOpen && { display: 'none' }),
|
|
62
|
-
}, onKeyDown: handleOnKeyDown, children: [enableModal ? (_jsx("div", { "data-mfui-content": "backdrop", className: cx(classes.backdrop, 'mfui-SidePane__backdrop'), onClick: handleBackdropClick })) : null, _jsxs("div", { "data-mfui-content": "inside", className: cx(classes.inside, 'mfui-SidePane__inside', insideProps?.className), onClick: handleStopCloseSidePane, children: [_jsxs("header", { className: cx(classes.header, 'mfui-SidePane__header'), children: [renderHeading ? (renderHeading({ headingNode })) : (_jsx("div", { className: cx(classes.title, 'mfui-SidePane__title'), children: headingNode })), _jsxs("div", { className: cx(classes.actionSlotWrapper, 'mfui-SidePane__actionSlotWrapper'), children: [!!actionSlot && (_jsx("div", { className: cx(classes.actionSlot, 'mfui-SidePane__actionSlot'), children: actionSlot })), _jsx("div", { className: cx(classes.closeButtonWrapper, 'mfui-SidePane__closeButtonWrapper'), children: _jsx(IconButton, { "aria-label": closeButtonProps?.['aria-label'] ?? '閉じる', onClick: handleCloseSidePane, children: closeButtonIcon }) })] })] }), _jsxs("main", { className: cx(classes.content, 'mfui-SidePane__content'), children: [_jsx("div", { className: cx(classes.contentBody, 'mfui-SidePane__contentBody'), children: children }), resolvedFormFooterPosition === 'stacking' ? (_jsx("div", { className: cx(classes.footer, 'mfui-SidePane__footer'), children: _jsx(FormFooter, { sectionPosition: "fill", ...formFooterProps, position: resolvedFormFooterPosition }) })) : null] }), formFooterProps && resolvedFormFooterPosition !== 'stacking' ? (_jsx("div", { className: cx(classes.footer, 'mfui-SidePane__footer'), children: _jsx(FormFooter, { sectionPosition: "fill", ...formFooterProps, position: resolvedFormFooterPosition }) })) : null] })] }) })) : null }));
|
|
66
|
+
}, onKeyDown: handleOnKeyDown, children: [enableModal ? (_jsx("div", { "data-mfui-content": "backdrop", className: cx(classes.backdrop, 'mfui-SidePane__backdrop'), onClick: handleBackdropClick })) : null, _jsxs("div", { ref: setInsideElement, role: "dialog", "aria-modal": enableModal ? 'true' : undefined, tabIndex: -1, "data-mfui-content": "inside", className: cx(classes.inside, 'mfui-SidePane__inside', insideProps?.className), onClick: handleStopCloseSidePane, children: [_jsxs("header", { className: cx(classes.header, 'mfui-SidePane__header'), children: [renderHeading ? (renderHeading({ headingNode })) : (_jsx("div", { className: cx(classes.title, 'mfui-SidePane__title'), children: headingNode })), _jsxs("div", { className: cx(classes.actionSlotWrapper, 'mfui-SidePane__actionSlotWrapper'), children: [!!actionSlot && (_jsx("div", { className: cx(classes.actionSlot, 'mfui-SidePane__actionSlot'), children: actionSlot })), _jsx("div", { className: cx(classes.closeButtonWrapper, 'mfui-SidePane__closeButtonWrapper'), children: _jsx(IconButton, { "aria-label": closeButtonProps?.['aria-label'] ?? '閉じる', onClick: handleCloseSidePane, children: closeButtonIcon }) })] })] }), _jsxs("main", { className: cx(classes.content, 'mfui-SidePane__content'), children: [_jsx("div", { className: cx(classes.contentBody, 'mfui-SidePane__contentBody'), children: children }), resolvedFormFooterPosition === 'stacking' ? (_jsx("div", { className: cx(classes.footer, 'mfui-SidePane__footer'), children: _jsx(FormFooter, { sectionPosition: "fill", ...formFooterProps, position: resolvedFormFooterPosition }) })) : null] }), formFooterProps && resolvedFormFooterPosition !== 'stacking' ? (_jsx("div", { className: cx(classes.footer, 'mfui-SidePane__footer'), children: _jsx(FormFooter, { sectionPosition: "fill", ...formFooterProps, position: resolvedFormFooterPosition }) })) : null] })] }) })) : null }));
|
|
63
67
|
});
|
|
@@ -3,6 +3,7 @@ type UseSidePaneStateController = {
|
|
|
3
3
|
open?: boolean;
|
|
4
4
|
onClose?: () => void;
|
|
5
5
|
ref?: ForwardedRef<HTMLDivElement | null>;
|
|
6
|
+
insideElement?: HTMLElement | null;
|
|
6
7
|
};
|
|
7
8
|
/**
|
|
8
9
|
* Custom hook to control the open/close state of a SidePane.
|
|
@@ -15,9 +16,13 @@ type UseSidePaneStateController = {
|
|
|
15
16
|
*
|
|
16
17
|
* @param options.onClose - Callback function to execute when closing the SidePane.
|
|
17
18
|
*
|
|
18
|
-
* @
|
|
19
|
+
* @param options.insideElement - The pane window element with `role="dialog"`. It receives
|
|
20
|
+
* default focus when the SidePane opens, keeping focus inside the `aria-modal` boundary.
|
|
21
|
+
*
|
|
22
|
+
* @returns Object containing the local open state, close/click handlers, and a callback
|
|
23
|
+
* ref (`setSidePaneElement`) to attach to the root overlay element.
|
|
19
24
|
*/
|
|
20
|
-
export declare function useSidePaneStateController({ open, onClose, ref }: UseSidePaneStateController): {
|
|
25
|
+
export declare function useSidePaneStateController({ open, onClose, ref, insideElement }: UseSidePaneStateController): {
|
|
21
26
|
localOpen: boolean | undefined;
|
|
22
27
|
handleCloseSidePane: () => void;
|
|
23
28
|
handleStopCloseSidePane: (event: React.MouseEvent<HTMLDivElement>) => void;
|
|
@@ -12,9 +12,13 @@ import { useSidePaneOpenStateWithAnimation } from './useSidePaneOpenStateWithAni
|
|
|
12
12
|
*
|
|
13
13
|
* @param options.onClose - Callback function to execute when closing the SidePane.
|
|
14
14
|
*
|
|
15
|
-
* @
|
|
15
|
+
* @param options.insideElement - The pane window element with `role="dialog"`. It receives
|
|
16
|
+
* default focus when the SidePane opens, keeping focus inside the `aria-modal` boundary.
|
|
17
|
+
*
|
|
18
|
+
* @returns Object containing the local open state, close/click handlers, and a callback
|
|
19
|
+
* ref (`setSidePaneElement`) to attach to the root overlay element.
|
|
16
20
|
*/
|
|
17
|
-
export function useSidePaneStateController({ open, onClose, ref }) {
|
|
21
|
+
export function useSidePaneStateController({ open, onClose, ref, insideElement }) {
|
|
18
22
|
const [sidePaneElement, setSidePaneElement] = useState(null);
|
|
19
23
|
const { localOpen, openSidePane, closeSidePane } = useSidePaneOpenStateWithAnimation({ open, sidePaneElement });
|
|
20
24
|
const setSidePaneElementRef = useCallback((element) => {
|
|
@@ -49,12 +53,14 @@ export function useSidePaneStateController({ open, onClose, ref }) {
|
|
|
49
53
|
const handleStopCloseSidePane = useCallback((event) => {
|
|
50
54
|
event.stopPropagation();
|
|
51
55
|
}, []);
|
|
52
|
-
// Autofocus to the
|
|
56
|
+
// Autofocus to the pane window (`role="dialog"` element) when it opens.
|
|
57
|
+
// Screen readers honor `aria-modal` only when focus is on the `role="dialog"`
|
|
58
|
+
// element or its descendants, so the root wrapper must not be the focus target.
|
|
53
59
|
useEffect(() => {
|
|
54
60
|
if (!localOpen)
|
|
55
61
|
return;
|
|
56
|
-
sidePaneElement?.focus();
|
|
57
|
-
}, [sidePaneElement, localOpen]);
|
|
62
|
+
(insideElement ?? sidePaneElement)?.focus();
|
|
63
|
+
}, [insideElement, sidePaneElement, localOpen]);
|
|
58
64
|
return {
|
|
59
65
|
localOpen,
|
|
60
66
|
handleCloseSidePane,
|
package/dist/src/Tag/Tag.d.ts
CHANGED
|
@@ -7,4 +7,4 @@ import { type TagProps } from './Tag.types';
|
|
|
7
7
|
* - If `disabled` is `true` and `onClick` is provided, the tag will be displayed as disabled button.
|
|
8
8
|
* - If `showCloseButton` is `true`, the tag will be displayed with a close button.
|
|
9
9
|
*/
|
|
10
|
-
export declare function Tag({ label, onClick, href, customLinkComponent, disabled, onClose, target, rel, className, }: TagProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export declare function Tag({ label, onClick, href, customLinkComponent, disabled, onClose, closeButtonProps, target, rel, className, }: TagProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/src/Tag/Tag.js
CHANGED
|
@@ -14,10 +14,10 @@ import { cx } from '../../styled-system/css';
|
|
|
14
14
|
* - If `disabled` is `true` and `onClick` is provided, the tag will be displayed as disabled button.
|
|
15
15
|
* - If `showCloseButton` is `true`, the tag will be displayed with a close button.
|
|
16
16
|
*/
|
|
17
|
-
export function Tag({ label, onClick, href, customLinkComponent, disabled = false, onClose, target, rel, className, }) {
|
|
17
|
+
export function Tag({ label, onClick, href, customLinkComponent, disabled = false, onClose, closeButtonProps, target, rel, className, }) {
|
|
18
18
|
const isClickable = !!onClick || !!href;
|
|
19
19
|
const classes = tagSlotRecipe({ interactable: isClickable, showCloseButton: !!onClose });
|
|
20
|
-
const closeButton = (_jsx(IconButton, { disabled: disabled, className: cx(classes.closeButton, 'mfui-Tag__closeButton'), "aria-label": `${label}を削除する`, onClick: onClose, children: _jsx(Close, {}) }));
|
|
20
|
+
const closeButton = (_jsx(IconButton, { disabled: disabled, className: cx(classes.closeButton, 'mfui-Tag__closeButton'), "aria-label": closeButtonProps?.['aria-label'] ?? `${label}を削除する`, onClick: onClose, children: _jsx(Close, {}) }));
|
|
21
21
|
if (!isClickable) {
|
|
22
22
|
return (_jsx(FocusIndicator, { children: _jsxs("span", { className: cx(classes.root, 'mfui-Tag__root', className), children: [_jsx(Typography, { variant: "condensedInput", children: label }), onClose ? closeButton : null] }) }));
|
|
23
23
|
}
|
|
@@ -33,6 +33,13 @@ export type TagProps = {
|
|
|
33
33
|
* The handler to close the tag. When this prop is provided, the close button will be shown
|
|
34
34
|
*/
|
|
35
35
|
onClose?: (event: MouseEvent<HTMLButtonElement>) => void;
|
|
36
|
+
/**
|
|
37
|
+
* The properties for the close button.
|
|
38
|
+
* If this prop is not passed, the default values for each property will be applied.
|
|
39
|
+
*
|
|
40
|
+
* @property aria-label - The alternative text for the close button. default is '{label}を削除する'.
|
|
41
|
+
*/
|
|
42
|
+
closeButtonProps?: Pick<ComponentPropsWithoutRef<'button'>, 'aria-label'>;
|
|
36
43
|
/**
|
|
37
44
|
* Class name to be applied to the wrapper element.
|
|
38
45
|
*/
|
|
@@ -52,6 +52,7 @@ const dataTableCellSlotRecipeVariantKeys = [
|
|
|
52
52
|
"isCheckboxCell",
|
|
53
53
|
"rowHighlighted",
|
|
54
54
|
"fixedColumn",
|
|
55
|
+
"verticalBorder",
|
|
55
56
|
"edgeFixedColumn",
|
|
56
57
|
"layout",
|
|
57
58
|
"type"
|
|
@@ -77,6 +78,9 @@ export const dataTableCellSlotRecipe = /* @__PURE__ */ Object.assign(dataTableCe
|
|
|
77
78
|
"fixedColumn": [
|
|
78
79
|
"true"
|
|
79
80
|
],
|
|
81
|
+
"verticalBorder": [
|
|
82
|
+
"true"
|
|
83
|
+
],
|
|
80
84
|
"edgeFixedColumn": [
|
|
81
85
|
"left",
|
|
82
86
|
"right"
|
|
@@ -5,6 +5,7 @@ import type { DistributiveOmit, Pretty } from '../types/system-types';
|
|
|
5
5
|
interface DataTableHeaderCellSlotRecipeVariant {
|
|
6
6
|
size: "small" | "medium"
|
|
7
7
|
fixedColumn: boolean
|
|
8
|
+
verticalBorder: boolean
|
|
8
9
|
edgeFixedColumn: "left" | "right"
|
|
9
10
|
isCheckboxCell: boolean
|
|
10
11
|
type: "text" | "amount"
|
|
@@ -35,6 +35,7 @@ const dataTableHeaderCellSlotRecipeFn = memo((props = {}) => {
|
|
|
35
35
|
const dataTableHeaderCellSlotRecipeVariantKeys = [
|
|
36
36
|
"size",
|
|
37
37
|
"fixedColumn",
|
|
38
|
+
"verticalBorder",
|
|
38
39
|
"edgeFixedColumn",
|
|
39
40
|
"isCheckboxCell",
|
|
40
41
|
"type"
|
|
@@ -54,6 +55,9 @@ export const dataTableHeaderCellSlotRecipe = /* @__PURE__ */ Object.assign(dataT
|
|
|
54
55
|
"fixedColumn": [
|
|
55
56
|
"true"
|
|
56
57
|
],
|
|
58
|
+
"verticalBorder": [
|
|
59
|
+
"true"
|
|
60
|
+
],
|
|
57
61
|
"edgeFixedColumn": [
|
|
58
62
|
"left",
|
|
59
63
|
"right"
|
|
@@ -10,7 +10,7 @@ type DropdownMenuItemSlotRecipeVariantMap = {
|
|
|
10
10
|
[key in keyof DropdownMenuItemSlotRecipeVariant]: Array<DropdownMenuItemSlotRecipeVariant[key]>
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
type DropdownMenuItemSlotRecipeSlot = "root"
|
|
13
|
+
type DropdownMenuItemSlotRecipeSlot = "root" | "content" | "disabledMessage" | "disabledMessageIcon"
|
|
14
14
|
|
|
15
15
|
export type DropdownMenuItemSlotRecipeVariantProps = {
|
|
16
16
|
[key in keyof DropdownMenuItemSlotRecipeVariant]?: ConditionalValue<DropdownMenuItemSlotRecipeVariant[key]> | undefined
|
|
@@ -6,6 +6,18 @@ const dropdownMenuItemSlotRecipeSlotNames = [
|
|
|
6
6
|
[
|
|
7
7
|
"root",
|
|
8
8
|
"DropdownMenuItem__root"
|
|
9
|
+
],
|
|
10
|
+
[
|
|
11
|
+
"content",
|
|
12
|
+
"DropdownMenuItem__content"
|
|
13
|
+
],
|
|
14
|
+
[
|
|
15
|
+
"disabledMessage",
|
|
16
|
+
"DropdownMenuItem__disabledMessage"
|
|
17
|
+
],
|
|
18
|
+
[
|
|
19
|
+
"disabledMessageIcon",
|
|
20
|
+
"DropdownMenuItem__disabledMessageIcon"
|
|
9
21
|
]
|
|
10
22
|
];
|
|
11
23
|
const dropdownMenuItemSlotRecipeSlotFns = /* @__PURE__ */ dropdownMenuItemSlotRecipeSlotNames.map(([slotName, slotKey]) => [slotName, createRecipe(slotKey, dropdownMenuItemSlotRecipeDefaultVariants, getSlotCompoundVariant(dropdownMenuItemSlotRecipeCompoundVariants, slotName))]);
|
package/dist/styles.css
CHANGED
|
@@ -229,9 +229,13 @@
|
|
|
229
229
|
--mfui-colors-mfui\.color\.selected\.border\.none: #2e72d8ff;
|
|
230
230
|
--mfui-colors-mfui\.color\.selected\.border\.hovered: #0d57b1ff;
|
|
231
231
|
--mfui-colors-mfui\.color\.selected\.border\.pressed: #003e90ff;
|
|
232
|
-
|
|
232
|
+
--mfui-colors-mfui\.color\.selected\.text-on-background\.none: #165cb7ff;
|
|
233
|
+
--mfui-colors-mfui\.color\.selected\.text-on-background\.hovered: #004396ff;
|
|
234
|
+
--mfui-colors-mfui\.color\.selected\.text-on-background\.pressed: #072977ff;
|
|
235
|
+
--mfui-colors-mfui\.color\.signal-green\.content\.none: #008644ff;
|
|
233
236
|
--mfui-colors-mfui\.color\.signal-green\.sub-background\.none: #b3eac0ff;
|
|
234
|
-
|
|
237
|
+
--mfui-colors-mfui\.color\.signal-green\.text-on-sub-background\.none: #006d36ff;
|
|
238
|
+
--mfui-colors-mfui\.color\.signal-neutral\.sub-background\.none: #f3f3f3ff;
|
|
235
239
|
--mfui-colors-mfui\.color\.signal-red\.content\.none: #d7383cff;
|
|
236
240
|
--mfui-colors-mfui\.color\.signal-red\.content\.hovered: #b40d24ff;
|
|
237
241
|
--mfui-colors-mfui\.color\.signal-red\.content\.pressed: #8a0017ff;
|
|
@@ -242,9 +246,11 @@
|
|
|
242
246
|
--mfui-colors-mfui\.color\.signal-red\.background\.hovered: #b40d24ff;
|
|
243
247
|
--mfui-colors-mfui\.color\.signal-red\.background\.pressed: #8a0017ff;
|
|
244
248
|
--mfui-colors-mfui\.color\.signal-red\.sub-background\.none: #ffd3ccff;
|
|
245
|
-
|
|
249
|
+
--mfui-colors-mfui\.color\.signal-red\.text-on-sub-background\.none: #ba1828ff;
|
|
250
|
+
--mfui-colors-mfui\.color\.signal-yellow\.content\.none: #8b7200ff;
|
|
246
251
|
--mfui-colors-mfui\.color\.signal-yellow\.sub-background\.none: #f7da90ff;
|
|
247
|
-
|
|
252
|
+
--mfui-colors-mfui\.color\.signal-yellow\.text-on-sub-background\.none: #775a01ff;
|
|
253
|
+
--mfui-shadows-mfui\.elevation\.plus-1\.shadow-1: 0px 1px 1px 1px #00000033;
|
|
248
254
|
--mfui-shadows-mfui\.elevation\.plus-1\.shadow-2: 0px 2px 2px 1px #0000001a;
|
|
249
255
|
--mfui-shadows-mfui\.elevation\.plus-2\.shadow-1: 0px 4px 8px 0px #00000040;
|
|
250
256
|
--mfui-shadows-mfui\.elevation\.plus-2\.shadow-2: 0px 6px 16px 0px #00000033;
|
|
@@ -818,6 +824,28 @@
|
|
|
818
824
|
background-color: var(--mfui-colors-mfui\.color\.base\.background\.pressed);
|
|
819
825
|
}
|
|
820
826
|
|
|
827
|
+
.mfui-eMicFf {
|
|
828
|
+
display: flex;
|
|
829
|
+
flex-direction: column;
|
|
830
|
+
min-width: 0;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
.mfui-hsjcaJ {
|
|
834
|
+
gap: var(--mfui-spacing-mfui\.size\.spacing\.icon-and-text\.horizontal\.comfort);
|
|
835
|
+
display: flex;
|
|
836
|
+
align-items: flex-start;
|
|
837
|
+
color: var(--mfui-colors-mfui\.color\.neutral\.content\.none);
|
|
838
|
+
overflow-wrap: break-word;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
.mfui-imxNHe {
|
|
842
|
+
flex: 0 0 auto;
|
|
843
|
+
display: block;
|
|
844
|
+
width: var(--mfui-sizes-mfui\.size\.dimension\.icon\.square\.condensed);
|
|
845
|
+
height: var(--mfui-sizes-mfui\.size\.dimension\.icon\.square\.condensed);
|
|
846
|
+
margin-top: 2px;
|
|
847
|
+
}
|
|
848
|
+
|
|
821
849
|
.mfui-dHwbKW {
|
|
822
850
|
gap: var(--mfui-spacing-mfui\.size\.spacing\.icon-and-text\.horizontal\.comfort);
|
|
823
851
|
display: inline-flex;
|
|
@@ -2331,6 +2359,8 @@
|
|
|
2331
2359
|
.mfui-hveqnu {
|
|
2332
2360
|
border: none;
|
|
2333
2361
|
inset-inline: auto 0;
|
|
2362
|
+
outline: 2px solid transparent;
|
|
2363
|
+
outline-offset: 2px;
|
|
2334
2364
|
position: absolute;
|
|
2335
2365
|
display: flex;
|
|
2336
2366
|
flex-direction: column;
|
|
@@ -2543,7 +2573,7 @@
|
|
|
2543
2573
|
}
|
|
2544
2574
|
|
|
2545
2575
|
.mfui-fhtZXM {
|
|
2546
|
-
max-height:
|
|
2576
|
+
max-height: 288px;
|
|
2547
2577
|
width: fit-content;
|
|
2548
2578
|
}
|
|
2549
2579
|
|
|
@@ -3067,7 +3097,7 @@
|
|
|
3067
3097
|
border-color: var(--mfui-colors-mfui\.color\.selected\.border\.none);
|
|
3068
3098
|
padding-block: calc(var(--mfui-spacing-mfui\.size\.padding\.sub-navigation\.vertical\.comfort) - 1px);
|
|
3069
3099
|
padding-inline: calc(var(--mfui-spacing-mfui\.size\.padding\.sub-navigation\.horizontal\.comfort) - 1px);
|
|
3070
|
-
color: var(--mfui-colors-mfui\.color\.selected\.
|
|
3100
|
+
color: var(--mfui-colors-mfui\.color\.selected\.text-on-background\.none);
|
|
3071
3101
|
background-color: var(--mfui-colors-mfui\.color\.selected\.background\.none);
|
|
3072
3102
|
}
|
|
3073
3103
|
|
|
@@ -3081,7 +3111,7 @@
|
|
|
3081
3111
|
|
|
3082
3112
|
.mfui-ljuOJV:is(:focus, [data-focus])[aria-current=page] {
|
|
3083
3113
|
border-color: var(--mfui-colors-mfui\.color\.selected\.border\.hovered);
|
|
3084
|
-
color: var(--mfui-colors-mfui\.color\.selected\.
|
|
3114
|
+
color: var(--mfui-colors-mfui\.color\.selected\.text-on-background\.hovered);
|
|
3085
3115
|
background-color: var(--mfui-colors-mfui\.color\.selected\.background\.hovered);
|
|
3086
3116
|
}
|
|
3087
3117
|
|
|
@@ -3091,7 +3121,7 @@
|
|
|
3091
3121
|
|
|
3092
3122
|
.mfui-ljuOJV:is(:hover, [data-hover])[aria-current=page] {
|
|
3093
3123
|
border-color: var(--mfui-colors-mfui\.color\.selected\.border\.hovered);
|
|
3094
|
-
color: var(--mfui-colors-mfui\.color\.selected\.
|
|
3124
|
+
color: var(--mfui-colors-mfui\.color\.selected\.text-on-background\.hovered);
|
|
3095
3125
|
background-color: var(--mfui-colors-mfui\.color\.selected\.background\.hovered);
|
|
3096
3126
|
}
|
|
3097
3127
|
|
|
@@ -3101,7 +3131,7 @@
|
|
|
3101
3131
|
|
|
3102
3132
|
.mfui-ljuOJV:is(:active, [data-active])[aria-current=page] {
|
|
3103
3133
|
border-color: var(--mfui-colors-mfui\.color\.selected\.border\.pressed);
|
|
3104
|
-
color: var(--mfui-colors-mfui\.color\.selected\.
|
|
3134
|
+
color: var(--mfui-colors-mfui\.color\.selected\.text-on-background\.pressed);
|
|
3105
3135
|
background-color: var(--mfui-colors-mfui\.color\.selected\.background\.pressed);
|
|
3106
3136
|
}
|
|
3107
3137
|
|
|
@@ -7358,6 +7388,23 @@ li:last-child > .mfui-cXGJls {
|
|
|
7358
7388
|
background-color: inherit;
|
|
7359
7389
|
}
|
|
7360
7390
|
|
|
7391
|
+
.mfui-hGAWTy {
|
|
7392
|
+
border-left-style: solid;
|
|
7393
|
+
border-left-width: var(--mfui-border-widths-mfui\.size\.border\.base\.width\.comfort);
|
|
7394
|
+
border-left-color: var(--mfui-colors-mfui\.color\.neutral\.thin-border\.none);
|
|
7395
|
+
border-right-style: solid;
|
|
7396
|
+
border-right-width: var(--mfui-border-widths-mfui\.size\.border\.base\.width\.comfort);
|
|
7397
|
+
border-right-color: var(--mfui-colors-mfui\.color\.neutral\.thin-border\.none);
|
|
7398
|
+
}
|
|
7399
|
+
|
|
7400
|
+
.mfui-hGAWTy:first-child {
|
|
7401
|
+
border-left-width: 0;
|
|
7402
|
+
}
|
|
7403
|
+
|
|
7404
|
+
.mfui-hGAWTy:last-child {
|
|
7405
|
+
border-right-width: 0;
|
|
7406
|
+
}
|
|
7407
|
+
|
|
7361
7408
|
.mfui-izXzGC::before {
|
|
7362
7409
|
border-right-width: var(--mfui-border-widths-mfui\.size\.border\.fixed-cell\.horizontal\.comfort);
|
|
7363
7410
|
border-right-style: solid;
|
|
@@ -7433,6 +7480,23 @@ li:last-child > .mfui-cXGJls {
|
|
|
7433
7480
|
position: sticky;
|
|
7434
7481
|
}
|
|
7435
7482
|
|
|
7483
|
+
.mfui-haQzOt {
|
|
7484
|
+
border-left-style: solid;
|
|
7485
|
+
border-left-width: var(--mfui-border-widths-mfui\.size\.border\.base\.width\.comfort);
|
|
7486
|
+
border-left-color: var(--mfui-colors-mfui\.color\.neutral\.sub-border\.none);
|
|
7487
|
+
border-right-style: solid;
|
|
7488
|
+
border-right-width: var(--mfui-border-widths-mfui\.size\.border\.base\.width\.comfort);
|
|
7489
|
+
border-right-color: var(--mfui-colors-mfui\.color\.neutral\.sub-border\.none);
|
|
7490
|
+
}
|
|
7491
|
+
|
|
7492
|
+
.mfui-haQzOt:first-child {
|
|
7493
|
+
border-left-width: 0;
|
|
7494
|
+
}
|
|
7495
|
+
|
|
7496
|
+
.mfui-haQzOt:last-child {
|
|
7497
|
+
border-right-width: 0;
|
|
7498
|
+
}
|
|
7499
|
+
|
|
7436
7500
|
.mfui-fGBQCd::before {
|
|
7437
7501
|
border-right-width: var(--mfui-border-widths-mfui\.size\.border\.fixed-cell\.horizontal\.comfort);
|
|
7438
7502
|
border-right-style: solid;
|
|
@@ -7988,17 +8052,17 @@ li:last-child > .mfui-cXGJls {
|
|
|
7988
8052
|
|
|
7989
8053
|
.mfui-cNbYAX {
|
|
7990
8054
|
background-color: var(--mfui-colors-mfui\.color\.signal-green\.sub-background\.none);
|
|
7991
|
-
color: var(--mfui-colors-mfui\.color\.signal-green\.
|
|
8055
|
+
color: var(--mfui-colors-mfui\.color\.signal-green\.text-on-sub-background\.none);
|
|
7992
8056
|
}
|
|
7993
8057
|
|
|
7994
8058
|
.mfui-bAfwQA {
|
|
7995
8059
|
background-color: var(--mfui-colors-mfui\.color\.signal-red\.sub-background\.none);
|
|
7996
|
-
color: var(--mfui-colors-mfui\.color\.signal-red\.
|
|
8060
|
+
color: var(--mfui-colors-mfui\.color\.signal-red\.text-on-sub-background\.none);
|
|
7997
8061
|
}
|
|
7998
8062
|
|
|
7999
8063
|
.mfui-dZGLHT {
|
|
8000
8064
|
background-color: var(--mfui-colors-mfui\.color\.signal-yellow\.sub-background\.none);
|
|
8001
|
-
color: var(--mfui-colors-mfui\.color\.signal-yellow\.
|
|
8065
|
+
color: var(--mfui-colors-mfui\.color\.signal-yellow\.text-on-sub-background\.none);
|
|
8002
8066
|
}
|
|
8003
8067
|
|
|
8004
8068
|
.mfui-bqPyQm {
|