@moneyforward/mfui-components 3.19.0 → 3.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/src/CheckboxCard/CheckboxCard.js +1 -7
  2. package/dist/src/DateTimeSelection/DatePicker/DatePicker.js +2 -11
  3. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePicker.d.ts +1 -1
  4. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePicker.js +10 -2
  5. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePicker.types.d.ts +14 -0
  6. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerPopover/DateRangePickerPopover.js +13 -5
  7. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerPopover/utilities/createDateRangePickerPopoverTestUtility.js +2 -2
  8. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerProvider/DateRangePickerProvider.js +11 -12
  9. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerTrigger/hooks/useDateRangeTriggerValueController.js +4 -3
  10. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerTrigger/utilities/createDateRangePickerTriggerTestUtility.js +3 -1
  11. package/dist/src/DateTimeSelection/FilterDateRangePicker/DateRangePickerContent/DateRangePickerContent.d.ts +6 -0
  12. package/dist/src/DateTimeSelection/FilterDateRangePicker/DateRangePickerContent/DateRangePickerContent.js +33 -0
  13. package/dist/src/DateTimeSelection/FilterDateRangePicker/DateRangePickerContent/DateRangePickerContent.types.d.ts +17 -0
  14. package/dist/src/DateTimeSelection/FilterDateRangePicker/FilterDateRangePicker.d.ts +14 -0
  15. package/dist/src/DateTimeSelection/FilterDateRangePicker/FilterDateRangePicker.js +42 -0
  16. package/dist/src/DateTimeSelection/FilterDateRangePicker/FilterDateRangePicker.types.d.ts +78 -0
  17. package/dist/src/DateTimeSelection/FilterDateRangePicker/FilterDateRangePicker.types.js +1 -0
  18. package/dist/src/DateTimeSelection/FilterDateRangePicker/index.d.ts +2 -0
  19. package/dist/src/DateTimeSelection/FilterDateRangePicker/index.js +1 -0
  20. package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.d.ts +14 -0
  21. package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.js +75 -0
  22. package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.types.d.ts +83 -0
  23. package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.types.js +1 -0
  24. package/dist/src/DateTimeSelection/FilterMonthPicker/MonthPickerPopover/MonthPickerPopover.d.ts +6 -0
  25. package/dist/src/DateTimeSelection/FilterMonthPicker/MonthPickerPopover/MonthPickerPopover.js +45 -0
  26. package/dist/src/DateTimeSelection/FilterMonthPicker/MonthPickerPopover/MonthPickerPopover.types.d.ts +24 -0
  27. package/dist/src/DateTimeSelection/FilterMonthPicker/MonthPickerPopover/MonthPickerPopover.types.js +1 -0
  28. package/dist/src/DateTimeSelection/FilterMonthPicker/index.d.ts +2 -0
  29. package/dist/src/DateTimeSelection/FilterMonthPicker/index.js +1 -0
  30. package/dist/src/DateTimeSelection/FilterMonthRangePicker/FilterMonthRangePicker.d.ts +15 -0
  31. package/dist/src/DateTimeSelection/FilterMonthRangePicker/FilterMonthRangePicker.js +89 -0
  32. package/dist/src/DateTimeSelection/FilterMonthRangePicker/FilterMonthRangePicker.types.d.ts +75 -0
  33. package/dist/src/DateTimeSelection/FilterMonthRangePicker/FilterMonthRangePicker.types.js +1 -0
  34. package/dist/src/DateTimeSelection/FilterMonthRangePicker/MonthRangePickerPopover/MonthRangePickerPopover.d.ts +5 -0
  35. package/dist/src/DateTimeSelection/FilterMonthRangePicker/MonthRangePickerPopover/MonthRangePickerPopover.js +54 -0
  36. package/dist/src/DateTimeSelection/FilterMonthRangePicker/MonthRangePickerPopover/MonthRangePickerPopover.types.d.ts +19 -0
  37. package/dist/src/DateTimeSelection/FilterMonthRangePicker/MonthRangePickerPopover/MonthRangePickerPopover.types.js +1 -0
  38. package/dist/src/DateTimeSelection/FilterMonthRangePicker/index.d.ts +2 -0
  39. package/dist/src/DateTimeSelection/FilterMonthRangePicker/index.js +1 -0
  40. package/dist/src/DateTimeSelection/MonthPicker/MonthPicker.d.ts +1 -1
  41. package/dist/src/DateTimeSelection/MonthPicker/MonthPicker.js +10 -2
  42. package/dist/src/DateTimeSelection/MonthPicker/MonthPicker.types.d.ts +14 -0
  43. package/dist/src/DateTimeSelection/MonthPicker/MonthPickerPanel/MonthCell/MonthCell.js +2 -25
  44. package/dist/src/DateTimeSelection/MonthPicker/MonthPickerPanel/MonthPickerPanel.js +1 -1
  45. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePicker.d.ts +1 -1
  46. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePicker.js +14 -5
  47. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePicker.types.d.ts +21 -0
  48. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerMonthCell/MonthRangePickerMonthCell.d.ts +1 -0
  49. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerMonthCell/MonthRangePickerMonthCell.js +10 -3
  50. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerNavigation/MonthRangePickerNavigation.js +16 -3
  51. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePickerPanel/MonthRangePickerPanel.js +3 -1
  52. package/dist/src/DateTimeSelection/TimePicker/TimePicker.d.ts +43 -0
  53. package/dist/src/DateTimeSelection/TimePicker/TimePicker.js +85 -0
  54. package/dist/src/DateTimeSelection/TimePicker/TimePicker.types.d.ts +61 -0
  55. package/dist/src/DateTimeSelection/TimePicker/TimePicker.types.js +1 -0
  56. package/dist/src/DateTimeSelection/TimePicker/constants.d.ts +4 -0
  57. package/dist/src/DateTimeSelection/TimePicker/constants.js +12 -0
  58. package/dist/src/DateTimeSelection/TimePicker/index.d.ts +2 -0
  59. package/dist/src/DateTimeSelection/TimePicker/index.js +1 -0
  60. package/dist/src/DateTimeSelection/index.d.ts +4 -0
  61. package/dist/src/DateTimeSelection/index.js +4 -0
  62. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.d.ts +1 -1
  63. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.js +26 -10
  64. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.types.d.ts +19 -0
  65. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerProvider/BaseRangePickerProvider.js +40 -19
  66. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerProvider/BaseRangePickerProvider.types.d.ts +19 -0
  67. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerTrigger/BaseRangePickerTrigger.js +4 -6
  68. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerTrigger/hooks/useDateRangeTriggerValueController.js +4 -3
  69. package/dist/src/DateTimeSelection/shared/CalendarLocale/CalendarLocaleContext.d.ts +1 -1
  70. package/dist/src/DateTimeSelection/shared/CalendarLocale/CalendarLocaleContext.js +1 -1
  71. package/dist/src/DateTimeSelection/shared/DayCell/DayCell.js +2 -3
  72. package/dist/src/DateTimeSelection/shared/MonthGrid/MonthGrid.js +3 -3
  73. package/dist/src/DateTimeSelection/shared/YearSelector/YearSelector.js +1 -1
  74. package/dist/src/DateTimeSelection/shared/utilities/dateParsing.js +27 -9
  75. package/dist/src/DateTimeSelection/shared/utilities/japaneseCalendar.d.ts +36 -8
  76. package/dist/src/DateTimeSelection/shared/utilities/japaneseCalendar.js +82 -15
  77. package/dist/src/DateTimeSelection/shared/utilities/monthCellMonthFormat.d.ts +14 -0
  78. package/dist/src/DateTimeSelection/shared/utilities/monthCellMonthFormat.js +35 -0
  79. package/dist/src/MultilineTextBox/index.d.ts +10 -2
  80. package/dist/src/MultilineTextBox/index.js +9 -1
  81. package/dist/src/MultipleSelectBox/MultipleSelectBoxTrigger/MultipleSelectBoxTrigger.js +1 -3
  82. package/dist/src/RadioButtonCard/RadioButtonCard.js +1 -7
  83. package/dist/src/TextBox/TextBox.js +2 -6
  84. package/dist/src/{MultilineTextBox/MultilineTextBox.d.ts → Textarea/Textarea.d.ts} +5 -2
  85. package/dist/src/{MultilineTextBox/MultilineTextBox.js → Textarea/Textarea.js} +9 -5
  86. package/dist/src/{MultilineTextBox/MultilineTextBox.types.d.ts → Textarea/Textarea.types.d.ts} +2 -2
  87. package/dist/src/Textarea/Textarea.types.js +1 -0
  88. package/dist/src/Textarea/index.d.ts +2 -0
  89. package/dist/src/Textarea/index.js +1 -0
  90. package/dist/src/Tooltip/Tooltip.js +12 -3
  91. package/dist/src/Typography/Typography.js +1 -3
  92. package/dist/src/index.d.ts +1 -0
  93. package/dist/src/index.js +1 -0
  94. package/dist/styled-system/recipes/filter-date-range-picker-slot-recipe.d.ts +33 -0
  95. package/dist/styled-system/recipes/filter-date-range-picker-slot-recipe.js +48 -0
  96. package/dist/styled-system/recipes/filter-month-picker-slot-recipe.d.ts +33 -0
  97. package/dist/styled-system/recipes/filter-month-picker-slot-recipe.js +44 -0
  98. package/dist/styled-system/recipes/filter-month-range-picker-slot-recipe.d.ts +33 -0
  99. package/dist/styled-system/recipes/filter-month-range-picker-slot-recipe.js +44 -0
  100. package/dist/styled-system/recipes/index.d.ts +5 -1
  101. package/dist/styled-system/recipes/index.js +5 -1
  102. package/dist/styled-system/recipes/textarea-slot-recipe.d.ts +52 -0
  103. package/dist/styled-system/recipes/textarea-slot-recipe.js +64 -0
  104. package/dist/styled-system/recipes/time-picker-slot-recipe.d.ts +44 -0
  105. package/dist/styled-system/recipes/time-picker-slot-recipe.js +62 -0
  106. package/dist/styles.css +601 -25
  107. package/dist/tsconfig.build.tsbuildinfo +1 -1
  108. package/package.json +13 -11
  109. package/dist/src/DateTimeSelection/shared/BasePicker/YearSelector/YearSelector.d.ts +0 -18
  110. package/dist/src/DateTimeSelection/shared/BasePicker/YearSelector/YearSelector.js +0 -36
  111. package/dist/styled-system/recipes/multiline-text-box-slot-recipe.d.ts +0 -52
  112. package/dist/styled-system/recipes/multiline-text-box-slot-recipe.js +0 -64
  113. /package/dist/src/{MultilineTextBox/MultilineTextBox.types.js → DateTimeSelection/FilterDateRangePicker/DateRangePickerContent/DateRangePickerContent.types.js} +0 -0
@@ -0,0 +1,6 @@
1
+ import { type InternalMonthPickerPopoverProps } from './MonthPickerPopover.types';
2
+ /**
3
+ * Internal component that accesses BasePickerContext for viewing-month reset and focus trap.
4
+ * Must be rendered inside a BasePickerProvider.
5
+ */
6
+ export declare function InternalMonthPickerPopover({ currentValue, defaultValue, isPickerOpen, togglePicker, handleValueChange, handleClear, disabled, className, classes, formattedValue, hasValue, label, clearButtonProps, checkDisabledMonth, minMonth, maxMonth, calendarLocale, triggerRef, targetDOMNode, dialogId, }: InternalMonthPickerPopoverProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,45 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback } from 'react';
4
+ import { Calendar as CalendarIcon } from '@moneyforward/mfui-icons-react';
5
+ import { Popover } from '../../../Popover';
6
+ import { FocusIndicator } from '../../../FocusIndicator';
7
+ import { ClearButton } from '../../../shared';
8
+ import { Typography } from '../../../Typography';
9
+ import { cx } from '../../../../styled-system/css';
10
+ import { dayjs } from '../../../utilities/date/dayjs';
11
+ import { MonthPickerPanel } from '../../MonthPicker/MonthPickerPanel/MonthPickerPanel';
12
+ import { CalendarLocaleProvider } from '../../shared/CalendarLocale/CalendarLocaleContext';
13
+ import { useBasePickerContext } from '../../shared/BasePicker/BasePickerProvider';
14
+ import { useFocusTrap } from '../../../utilities/dom/useFocusTrap';
15
+ import { getFocusableNodes } from '../../../utilities/dom/getFocusableNodes';
16
+ const MONTH_BASE_FORMAT = 'YYYY-MM';
17
+ /**
18
+ * Internal component that accesses BasePickerContext for viewing-month reset and focus trap.
19
+ * Must be rendered inside a BasePickerProvider.
20
+ */
21
+ export function InternalMonthPickerPopover({ currentValue, defaultValue, isPickerOpen, togglePicker, handleValueChange, handleClear, disabled, className, classes, formattedValue, hasValue, label, clearButtonProps, checkDisabledMonth, minMonth, maxMonth, calendarLocale = 'ja', triggerRef, targetDOMNode, dialogId, }) {
22
+ const { viewingValue, setViewingValue, setPendingFocusDate } = useBasePickerContext();
23
+ const { handleOnKeyDown } = useFocusTrap({
24
+ onPressingTabOnNotFocusableElement: (event) => {
25
+ const nodes = getFocusableNodes(event.currentTarget);
26
+ const focusableNodeAfterFocusingNode = nodes.find((element) =>
27
+ // eslint-disable-next-line no-bitwise
28
+ element.compareDocumentPosition(event.target) & Node.DOCUMENT_POSITION_PRECEDING);
29
+ const firstFocusableNode = nodes.at(0);
30
+ const nextFocusableNode = focusableNodeAfterFocusingNode ?? firstFocusableNode;
31
+ nextFocusableNode?.focus();
32
+ event.preventDefault();
33
+ },
34
+ });
35
+ const handlePopoverOpen = useCallback(() => {
36
+ const monthToFocus = dayjs(currentValue ?? defaultValue ?? new Date())
37
+ .startOf('month')
38
+ .toDate();
39
+ setViewingValue(monthToFocus);
40
+ setPendingFocusDate(dayjs(monthToFocus).format(MONTH_BASE_FORMAT));
41
+ }, [currentValue, defaultValue, setViewingValue, setPendingFocusDate]);
42
+ return (_jsx(Popover, { open: isPickerOpen, targetDOMNode: targetDOMNode, renderTrigger: ({ setTriggerRef, togglePopover, handleTriggerKeyDown, handleTriggerBlur }) => (_jsxs("div", { ref: setTriggerRef, "data-selected": hasValue, className: cx(classes.wrapper, 'mfui-FilterMonthPicker__wrapper'), children: [_jsx(FocusIndicator, { children: _jsxs("button", { ref: triggerRef, type: "button", disabled: disabled, className: cx(classes.trigger, 'mfui-FilterMonthPicker__trigger', className), "data-selected": hasValue, "data-mfui-has-clear-button": hasValue, "aria-controls": dialogId, "aria-expanded": isPickerOpen, "aria-haspopup": "dialog", onClick: togglePopover, onKeyDown: handleTriggerKeyDown, onBlur: handleTriggerBlur, children: [_jsx("span", { className: cx(classes.content, 'mfui-FilterMonthPicker__content'), "data-mfui-content": "filter-month-picker-display-value", children: hasValue ? (_jsxs(Typography, { variant: "strongBody", children: [_jsx("span", { "data-mfui-content": "filter-month-picker-label", children: `${label}:` }), _jsx("span", { "data-mfui-content": "filter-month-picker-value", children: formattedValue })] })) : (_jsx(Typography, { variant: "controlLabel", children: _jsx("span", { "data-mfui-content": "filter-month-picker-label", children: label }) })) }), _jsx("span", { className: cx(classes.calendarIcon, 'mfui-FilterMonthPicker__calendarIcon'), children: _jsx(CalendarIcon, { "aria-hidden": true }) })] }) }), hasValue ? (_jsx("div", { className: cx(classes.clearButtonWrapper, 'mfui-FilterMonthPicker__clearButtonWrapper'), children: _jsx(ClearButton, { "aria-label": clearButtonProps?.['aria-label'] ?? '値をクリアする', "data-mfui-content": "filter-month-picker-clear-button", disabled: disabled, onClick: handleClear }) })) : null] })), renderContent: () => (_jsx("div", { id: dialogId, role: "dialog", "aria-label": label, className: "mfui-FilterMonthPicker__popoverWrapper", onKeyDown: handleOnKeyDown, children: _jsx(CalendarLocaleProvider, { value: calendarLocale, children: _jsx(MonthPickerPanel, { viewingValue: viewingValue, setViewingValue: setViewingValue, value: currentValue, minMonth: minMonth, maxMonth: maxMonth, checkDisabledMonth: checkDisabledMonth, onChange: (date) => {
43
+ handleValueChange(date);
44
+ } }) }) })), minWidth: "min-content", allowedPlacements: ['bottom-start', 'top-start'], onOpenStateChanged: togglePicker, onOpen: handlePopoverOpen }));
45
+ }
@@ -0,0 +1,24 @@
1
+ import { type filterMonthPickerSlotRecipe } from '../../../../styled-system/recipes';
2
+ import { type FilterMonthPickerProps } from '../FilterMonthPicker.types';
3
+ export type InternalMonthPickerPopoverProps = {
4
+ currentValue: Date | undefined;
5
+ defaultValue: Date | undefined;
6
+ isPickerOpen: boolean;
7
+ togglePicker: () => void;
8
+ handleValueChange: (date: Date | undefined) => void;
9
+ handleClear: (event: React.MouseEvent<HTMLButtonElement>) => void;
10
+ disabled: boolean;
11
+ className: string | undefined;
12
+ classes: ReturnType<typeof filterMonthPickerSlotRecipe>;
13
+ formattedValue: string | undefined;
14
+ hasValue: boolean;
15
+ label: string;
16
+ clearButtonProps: FilterMonthPickerProps['clearButtonProps'];
17
+ checkDisabledMonth: FilterMonthPickerProps['checkDisabledMonth'];
18
+ minMonth: Date | undefined;
19
+ maxMonth: Date | undefined;
20
+ calendarLocale: FilterMonthPickerProps['calendarLocale'];
21
+ triggerRef: React.RefObject<HTMLButtonElement | null>;
22
+ targetDOMNode: FilterMonthPickerProps['targetDOMNode'];
23
+ dialogId: string;
24
+ };
@@ -0,0 +1,2 @@
1
+ export { FilterMonthPicker } from './FilterMonthPicker';
2
+ export type { FilterMonthPickerProps } from './FilterMonthPicker.types';
@@ -0,0 +1 @@
1
+ export { FilterMonthPicker } from './FilterMonthPicker';
@@ -0,0 +1,15 @@
1
+ import { type FilterMonthRangePickerProps } from './FilterMonthRangePicker.types';
2
+ /**
3
+ * FilterMonthRangePicker component.
4
+ *
5
+ * A filter trigger component that allows selecting a month range via the same panel as
6
+ * {@link MonthRangePicker}. Displays the label alone when no range is selected, and
7
+ * `label:start〜end` after both bounds are selected. Conforms to the FilterSelectBox / FilterDateBox
8
+ * visual style.
9
+ *
10
+ * Note: The trigger markup intentionally does not reuse FilterTrigger because
11
+ * FilterTrigger hardcodes the DropdownIcon and adds a hidden `<input>` element,
12
+ * neither of which are appropriate here. FilterMonthRangePicker uses CalendarIcon and
13
+ * has no form-field semantics.
14
+ */
15
+ export declare function FilterMonthRangePicker({ label, value, defaultValue, onChange, open, onOpenStateChanged, format, initialDisplayedMonths, clearButtonProps, disabled, className, targetDOMNode, minMonth, maxMonth, }: FilterMonthRangePickerProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,89 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useId, useRef, useState } from 'react';
4
+ import { BaseRangePickerProvider } from '../shared/BaseRangePicker/BaseRangePickerProvider';
5
+ import { useDisclosure } from '../../utilities/state/useDisclosure';
6
+ import { dayjs } from '../../utilities/date/dayjs';
7
+ import { filterMonthRangePickerSlotRecipe } from '../../../styled-system/recipes';
8
+ import { SEPARATOR_CONTENT } from '../shared/BaseRangePicker/BaseRangePickerTrigger/constants';
9
+ import { InternalMonthRangePickerPopover } from './MonthRangePickerPopover/MonthRangePickerPopover';
10
+ const DEFAULT_FORMAT = 'YYYY/MM';
11
+ /**
12
+ * FilterMonthRangePicker component.
13
+ *
14
+ * A filter trigger component that allows selecting a month range via the same panel as
15
+ * {@link MonthRangePicker}. Displays the label alone when no range is selected, and
16
+ * `label:start〜end` after both bounds are selected. Conforms to the FilterSelectBox / FilterDateBox
17
+ * visual style.
18
+ *
19
+ * Note: The trigger markup intentionally does not reuse FilterTrigger because
20
+ * FilterTrigger hardcodes the DropdownIcon and adds a hidden `<input>` element,
21
+ * neither of which are appropriate here. FilterMonthRangePicker uses CalendarIcon and
22
+ * has no form-field semantics.
23
+ */
24
+ export function FilterMonthRangePicker({ label, value, defaultValue, onChange, open, onOpenStateChanged, format = DEFAULT_FORMAT, initialDisplayedMonths = 'currentAndNext', clearButtonProps, disabled = false, className, targetDOMNode, minMonth, maxMonth, }) {
25
+ const classes = filterMonthRangePickerSlotRecipe();
26
+ const triggerRef = useRef(null);
27
+ const dialogId = useId();
28
+ const [internalValue, setInternalValue] = useState(() => defaultValue ?? [undefined, undefined]);
29
+ const currentValue = value !== undefined ? value : internalValue;
30
+ const previousValueRef = useRef(value);
31
+ useEffect(() => {
32
+ const previousValue = previousValueRef.current;
33
+ previousValueRef.current = value;
34
+ if (previousValue !== undefined && value === undefined) {
35
+ setInternalValue(defaultValue ?? [undefined, undefined]);
36
+ }
37
+ }, [value, defaultValue]);
38
+ const { isOpen: isPickerOpen, toggle: togglePicker, close: closePicker, open: openPicker, } = useDisclosure({
39
+ value: disabled ? false : open,
40
+ onToggle: (nextOpen) => {
41
+ if (!nextOpen) {
42
+ triggerRef.current?.focus();
43
+ }
44
+ onOpenStateChanged?.(nextOpen);
45
+ },
46
+ onClose: () => {
47
+ triggerRef.current?.focus();
48
+ },
49
+ });
50
+ const closePickerAndNotify = useCallback(() => {
51
+ closePicker();
52
+ onOpenStateChanged?.(false);
53
+ }, [closePicker, onOpenStateChanged]);
54
+ const handleRangeChange = useCallback((dates) => {
55
+ if (value === undefined) {
56
+ setInternalValue(dates);
57
+ }
58
+ onChange?.(dates);
59
+ }, [value, onChange]);
60
+ const handleClear = useCallback((event) => {
61
+ event.stopPropagation();
62
+ if (value === undefined) {
63
+ setInternalValue([undefined, undefined]);
64
+ }
65
+ onChange?.([undefined, undefined]);
66
+ triggerRef.current?.focus();
67
+ }, [value, onChange]);
68
+ const [startMonth, endMonth] = currentValue;
69
+ const formattedValue = startMonth && endMonth
70
+ ? `${dayjs(startMonth).format(format)}${SEPARATOR_CONTENT}${dayjs(endMonth).format(format)}`
71
+ : undefined;
72
+ const hasValue = !!formattedValue;
73
+ const initialViewingDate = (() => {
74
+ if (initialDisplayedMonths !== 'previousAndCurrent') {
75
+ return;
76
+ }
77
+ const startDate = value?.[0] ?? defaultValue?.[0];
78
+ if (!startDate) {
79
+ const today = new Date();
80
+ return new Date(today.getFullYear() - 1, 0, 1);
81
+ }
82
+ const endDate = value?.[1] ?? defaultValue?.[1];
83
+ if (endDate && startDate.getFullYear() !== endDate.getFullYear()) {
84
+ return;
85
+ }
86
+ return new Date(startDate.getFullYear() - 1, 0, 1);
87
+ })();
88
+ return (_jsx(BaseRangePickerProvider, { value: currentValue, defaultValue: defaultValue, disabled: disabled, format: format, isOpen: isPickerOpen, open: openPicker, close: closePickerAndNotify, minDate: minMonth, maxDate: maxMonth, initialDisplayedMonths: initialDisplayedMonths, initialViewingDate: initialViewingDate, onChange: handleRangeChange, children: _jsx(InternalMonthRangePickerPopover, { isPickerOpen: isPickerOpen, togglePicker: togglePicker, handleClear: handleClear, disabled: disabled, className: className, classes: classes, formattedValue: formattedValue, hasValue: hasValue, label: label, clearButtonProps: clearButtonProps, minMonth: minMonth, maxMonth: maxMonth, triggerRef: triggerRef, targetDOMNode: targetDOMNode, dialogId: dialogId }) }));
89
+ }
@@ -0,0 +1,75 @@
1
+ import { type PopoverProps } from '../../Popover';
2
+ import { type DateRangeInput } from '../shared/BaseRangePicker/BaseRangePicker.types';
3
+ export type FilterMonthRangePickerProps = {
4
+ /**
5
+ * The label to display for the filter.
6
+ */
7
+ label: string;
8
+ /**
9
+ * The selected month range (controlled). Each entry is the first day of a month.
10
+ */
11
+ value?: DateRangeInput;
12
+ /**
13
+ * The default month range for uncontrolled usage.
14
+ */
15
+ defaultValue?: DateRangeInput;
16
+ /**
17
+ * Callback fired when the selected month range changes.
18
+ *
19
+ * @param value - The new range `[startMonth, endMonth]`, or partial/empty tuples while interacting.
20
+ */
21
+ onChange?: (value: DateRangeInput) => void;
22
+ /**
23
+ * Whether the month range picker popover is open (controlled).
24
+ */
25
+ open?: boolean;
26
+ /**
27
+ * Callback fired when the open state of the picker changes.
28
+ *
29
+ * @param open - The new open state.
30
+ */
31
+ onOpenStateChanged?: (open: boolean) => void;
32
+ /**
33
+ * The display format for each bound of the range.
34
+ * Follows dayjs format tokens.
35
+ *
36
+ * @default 'YYYY/MM'
37
+ */
38
+ format?: string;
39
+ /**
40
+ * Props for the clear button, shown automatically when a complete range is selected.
41
+ *
42
+ * @property aria-label - The alternative text for the clear button. Default is '値をクリアする'.
43
+ */
44
+ clearButtonProps?: {
45
+ 'aria-label'?: string;
46
+ };
47
+ /**
48
+ * Whether the component is disabled.
49
+ *
50
+ * @default false
51
+ */
52
+ disabled?: boolean;
53
+ /**
54
+ * Additional class name applied to the trigger button.
55
+ */
56
+ className?: string;
57
+ /**
58
+ * The target DOM node to render the month range picker popover into.
59
+ */
60
+ targetDOMNode?: PopoverProps['targetDOMNode'];
61
+ /**
62
+ * The minimum selectable month.
63
+ */
64
+ minMonth?: Date;
65
+ /**
66
+ * The maximum selectable month.
67
+ */
68
+ maxMonth?: Date;
69
+ /**
70
+ * Controls which years are initially displayed in the two-panel calendar when there is no value.
71
+ *
72
+ * @default 'currentAndNext'
73
+ */
74
+ initialDisplayedMonths?: 'currentAndNext' | 'previousAndCurrent';
75
+ };
@@ -0,0 +1,5 @@
1
+ import { type InternalMonthRangePickerPopoverProps } from './MonthRangePickerPopover.types';
2
+ /**
3
+ * Internal popover UI for FilterMonthRangePicker. Must render inside BaseRangePickerProvider.
4
+ */
5
+ export declare function InternalMonthRangePickerPopover({ isPickerOpen, togglePicker, handleClear, disabled, className, classes, formattedValue, hasValue, label, clearButtonProps, minMonth, maxMonth, triggerRef, targetDOMNode, dialogId, }: InternalMonthRangePickerPopoverProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,54 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback } from 'react';
4
+ import { Calendar as CalendarIcon } from '@moneyforward/mfui-icons-react';
5
+ import { Popover } from '../../../Popover';
6
+ import { FocusIndicator } from '../../../FocusIndicator';
7
+ import { ClearButton } from '../../../shared';
8
+ import { Typography } from '../../../Typography';
9
+ import { cx } from '../../../../styled-system/css';
10
+ import { dayjs } from '../../../utilities/date/dayjs';
11
+ import { MonthRangePickerPanel } from '../../MonthRangePicker/MonthRangePickerPanel';
12
+ import { useBaseRangePickerContext } from '../../shared/BaseRangePicker/BaseRangePickerProvider';
13
+ import { useFocusTrap } from '../../../utilities/dom/useFocusTrap';
14
+ import { getFocusableNodes } from '../../../utilities/dom/getFocusableNodes';
15
+ /**
16
+ * Internal popover UI for FilterMonthRangePicker. Must render inside BaseRangePickerProvider.
17
+ */
18
+ export function InternalMonthRangePickerPopover({ isPickerOpen, togglePicker, handleClear, disabled, className, classes, formattedValue, hasValue, label, clearButtonProps, minMonth, maxMonth, triggerRef, targetDOMNode, dialogId, }) {
19
+ const { viewingMonth, setViewingMonth, temporaryStart, temporaryEnd, startDate, endDate, setPendingFocusDate } = useBaseRangePickerContext();
20
+ const { handleOnKeyDown } = useFocusTrap({
21
+ onPressingTabOnNotFocusableElement: (event) => {
22
+ const nodes = getFocusableNodes(event.currentTarget);
23
+ const focusableNodeAfterFocusingNode = nodes.find((element) =>
24
+ // eslint-disable-next-line no-bitwise
25
+ element.compareDocumentPosition(event.target) & Node.DOCUMENT_POSITION_PRECEDING);
26
+ const firstFocusableNode = nodes.at(0);
27
+ const nextFocusableNode = focusableNodeAfterFocusingNode ?? firstFocusableNode;
28
+ nextFocusableNode?.focus();
29
+ event.preventDefault();
30
+ },
31
+ });
32
+ const handlePopoverOpen = useCallback(() => {
33
+ let dateToFocus;
34
+ if (temporaryStart) {
35
+ dateToFocus = temporaryStart;
36
+ }
37
+ else if (temporaryEnd) {
38
+ dateToFocus = temporaryEnd;
39
+ }
40
+ else if (startDate) {
41
+ dateToFocus = startDate;
42
+ }
43
+ else if (endDate) {
44
+ dateToFocus = endDate;
45
+ }
46
+ else {
47
+ dateToFocus = new Date();
48
+ }
49
+ const monthToFocus = dayjs(dateToFocus).startOf('month').toDate();
50
+ setViewingMonth(monthToFocus);
51
+ setPendingFocusDate(dayjs(monthToFocus).format('YYYY-MM'));
52
+ }, [temporaryStart, temporaryEnd, startDate, endDate, setViewingMonth, setPendingFocusDate]);
53
+ return (_jsx(Popover, { open: isPickerOpen, targetDOMNode: targetDOMNode, renderTrigger: ({ setTriggerRef, togglePopover, handleTriggerKeyDown, handleTriggerBlur }) => (_jsxs("div", { ref: setTriggerRef, "data-selected": hasValue, className: cx(classes.wrapper, 'mfui-FilterMonthRangePicker__wrapper'), children: [_jsx(FocusIndicator, { children: _jsxs("button", { ref: triggerRef, type: "button", disabled: disabled, className: cx(classes.trigger, 'mfui-FilterMonthRangePicker__trigger', className), "data-selected": hasValue, "data-mfui-has-clear-button": hasValue, "aria-controls": dialogId, "aria-expanded": isPickerOpen, "aria-haspopup": "dialog", onClick: togglePopover, onKeyDown: handleTriggerKeyDown, onBlur: handleTriggerBlur, children: [_jsx("span", { className: cx(classes.content, 'mfui-FilterMonthRangePicker__content'), "data-mfui-content": "filter-month-range-picker-display-value", children: hasValue ? (_jsxs(Typography, { variant: "strongBody", children: [_jsx("span", { "data-mfui-content": "filter-month-range-picker-label", children: `${label}:` }), _jsx("span", { "data-mfui-content": "filter-month-range-picker-value", children: formattedValue })] })) : (_jsx(Typography, { variant: "controlLabel", children: _jsx("span", { "data-mfui-content": "filter-month-range-picker-label", children: label }) })) }), _jsx("span", { className: cx(classes.calendarIcon, 'mfui-FilterMonthRangePicker__calendarIcon'), children: _jsx(CalendarIcon, { "aria-hidden": true }) })] }) }), hasValue ? (_jsx("div", { className: cx(classes.clearButtonWrapper, 'mfui-FilterMonthRangePicker__clearButtonWrapper'), children: _jsx(ClearButton, { "aria-label": clearButtonProps?.['aria-label'] ?? '値をクリアする', "data-mfui-content": "filter-month-range-picker-clear-button", disabled: disabled, onClick: handleClear }) })) : null] })), renderContent: () => (_jsx("div", { id: dialogId, role: "dialog", "aria-label": label, className: "mfui-FilterMonthRangePicker__popoverWrapper", onKeyDown: handleOnKeyDown, children: _jsx(MonthRangePickerPanel, { viewingValue: viewingMonth, setViewingValue: setViewingMonth, value: [temporaryStart, temporaryEnd], minMonth: minMonth, maxMonth: maxMonth }) })), minWidth: "min-content", allowedPlacements: ['bottom-start', 'top-start'], onOpenStateChanged: togglePicker, onOpen: handlePopoverOpen }));
54
+ }
@@ -0,0 +1,19 @@
1
+ import { type filterMonthRangePickerSlotRecipe } from '../../../../styled-system/recipes';
2
+ import { type FilterMonthRangePickerProps } from '../FilterMonthRangePicker.types';
3
+ export type InternalMonthRangePickerPopoverProps = {
4
+ isPickerOpen: boolean;
5
+ togglePicker: () => void;
6
+ handleClear: (event: React.MouseEvent<HTMLButtonElement>) => void;
7
+ disabled: boolean;
8
+ className: string | undefined;
9
+ classes: ReturnType<typeof filterMonthRangePickerSlotRecipe>;
10
+ formattedValue: string | undefined;
11
+ hasValue: boolean;
12
+ label: string;
13
+ clearButtonProps: FilterMonthRangePickerProps['clearButtonProps'];
14
+ minMonth: FilterMonthRangePickerProps['minMonth'];
15
+ maxMonth: FilterMonthRangePickerProps['maxMonth'];
16
+ triggerRef: React.RefObject<HTMLButtonElement | null>;
17
+ targetDOMNode: FilterMonthRangePickerProps['targetDOMNode'];
18
+ dialogId: string;
19
+ };
@@ -0,0 +1,2 @@
1
+ export { FilterMonthRangePicker } from './FilterMonthRangePicker';
2
+ export type { FilterMonthRangePickerProps } from './FilterMonthRangePicker.types';
@@ -0,0 +1 @@
1
+ export { FilterMonthRangePicker } from './FilterMonthRangePicker';
@@ -5,4 +5,4 @@ import { type MonthPickerProps } from './MonthPicker.types';
5
5
  *
6
6
  * @param props - MonthPicker props including minMonth and maxMonth constraints
7
7
  */
8
- export declare function MonthPicker({ format, minMonth, maxMonth, checkDisabledMonth, calendarLocale, ...restProps }: MonthPickerProps): import("react/jsx-runtime").JSX.Element;
8
+ export declare function MonthPicker({ format, minMonth, maxMonth, checkDisabledMonth, calendarLocale, displayJapaneseCalendar, ...restProps }: MonthPickerProps): import("react/jsx-runtime").JSX.Element;
@@ -6,15 +6,23 @@ import { jsx as _jsx } from "react/jsx-runtime";
6
6
  * All logic and props are inherited from BasePicker, except for the popover content and format.
7
7
  * See BasePicker for implementation details.
8
8
  */
9
+ import { useMemo } from 'react';
9
10
  import { BasePicker } from '../shared/BasePicker';
10
11
  import { CalendarLocaleProvider } from '../shared/CalendarLocale/CalendarLocaleContext';
12
+ import { DisplayJapaneseCalendarProvider } from '../shared/CalendarLocale/DisplayJapaneseCalendarContext';
11
13
  import { MonthPickerPanel } from './MonthPickerPanel/MonthPickerPanel';
14
+ import { dateToEnglishEraMonthShort, dateToJapaneseEraMonth } from '../shared/utilities/japaneseCalendar';
12
15
  /**
13
16
  * MonthPicker component for selecting months.
14
17
  * Extends BasePicker with month-specific functionality.
15
18
  *
16
19
  * @param props - MonthPicker props including minMonth and maxMonth constraints
17
20
  */
18
- export function MonthPicker({ format = 'YYYY/MM', minMonth, maxMonth, checkDisabledMonth, calendarLocale = 'ja', ...restProps }) {
19
- return (_jsx(BasePicker, { ...restProps, format: format, baseFormat: "YYYY-MM", calendarLocale: calendarLocale, renderPopoverContent: ({ viewingValue, setViewingValue, value, onChange }) => (_jsx(CalendarLocaleProvider, { value: calendarLocale, children: _jsx(MonthPickerPanel, { viewingValue: viewingValue, setViewingValue: setViewingValue, value: value, minMonth: minMonth, maxMonth: maxMonth, checkDisabledMonth: checkDisabledMonth, onChange: onChange }) })) }));
21
+ export function MonthPicker({ format = 'YYYY/MM', minMonth, maxMonth, checkDisabledMonth, calendarLocale = 'ja', displayJapaneseCalendar = false, ...restProps }) {
22
+ const customFormatValue = useMemo(() => {
23
+ if (!displayJapaneseCalendar)
24
+ return;
25
+ return calendarLocale === 'en' ? dateToEnglishEraMonthShort : dateToJapaneseEraMonth;
26
+ }, [displayJapaneseCalendar, calendarLocale]);
27
+ return (_jsx(BasePicker, { ...restProps, format: format, baseFormat: "YYYY-MM", calendarLocale: calendarLocale, customFormatValue: customFormatValue, renderPopoverContent: ({ viewingValue, setViewingValue, value, onChange }) => (_jsx(DisplayJapaneseCalendarProvider, { value: displayJapaneseCalendar, children: _jsx(CalendarLocaleProvider, { value: calendarLocale, children: _jsx(MonthPickerPanel, { viewingValue: viewingValue, setViewingValue: setViewingValue, value: value, minMonth: minMonth, maxMonth: maxMonth, checkDisabledMonth: checkDisabledMonth, onChange: onChange }) }) })) }));
20
28
  }
@@ -39,4 +39,18 @@ export type MonthPickerProps = Omit<BasePickerProps, 'renderPopoverContent' | 'b
39
39
  * // Displays: "Jan", "Feb", etc. and English navigation labels
40
40
  */
41
41
  calendarLocale?: BasePickerProps['calendarLocale'];
42
+ /**
43
+ * Whether to display the month in Japanese calendar format (e.g., "令和7年1月")
44
+ * When enabled, the text input will show months in Japanese calendar format
45
+ * while internal value handling remains unchanged.
46
+ *
47
+ * @default false
48
+ *
49
+ * @remarks
50
+ * - When combined with `calendarLocale="en"`, months are romanized with
51
+ * a zero-padded month number (e.g., "Reiwa 7/01").
52
+ * - Pre-Meiji months (year < 1868) fall back to the Gregorian year
53
+ * (e.g., "1867/12").
54
+ */
55
+ displayJapaneseCalendar?: boolean;
42
56
  };
@@ -2,38 +2,15 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useMemo } from 'react';
3
3
  import { MonthCell } from '../../../shared/MonthCell';
4
4
  import { useCalendarLocale } from '../../../shared/CalendarLocale/CalendarLocaleContext';
5
+ import { getMonthCellMonthFormat } from '../../../shared/utilities/monthCellMonthFormat';
5
6
  import { useMonthPickerKeyboardNavigation } from '../../utilities/useMonthPickerKeyboardNavigation';
6
- // English month abbreviations wrapped in square brackets to escape them in dayjs format strings
7
- const ENGLISH_MONTH_ABBREVIATIONS = [
8
- '[Jan]',
9
- '[Feb]',
10
- '[Mar]',
11
- '[Apr]',
12
- '[May]',
13
- '[Jun]',
14
- '[Jul]',
15
- '[Aug]',
16
- '[Sep]',
17
- '[Oct]',
18
- '[Nov]',
19
- '[Dec]',
20
- ];
21
7
  /**
22
8
  * MonthPickerMonthCell is a wrapper around the shared MonthCell component.
23
9
  * It handles month selection and integrates with the MonthPicker context.
24
10
  */
25
11
  export function MonthPickerMonthCell({ onKeyDown, minMonth, maxMonth, checkDisabledMonth, date, ...rest }) {
26
12
  const locale = useCalendarLocale();
27
- // Generate month format string based on locale
28
- const monthFormat = useMemo(() => {
29
- const monthIndex = date.getMonth();
30
- if (locale === 'en') {
31
- // Return English month abbreviation escaped for dayjs
32
- return ENGLISH_MONTH_ABBREVIATIONS[monthIndex];
33
- }
34
- // Japanese format: "1月", "2月", etc. (using dayjs format)
35
- return 'M月';
36
- }, [locale, date]);
13
+ const monthFormat = useMemo(() => getMonthCellMonthFormat(locale, date), [locale, date]);
37
14
  const handleKeyDown = useMonthPickerKeyboardNavigation(onKeyDown, minMonth, maxMonth, checkDisabledMonth);
38
15
  return _jsx(MonthCell, { ...rest, date: date, monthFormat: monthFormat, onKeyDown: handleKeyDown });
39
16
  }
@@ -109,7 +109,7 @@ export function MonthPickerPanel({ viewingValue, setViewingValue, value, onChang
109
109
  setPendingFocusDate(null);
110
110
  }, [viewingValue, pendingFocusDate, setPendingFocusDate, baseFormat]);
111
111
  const classNames = monthPickerPanelSlotRecipe();
112
- return (_jsxs("div", { ...props, className: cx(classNames.root, 'mfui-MonthPickerPanel__root', className), children: [_jsxs("div", { className: cx(classNames.navigation, 'mfui-MonthPickerPanel__navigation'), children: [!isPreviousYearDisabled ? (_jsx(IconButton, { "aria-label": getMonthPickerLabel(locale ?? 'ja', 'prevYear'), onClick: handlePreviousYear, children: _jsx(PickerBefore, {}) })) : (_jsx(YearNavigationPlaceholder, { className: cx(classNames.yearNavigationPlaceholder, 'mfui-MonthPickerPanel__yearNavigationPlaceholder') })), _jsx(YearSelector, { value: viewingValue, triggerWrapperProps: { className: cx(classNames.yearSelector, 'mfui-MonthPickerPanel__yearSelector') }, filterYear: shouldFilterYears ? filterYear : undefined, onChange: handleYearSelect }), !isNextYearDisabled ? (_jsx(IconButton, { "aria-label": getMonthPickerLabel(locale ?? 'ja', 'nextYear'), onClick: handleNextYear, children: _jsx(PickerAfter, {}) })) : (_jsx(YearNavigationPlaceholder, { className: cx(classNames.yearNavigationPlaceholder, 'mfui-MonthPickerPanel__yearNavigationPlaceholder') }))] }), _jsx(MonthPickerMonthGrid, { viewingValue: viewingValue, value: value, minMonth: minMonth, maxMonth: maxMonth, checkDisabledMonth: checkDisabledMonth, onChange: onChange })] }));
112
+ return (_jsxs("div", { ...props, className: cx(classNames.root, 'mfui-MonthPickerPanel__root', className), children: [_jsxs("div", { className: cx(classNames.navigation, 'mfui-MonthPickerPanel__navigation'), children: [!isPreviousYearDisabled ? (_jsx(IconButton, { "aria-label": getMonthPickerLabel(locale, 'prevYear'), onClick: handlePreviousYear, children: _jsx(PickerBefore, {}) })) : (_jsx(YearNavigationPlaceholder, { className: cx(classNames.yearNavigationPlaceholder, 'mfui-MonthPickerPanel__yearNavigationPlaceholder') })), _jsx(YearSelector, { value: viewingValue, triggerWrapperProps: { className: cx(classNames.yearSelector, 'mfui-MonthPickerPanel__yearSelector') }, filterYear: shouldFilterYears ? filterYear : undefined, onChange: handleYearSelect }), !isNextYearDisabled ? (_jsx(IconButton, { "aria-label": getMonthPickerLabel(locale, 'nextYear'), onClick: handleNextYear, children: _jsx(PickerAfter, {}) })) : (_jsx(YearNavigationPlaceholder, { className: cx(classNames.yearNavigationPlaceholder, 'mfui-MonthPickerPanel__yearNavigationPlaceholder') }))] }), _jsx(MonthPickerMonthGrid, { viewingValue: viewingValue, value: value, minMonth: minMonth, maxMonth: maxMonth, checkDisabledMonth: checkDisabledMonth, onChange: onChange })] }));
113
113
  }
114
114
  // Create a placeholder div that matches IconButton dimensions to prevent layout shift
115
115
  function YearNavigationPlaceholder({ className }) {
@@ -14,4 +14,4 @@ import { type MonthRangePickerProps } from './MonthRangePicker.types';
14
14
  * />
15
15
  * ```
16
16
  */
17
- export declare function MonthRangePicker({ format, minMonth, maxMonth, startInputProps, endInputProps, initialDisplayedMonths, ...restProps }: MonthRangePickerProps): import("react/jsx-runtime").JSX.Element;
17
+ export declare function MonthRangePicker({ format, minMonth, maxMonth, startInputProps, endInputProps, initialDisplayedMonths, displayJapaneseCalendar, calendarLocale, ...restProps }: MonthRangePickerProps): import("react/jsx-runtime").JSX.Element;
@@ -12,8 +12,12 @@ import { jsx as _jsx } from "react/jsx-runtime";
12
12
  * - Keyboard navigation support
13
13
  * - Apply/Cancel workflow
14
14
  */
15
+ import { useMemo } from 'react';
15
16
  import { BaseRangePicker } from '../shared/BaseRangePicker';
17
+ import { CalendarLocaleProvider } from '../shared/CalendarLocale/CalendarLocaleContext';
18
+ import { DisplayJapaneseCalendarProvider } from '../shared/CalendarLocale/DisplayJapaneseCalendarContext';
16
19
  import { MonthRangePickerPanel } from './MonthRangePickerPanel';
20
+ import { dateToEnglishEraMonthShort, dateToJapaneseEraMonth } from '../shared/utilities/japaneseCalendar';
17
21
  /**
18
22
  * MonthRangePicker component for selecting month ranges.
19
23
  *
@@ -29,7 +33,12 @@ import { MonthRangePickerPanel } from './MonthRangePickerPanel';
29
33
  * />
30
34
  * ```
31
35
  */
32
- export function MonthRangePicker({ format = 'YYYY/MM', minMonth, maxMonth, startInputProps = {}, endInputProps = {}, initialDisplayedMonths, ...restProps }) {
36
+ export function MonthRangePicker({ format = 'YYYY/MM', minMonth, maxMonth, startInputProps = {}, endInputProps = {}, initialDisplayedMonths, displayJapaneseCalendar = false, calendarLocale = 'ja', ...restProps }) {
37
+ const customFormatValue = useMemo(() => {
38
+ if (!displayJapaneseCalendar)
39
+ return;
40
+ return calendarLocale === 'en' ? dateToEnglishEraMonthShort : dateToJapaneseEraMonth;
41
+ }, [displayJapaneseCalendar, calendarLocale]);
33
42
  // MonthRangePicker panels are grouped by year (left = viewingYear, right = viewingYear + 1).
34
43
  // When `previousAndCurrent` is requested, we shift by a full year so the left panel
35
44
  // shows the previous year. This applies regardless of whether `value` is set.
@@ -51,11 +60,11 @@ export function MonthRangePicker({ format = 'YYYY/MM', minMonth, maxMonth, start
51
60
  // Same-year value + previousAndCurrent: show previous year on the left
52
61
  return new Date(startDate.getFullYear() - 1, 0, 1);
53
62
  })();
54
- return (_jsx(BaseRangePicker, { ...restProps, format: format, initialDisplayedMonths: initialDisplayedMonths, initialViewingDate: initialViewingDate, startInputProps: {
55
- placeholder: '開始月',
63
+ return (_jsx(BaseRangePicker, { ...restProps, format: format, pendingFocusDayjsFormat: "YYYY-MM", initialDisplayedMonths: initialDisplayedMonths, initialViewingDate: initialViewingDate, customFormatValue: customFormatValue, startInputProps: {
64
+ placeholder: calendarLocale === 'en' ? 'Start month' : '開始月',
56
65
  ...startInputProps,
57
66
  }, endInputProps: {
58
- placeholder: '終了月',
67
+ placeholder: calendarLocale === 'en' ? 'End month' : '終了月',
59
68
  ...endInputProps,
60
- }, minDate: minMonth, maxDate: maxMonth, minWidth: "min-content", renderPopoverContent: ({ viewingValue, setViewingValue, value }) => (_jsx(MonthRangePickerPanel, { viewingValue: viewingValue, setViewingValue: setViewingValue, value: value, minMonth: minMonth, maxMonth: maxMonth })) }));
69
+ }, minDate: minMonth, maxDate: maxMonth, minWidth: "min-content", calendarLocale: calendarLocale, renderPopoverContent: ({ viewingValue, setViewingValue, value }) => (_jsx(DisplayJapaneseCalendarProvider, { value: displayJapaneseCalendar, children: _jsx(CalendarLocaleProvider, { value: calendarLocale, children: _jsx(MonthRangePickerPanel, { viewingValue: viewingValue, setViewingValue: setViewingValue, value: value, minMonth: minMonth, maxMonth: maxMonth }) }) })) }));
61
70
  }
@@ -1,3 +1,4 @@
1
+ import { type BasePickerProps } from '../shared/BasePicker';
1
2
  import { type BaseRangePickerProps } from '../shared/BaseRangePicker';
2
3
  /**
3
4
  * The props for the MonthRangePicker component
@@ -25,4 +26,24 @@ export type MonthRangePickerProps = BaseRangePickerProps & {
25
26
  * ```
26
27
  */
27
28
  maxMonth?: Date;
29
+ /**
30
+ * Whether to display the month in Japanese calendar format (e.g., "令和7年1月")
31
+ * When enabled, the text inputs will show months in Japanese calendar format
32
+ * while internal value handling remains unchanged.
33
+ *
34
+ * @default false
35
+ *
36
+ * @remarks
37
+ * - When combined with `calendarLocale="en"`, months are romanized with
38
+ * a zero-padded month number (e.g., "Reiwa 7/01").
39
+ * - Pre-Meiji months (year < 1868) fall back to the Gregorian year
40
+ * (e.g., "1867/12").
41
+ */
42
+ displayJapaneseCalendar?: boolean;
43
+ /**
44
+ * The locale of the calendar.
45
+ *
46
+ * @default 'ja'
47
+ */
48
+ calendarLocale?: BasePickerProps['calendarLocale'];
28
49
  };
@@ -1,4 +1,5 @@
1
1
  import { type MonthRangePickerMonthCellProps } from './MonthRangePickerMonthCell.types';
2
+ import 'dayjs/locale/en';
2
3
  /**
3
4
  * MonthRangePickerMonthCell component that wraps MonthCell with range selection visual states.
4
5
  *
@@ -1,8 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useMemo } from 'react';
3
3
  import { MonthCell } from '../../shared/MonthCell';
4
+ import { useCalendarLocale } from '../../shared/CalendarLocale/CalendarLocaleContext';
5
+ import { getMonthCellMonthFormat } from '../../shared/utilities/monthCellMonthFormat';
4
6
  import { dayjs } from '../../../utilities/date/dayjs';
5
7
  import { MONTH_RANGE_PICKER_A11Y_LABELS } from './constants';
8
+ import 'dayjs/locale/en';
6
9
  /**
7
10
  * MonthRangePickerMonthCell component that wraps MonthCell with range selection visual states.
8
11
  *
@@ -17,11 +20,15 @@ import { MONTH_RANGE_PICKER_A11Y_LABELS } from './constants';
17
20
  * @param props - MonthRangePickerMonthCell props
18
21
  */
19
22
  export function MonthRangePickerMonthCell({ isInRange, isRangeStart, isRangeEnd, onMouseEnter, onMouseLeave, handleKeyboardNavigation, ...monthCellProps }) {
23
+ const calendarLocale = useCalendarLocale();
20
24
  // Calculate if this month is selected (either start or end of range)
21
25
  const selected = isRangeStart || isRangeEnd || monthCellProps.selected || false;
26
+ const monthFormat = useMemo(() => getMonthCellMonthFormat(calendarLocale, monthCellProps.date), [calendarLocale, monthCellProps.date]);
22
27
  // Create accessible label for range selection
23
28
  const accessibleLabel = useMemo(() => {
24
- const monthText = dayjs(monthCellProps.date).format('MMMM YYYY');
29
+ const monthText = calendarLocale === 'en'
30
+ ? dayjs(monthCellProps.date).locale('en').format('MMMM YYYY')
31
+ : dayjs(monthCellProps.date).format('MMMM YYYY');
25
32
  const statusParts = [];
26
33
  if (monthCellProps.disabled) {
27
34
  statusParts.push(MONTH_RANGE_PICKER_A11Y_LABELS.UNAVAILABLE);
@@ -41,6 +48,6 @@ export function MonthRangePickerMonthCell({ isInRange, isRangeStart, isRangeEnd,
41
48
  statusParts.push(MONTH_RANGE_PICKER_A11Y_LABELS.IN_RANGE);
42
49
  }
43
50
  return statusParts.length > 0 ? `${monthText}, ${statusParts.join(', ')}` : monthText;
44
- }, [monthCellProps.date, monthCellProps.disabled, selected, isInRange, isRangeStart, isRangeEnd]);
45
- return (_jsx("div", { onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, children: _jsx(MonthCell, { ...monthCellProps, selected: selected, inRange: isInRange, "aria-label": accessibleLabel, "aria-pressed": selected ? 'true' : undefined, "aria-selected": selected ? 'true' : 'false', onKeyDown: handleKeyboardNavigation }) }));
51
+ }, [calendarLocale, monthCellProps.date, monthCellProps.disabled, selected, isInRange, isRangeStart, isRangeEnd]);
52
+ return (_jsx("div", { onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, children: _jsx(MonthCell, { ...monthCellProps, monthFormat: monthFormat, selected: selected, inRange: isInRange, "aria-label": accessibleLabel, "aria-pressed": selected ? 'true' : undefined, "aria-selected": selected ? 'true' : 'false', onKeyDown: handleKeyboardNavigation }) }));
46
53
  }