@moneyforward/mfui-components 3.19.0 → 3.20.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 (75) hide show
  1. package/dist/src/CheckboxCard/CheckboxCard.js +1 -7
  2. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerProvider/DateRangePickerProvider.js +11 -12
  3. package/dist/src/DateTimeSelection/DateRangePicker/DateRangePickerTrigger/hooks/useDateRangeTriggerValueController.js +4 -3
  4. package/dist/src/DateTimeSelection/FilterDateRangePicker/DateRangePickerContent/DateRangePickerContent.d.ts +6 -0
  5. package/dist/src/DateTimeSelection/FilterDateRangePicker/DateRangePickerContent/DateRangePickerContent.js +33 -0
  6. package/dist/src/DateTimeSelection/FilterDateRangePicker/DateRangePickerContent/DateRangePickerContent.types.d.ts +17 -0
  7. package/dist/src/DateTimeSelection/FilterDateRangePicker/FilterDateRangePicker.d.ts +14 -0
  8. package/dist/src/DateTimeSelection/FilterDateRangePicker/FilterDateRangePicker.js +42 -0
  9. package/dist/src/DateTimeSelection/FilterDateRangePicker/FilterDateRangePicker.types.d.ts +78 -0
  10. package/dist/src/DateTimeSelection/FilterDateRangePicker/FilterDateRangePicker.types.js +1 -0
  11. package/dist/src/DateTimeSelection/FilterDateRangePicker/index.d.ts +2 -0
  12. package/dist/src/DateTimeSelection/FilterDateRangePicker/index.js +1 -0
  13. package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.d.ts +14 -0
  14. package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.js +75 -0
  15. package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.types.d.ts +83 -0
  16. package/dist/src/DateTimeSelection/FilterMonthPicker/FilterMonthPicker.types.js +1 -0
  17. package/dist/src/DateTimeSelection/FilterMonthPicker/MonthPickerPopover/MonthPickerPopover.d.ts +6 -0
  18. package/dist/src/DateTimeSelection/FilterMonthPicker/MonthPickerPopover/MonthPickerPopover.js +45 -0
  19. package/dist/src/DateTimeSelection/FilterMonthPicker/MonthPickerPopover/MonthPickerPopover.types.d.ts +24 -0
  20. package/dist/src/DateTimeSelection/FilterMonthPicker/MonthPickerPopover/MonthPickerPopover.types.js +1 -0
  21. package/dist/src/DateTimeSelection/FilterMonthPicker/index.d.ts +2 -0
  22. package/dist/src/DateTimeSelection/FilterMonthPicker/index.js +1 -0
  23. package/dist/src/DateTimeSelection/FilterMonthRangePicker/FilterMonthRangePicker.d.ts +15 -0
  24. package/dist/src/DateTimeSelection/FilterMonthRangePicker/FilterMonthRangePicker.js +89 -0
  25. package/dist/src/DateTimeSelection/FilterMonthRangePicker/FilterMonthRangePicker.types.d.ts +75 -0
  26. package/dist/src/DateTimeSelection/FilterMonthRangePicker/FilterMonthRangePicker.types.js +1 -0
  27. package/dist/src/DateTimeSelection/FilterMonthRangePicker/MonthRangePickerPopover/MonthRangePickerPopover.d.ts +5 -0
  28. package/dist/src/DateTimeSelection/FilterMonthRangePicker/MonthRangePickerPopover/MonthRangePickerPopover.js +54 -0
  29. package/dist/src/DateTimeSelection/FilterMonthRangePicker/MonthRangePickerPopover/MonthRangePickerPopover.types.d.ts +19 -0
  30. package/dist/src/DateTimeSelection/FilterMonthRangePicker/MonthRangePickerPopover/MonthRangePickerPopover.types.js +1 -0
  31. package/dist/src/DateTimeSelection/FilterMonthRangePicker/index.d.ts +2 -0
  32. package/dist/src/DateTimeSelection/FilterMonthRangePicker/index.js +1 -0
  33. package/dist/src/DateTimeSelection/MonthRangePicker/MonthRangePicker.js +1 -1
  34. package/dist/src/DateTimeSelection/index.d.ts +3 -0
  35. package/dist/src/DateTimeSelection/index.js +3 -0
  36. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.d.ts +1 -1
  37. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.js +26 -10
  38. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePicker.types.d.ts +9 -0
  39. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerProvider/BaseRangePickerProvider.js +32 -15
  40. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerProvider/BaseRangePickerProvider.types.d.ts +10 -0
  41. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerTrigger/BaseRangePickerTrigger.js +4 -6
  42. package/dist/src/DateTimeSelection/shared/BaseRangePicker/BaseRangePickerTrigger/hooks/useDateRangeTriggerValueController.js +4 -3
  43. package/dist/src/DateTimeSelection/shared/MonthGrid/MonthGrid.js +3 -3
  44. package/dist/src/DateTimeSelection/shared/utilities/dateParsing.js +11 -0
  45. package/dist/src/MultilineTextBox/index.d.ts +10 -2
  46. package/dist/src/MultilineTextBox/index.js +9 -1
  47. package/dist/src/MultipleSelectBox/MultipleSelectBoxTrigger/MultipleSelectBoxTrigger.js +1 -3
  48. package/dist/src/RadioButtonCard/RadioButtonCard.js +1 -7
  49. package/dist/src/TextBox/TextBox.js +2 -6
  50. package/dist/src/{MultilineTextBox/MultilineTextBox.d.ts → Textarea/Textarea.d.ts} +5 -2
  51. package/dist/src/{MultilineTextBox/MultilineTextBox.js → Textarea/Textarea.js} +9 -5
  52. package/dist/src/{MultilineTextBox/MultilineTextBox.types.d.ts → Textarea/Textarea.types.d.ts} +2 -2
  53. package/dist/src/Textarea/Textarea.types.js +1 -0
  54. package/dist/src/Textarea/index.d.ts +2 -0
  55. package/dist/src/Textarea/index.js +1 -0
  56. package/dist/src/Tooltip/Tooltip.js +12 -3
  57. package/dist/src/Typography/Typography.js +1 -3
  58. package/dist/src/index.d.ts +1 -0
  59. package/dist/src/index.js +1 -0
  60. package/dist/styled-system/recipes/filter-date-range-picker-slot-recipe.d.ts +33 -0
  61. package/dist/styled-system/recipes/filter-date-range-picker-slot-recipe.js +48 -0
  62. package/dist/styled-system/recipes/filter-month-picker-slot-recipe.d.ts +33 -0
  63. package/dist/styled-system/recipes/filter-month-picker-slot-recipe.js +44 -0
  64. package/dist/styled-system/recipes/filter-month-range-picker-slot-recipe.d.ts +33 -0
  65. package/dist/styled-system/recipes/filter-month-range-picker-slot-recipe.js +44 -0
  66. package/dist/styled-system/recipes/index.d.ts +4 -1
  67. package/dist/styled-system/recipes/index.js +4 -1
  68. package/dist/styled-system/recipes/textarea-slot-recipe.d.ts +52 -0
  69. package/dist/styled-system/recipes/textarea-slot-recipe.js +64 -0
  70. package/dist/styles.css +433 -24
  71. package/dist/tsconfig.build.tsbuildinfo +1 -1
  72. package/package.json +10 -8
  73. package/dist/styled-system/recipes/multiline-text-box-slot-recipe.d.ts +0 -52
  74. package/dist/styled-system/recipes/multiline-text-box-slot-recipe.js +0 -64
  75. /package/dist/src/{MultilineTextBox/MultilineTextBox.types.js → DateTimeSelection/FilterDateRangePicker/DateRangePickerContent/DateRangePickerContent.types.js} +0 -0
@@ -29,11 +29,5 @@ export const CheckboxCard = forwardRef(({ label, layout = 'vertical', renderDeta
29
29
  disableResponsive,
30
30
  isResponsive: Boolean(responsiveSlot.narrowViewport),
31
31
  });
32
- return (_jsxs("div", { className: cx(classes.root, 'mfui-CheckboxCard__root', className), children: [_jsx(FocusIndicator, { disableNestedFocus: true, children: _jsxs(Typography, { variant: "label", className: cx(classes.inputWrapper, 'mfui-CheckboxCard__inputWrapper'), as: "label", htmlFor: props.id, children: [_jsx(Checkbox, { ref: ref, ...props, className: cx(classes.input, 'mfui-CheckboxCard__input') }), _jsx("span", { className: cx(classes.label, 'mfui-CheckboxCard__label'), children: label })] }) }), responsiveSlot.narrowViewport && disableResponsive ? (
33
- /* When responsive is disabled, render single element but with both contents using CSS for switching */
34
- _jsxs(_Fragment, { children: [_jsx("div", { "data-mfui-content": "details", className: cx(classes.details, classes.detailsWide, 'mfui-CheckboxCard__details'), children: responsiveSlot.base(checkboxState) }), _jsx("div", { "data-mfui-content": "details", className: cx(classes.details, classes.detailsNarrow, 'mfui-CheckboxCard__details'), children: responsiveSlot.narrowViewport(checkboxState) })] })) : responsiveSlot.narrowViewport ? (
35
- /* When responsive is enabled, render separate wide/narrow elements */
36
- _jsxs(_Fragment, { children: [_jsx("div", { "data-mfui-content": "details", className: cx(classes.details, classes.detailsWide, 'mfui-CheckboxCard__details'), children: responsiveSlot.base(checkboxState) }), _jsx("div", { "data-mfui-content": "details", className: cx(classes.details, classes.detailsNarrow, 'mfui-CheckboxCard__details'), children: responsiveSlot.narrowViewport(checkboxState) })] })) : responsiveSlot.base ? (
37
- /* For function-based renderDetailsSlot or ResponsiveSlot with only base, use single details element */
38
- _jsx("div", { "data-mfui-content": "details", className: cx(classes.details, 'mfui-CheckboxCard__details'), children: responsiveSlot.base(checkboxState) })) : null] }));
32
+ return (_jsxs("div", { className: cx(classes.root, 'mfui-CheckboxCard__root', className), children: [_jsx(FocusIndicator, { disableNestedFocus: true, children: _jsxs(Typography, { variant: "label", className: cx(classes.inputWrapper, 'mfui-CheckboxCard__inputWrapper'), as: "label", htmlFor: props.id, children: [_jsx(Checkbox, { ref: ref, ...props, className: cx(classes.input, 'mfui-CheckboxCard__input') }), _jsx("span", { className: cx(classes.label, 'mfui-CheckboxCard__label'), children: label })] }) }), responsiveSlot.narrowViewport && disableResponsive ? (_jsxs(_Fragment, { children: [_jsx("div", { "data-mfui-content": "details", className: cx(classes.details, classes.detailsWide, 'mfui-CheckboxCard__details'), children: responsiveSlot.base(checkboxState) }), _jsx("div", { "data-mfui-content": "details", className: cx(classes.details, classes.detailsNarrow, 'mfui-CheckboxCard__details'), children: responsiveSlot.narrowViewport(checkboxState) })] })) : responsiveSlot.narrowViewport ? (_jsxs(_Fragment, { children: [_jsx("div", { "data-mfui-content": "details", className: cx(classes.details, classes.detailsWide, 'mfui-CheckboxCard__details'), children: responsiveSlot.base(checkboxState) }), _jsx("div", { "data-mfui-content": "details", className: cx(classes.details, classes.detailsNarrow, 'mfui-CheckboxCard__details'), children: responsiveSlot.narrowViewport(checkboxState) })] })) : responsiveSlot.base ? (_jsx("div", { "data-mfui-content": "details", className: cx(classes.details, 'mfui-CheckboxCard__details'), children: responsiveSlot.base(checkboxState) })) : null] }));
39
33
  });
@@ -5,6 +5,7 @@ import { flushSync } from 'react-dom';
5
5
  import { dayjs } from '../../../utilities/date/dayjs';
6
6
  import { useUpdateEffect } from '../../../utilities/effect/useUpdateEffect';
7
7
  import { useTransformedState } from '../../../utilities/state/useTransformedState';
8
+ import { parseInputDateSimple } from '../../shared/utilities/dateParsing';
8
9
  // eslint-disable-next-line @typescript-eslint/no-empty-function
9
10
  const noop = () => { };
10
11
  // Normalizes a date to the first of the month
@@ -92,14 +93,13 @@ const useDateRangePickerContextValue = ({ value, defaultValue, disabled = false,
92
93
  const handleStartDateStringChange = useCallback((value) => {
93
94
  setDateStrings([value, endDateString]);
94
95
  // Parse and update both viewing month and temporary selection for immediate visual feedback
95
- const parsedDate = dayjs(value, format, true);
96
- if (parsedDate.isValid() && !isDateOutsideConstraints(parsedDate.toDate(), minDate, maxDate)) {
97
- const newDate = parsedDate.toDate();
98
- setViewingMonth(newDate);
96
+ const parsedDate = parseInputDateSimple(value, format);
97
+ if (parsedDate && !isDateOutsideConstraints(parsedDate, minDate, maxDate)) {
98
+ setViewingMonth(parsedDate);
99
99
  // Update temporary selection for immediate calendar visual feedback
100
- setTemporaryStart(newDate);
100
+ setTemporaryStart(parsedDate);
101
101
  // Set pending focus date so when calendar opens, it focuses on this date
102
- setPendingFocusDate(parsedDate.format('YYYY-MM-DD'));
102
+ setPendingFocusDate(dayjs(parsedDate).format('YYYY-MM-DD'));
103
103
  }
104
104
  else if (value === '') {
105
105
  // Clear temporary selection if input is empty
@@ -110,14 +110,13 @@ const useDateRangePickerContextValue = ({ value, defaultValue, disabled = false,
110
110
  const handleEndDateStringChange = useCallback((value) => {
111
111
  setDateStrings([startDateString, value]);
112
112
  // Parse and update both viewing month and temporary selection for immediate visual feedback
113
- const parsedDate = dayjs(value, format, true);
114
- if (parsedDate.isValid() && !isDateOutsideConstraints(parsedDate.toDate(), minDate, maxDate)) {
115
- const newDate = parsedDate.toDate();
116
- setViewingMonth(newDate);
113
+ const parsedDate = parseInputDateSimple(value, format);
114
+ if (parsedDate && !isDateOutsideConstraints(parsedDate, minDate, maxDate)) {
115
+ setViewingMonth(parsedDate);
117
116
  // Update temporary selection for immediate calendar visual feedback
118
- setTemporaryEnd(newDate);
117
+ setTemporaryEnd(parsedDate);
119
118
  // Set pending focus date so when calendar opens, it focuses on this date
120
- setPendingFocusDate(parsedDate.format('YYYY-MM-DD'));
119
+ setPendingFocusDate(dayjs(parsedDate).format('YYYY-MM-DD'));
121
120
  }
122
121
  else if (value === '') {
123
122
  // Clear temporary selection if input is empty
@@ -1,6 +1,7 @@
1
1
  import { useMemo } from 'react';
2
2
  import { dayjs } from '../../../../utilities/date/dayjs';
3
3
  import { useDateRangePickerContext } from '../../DateRangePickerProvider';
4
+ import { parseInputDateSimple } from '../../../shared/utilities/dateParsing';
4
5
  const createInputProps = (baseProps, value, format, onStringChange, onDateChange, otherDate, isDateDisabled) => ({
5
6
  ...baseProps,
6
7
  value,
@@ -15,12 +16,12 @@ const createInputProps = (baseProps, value, format, onStringChange, onDateChange
15
16
  if (event.defaultPrevented)
16
17
  return;
17
18
  baseProps.onBlur?.(event);
18
- const dateValue = dayjs(value, format, true);
19
- if (!dateValue.isValid()) {
19
+ const parsedDate = parseInputDateSimple(value, format);
20
+ if (!parsedDate) {
20
21
  onDateChange(undefined, otherDate);
21
22
  return;
22
23
  }
23
- const currentDate = dateValue.startOf('day').toDate();
24
+ const currentDate = dayjs(parsedDate).startOf('day').toDate();
24
25
  // Check if the date is disabled due to constraints
25
26
  if (isDateDisabled?.(currentDate)) {
26
27
  // Clear the input if the date violates constraints
@@ -0,0 +1,6 @@
1
+ import { type InternalDateRangePickerContentProps } from './DateRangePickerContent.types';
2
+ /**
3
+ * Internal component that accesses BaseRangePickerContext for display and clear handling.
4
+ * Must be rendered inside a BaseRangePickerProvider.
5
+ */
6
+ export declare function InternalDateRangePickerContent({ classes, triggerRef, dialogId, label, format, disabled, className, clearButtonProps, targetDOMNode, isCalendarOpen, toggleCalendar, calendarLocale, }: InternalDateRangePickerContentProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,33 @@
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 { DateRangePickerPopover } from '../../DateRangePicker/DateRangePickerPopover';
12
+ import { CalendarLocaleProvider } from '../../shared/CalendarLocale/CalendarLocaleContext';
13
+ import { useBaseRangePickerContext } from '../../shared/BaseRangePicker/BaseRangePickerProvider';
14
+ /**
15
+ * Internal component that accesses BaseRangePickerContext for display and clear handling.
16
+ * Must be rendered inside a BaseRangePickerProvider.
17
+ */
18
+ export function InternalDateRangePickerContent({ classes, triggerRef, dialogId, label, format, disabled, className, clearButtonProps, targetDOMNode, isCalendarOpen, toggleCalendar, calendarLocale, }) {
19
+ const { startDate, endDate, handleClear: contextHandleClear, setPendingFocusDate } = useBaseRangePickerContext();
20
+ const formattedStartDate = startDate ? dayjs(startDate).format(format) : undefined;
21
+ const formattedEndDate = endDate ? dayjs(endDate).format(format) : undefined;
22
+ const hasValue = !!(formattedStartDate && formattedEndDate);
23
+ const formattedValue = hasValue ? `${formattedStartDate}〜${formattedEndDate}` : undefined;
24
+ const handleTriggerClear = useCallback((event) => {
25
+ event.stopPropagation();
26
+ contextHandleClear();
27
+ }, [contextHandleClear]);
28
+ const handlePopoverOpen = useCallback(() => {
29
+ const dateToFocus = startDate ?? endDate ?? new Date();
30
+ setPendingFocusDate(dayjs(dateToFocus).format('YYYY-MM-DD'));
31
+ }, [startDate, endDate, setPendingFocusDate]);
32
+ return (_jsx(Popover, { open: isCalendarOpen, targetDOMNode: targetDOMNode, minWidth: "min-content", allowedPlacements: ['bottom-start', 'top-start'], renderTrigger: ({ setTriggerRef, togglePopover, handleTriggerKeyDown, handleTriggerBlur }) => (_jsxs("div", { ref: setTriggerRef, "data-selected": hasValue, className: cx(classes.wrapper, 'mfui-FilterDateRangePicker__wrapper'), children: [_jsx(FocusIndicator, { children: _jsxs("button", { ref: triggerRef, type: "button", disabled: disabled, className: cx(classes.trigger, 'mfui-FilterDateRangePicker__trigger', className), "data-selected": hasValue, "data-mfui-has-clear-button": hasValue, "aria-controls": dialogId, "aria-expanded": isCalendarOpen, "aria-haspopup": "dialog", onClick: togglePopover, onKeyDown: handleTriggerKeyDown, onBlur: handleTriggerBlur, children: [_jsx("span", { className: cx(classes.content, 'mfui-FilterDateRangePicker__content'), "data-mfui-content": "filter-date-range-picker-display-value", children: hasValue ? (_jsxs(Typography, { variant: "strongBody", children: [_jsx("span", { "data-mfui-content": "filter-date-range-picker-label", children: `${label}:` }), _jsx("span", { "data-mfui-content": "filter-date-range-picker-value", children: formattedValue })] })) : (_jsx(Typography, { variant: "controlLabel", children: _jsx("span", { "data-mfui-content": "filter-date-range-picker-label", children: label }) })) }), _jsx("span", { className: cx(classes.calendarIcon, 'mfui-FilterDateRangePicker__calendarIcon'), children: _jsx(CalendarIcon, { "aria-hidden": true }) })] }) }), hasValue ? (_jsx("div", { className: cx(classes.clearButtonWrapper, 'mfui-FilterDateRangePicker__clearButtonWrapper'), children: _jsx(ClearButton, { "aria-label": clearButtonProps?.['aria-label'] ?? '値をクリアする', "data-mfui-content": "filter-date-range-picker-clear-button", disabled: disabled, onClick: handleTriggerClear }) })) : null] })), renderContent: () => (_jsx("div", { id: dialogId, role: "dialog", "aria-label": label, className: cx(classes.popoverWrapper, 'mfui-FilterDateRangePicker__popoverWrapper'), children: _jsx(CalendarLocaleProvider, { value: calendarLocale, children: _jsx(DateRangePickerPopover, {}) }) })), onOpenStateChanged: toggleCalendar, onOpen: handlePopoverOpen }));
33
+ }
@@ -0,0 +1,17 @@
1
+ import { type filterDateRangePickerSlotRecipe } from '../../../../styled-system/recipes';
2
+ import { type FilterDateRangePickerProps } from '../FilterDateRangePicker.types';
3
+ import { type BasePickerProps } from '../../shared/BasePicker';
4
+ export type InternalDateRangePickerContentProps = {
5
+ classes: ReturnType<typeof filterDateRangePickerSlotRecipe>;
6
+ triggerRef: React.RefObject<HTMLButtonElement | null>;
7
+ dialogId: string;
8
+ label: string;
9
+ format: string;
10
+ disabled: boolean;
11
+ className: string | undefined;
12
+ clearButtonProps: FilterDateRangePickerProps['clearButtonProps'];
13
+ targetDOMNode: FilterDateRangePickerProps['targetDOMNode'];
14
+ isCalendarOpen: boolean;
15
+ toggleCalendar: () => void;
16
+ calendarLocale: NonNullable<BasePickerProps['calendarLocale']>;
17
+ };
@@ -0,0 +1,14 @@
1
+ import { type FilterDateRangePickerProps } from './FilterDateRangePicker.types';
2
+ /**
3
+ * FilterDateRangePicker component.
4
+ *
5
+ * A filter trigger component that allows selecting a date range via a dual-calendar picker.
6
+ * Displays the label alone when no range is selected, and "label:startDate〜endDate"
7
+ * after a range is applied. Conforms to the FilterSelectBox visual style.
8
+ *
9
+ * Note: The trigger markup intentionally does not reuse FilterTrigger because
10
+ * FilterTrigger hardcodes the DropdownIcon and adds a hidden <input> element,
11
+ * neither of which are appropriate here. FilterDateRangePicker uses CalendarIcon and
12
+ * has no form-field semantics.
13
+ */
14
+ export declare function FilterDateRangePicker({ label, value, defaultValue, onChange, open, onOpenStateChanged, format, clearButtonProps, disabled, className, targetDOMNode, minDate, maxDate, calendarLocale, }: FilterDateRangePickerProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useId, useRef } from 'react';
4
+ import { filterDateRangePickerSlotRecipe } from '../../../styled-system/recipes';
5
+ import { useDisclosure } from '../../utilities/state/useDisclosure';
6
+ import { BaseRangePickerProvider } from '../shared/BaseRangePicker/BaseRangePickerProvider';
7
+ import { InternalDateRangePickerContent } from './DateRangePickerContent/DateRangePickerContent';
8
+ const DEFAULT_FORMAT = 'YYYY/MM/DD';
9
+ /**
10
+ * FilterDateRangePicker component.
11
+ *
12
+ * A filter trigger component that allows selecting a date range via a dual-calendar picker.
13
+ * Displays the label alone when no range is selected, and "label:startDate〜endDate"
14
+ * after a range is applied. Conforms to the FilterSelectBox visual style.
15
+ *
16
+ * Note: The trigger markup intentionally does not reuse FilterTrigger because
17
+ * FilterTrigger hardcodes the DropdownIcon and adds a hidden <input> element,
18
+ * neither of which are appropriate here. FilterDateRangePicker uses CalendarIcon and
19
+ * has no form-field semantics.
20
+ */
21
+ export function FilterDateRangePicker({ label, value, defaultValue, onChange, open, onOpenStateChanged, format = DEFAULT_FORMAT, clearButtonProps, disabled = false, className, targetDOMNode, minDate, maxDate, calendarLocale = 'ja', }) {
22
+ const classes = filterDateRangePickerSlotRecipe();
23
+ const triggerRef = useRef(null);
24
+ const dialogId = useId();
25
+ const { isOpen: isCalendarOpen, open: openCalendar, close: closeCalendar, toggle: toggleCalendar, } = useDisclosure({
26
+ // disabled takes precedence: never open the calendar when the trigger is disabled.
27
+ value: disabled ? false : open,
28
+ // onToggle fires for Escape and outside-click; focus trigger on close.
29
+ onToggle: (nextOpen) => {
30
+ if (!nextOpen) {
31
+ triggerRef.current?.focus();
32
+ }
33
+ onOpenStateChanged?.(nextOpen);
34
+ },
35
+ // onClose fires for programmatic close (Apply/Cancel buttons); focus trigger there too.
36
+ onClose: () => {
37
+ triggerRef.current?.focus();
38
+ onOpenStateChanged?.(false);
39
+ },
40
+ });
41
+ return (_jsx(BaseRangePickerProvider, { value: value, defaultValue: defaultValue, format: format, isOpen: isCalendarOpen, open: openCalendar, close: closeCalendar, minDate: minDate, maxDate: maxDate, disabled: disabled, onChange: onChange, children: _jsx(InternalDateRangePickerContent, { classes: classes, triggerRef: triggerRef, dialogId: dialogId, label: label, format: format, disabled: disabled, className: className, clearButtonProps: clearButtonProps, targetDOMNode: targetDOMNode, isCalendarOpen: isCalendarOpen, toggleCalendar: toggleCalendar, calendarLocale: calendarLocale }) }));
42
+ }
@@ -0,0 +1,78 @@
1
+ import { type PopoverProps } from '../../Popover';
2
+ import { type BasePickerProps } from '../shared/BasePicker';
3
+ import { type DateRangeInput } from '../shared/BaseRangePicker/BaseRangePicker.types';
4
+ export type FilterDateRangePickerProps = {
5
+ /**
6
+ * The label to display for the filter.
7
+ */
8
+ label: string;
9
+ /**
10
+ * The selected date range value (controlled).
11
+ * Pass `[undefined, undefined]` to clear the selection in controlled mode.
12
+ */
13
+ value?: DateRangeInput;
14
+ /**
15
+ * The default selected date range value for uncontrolled usage.
16
+ */
17
+ defaultValue?: DateRangeInput;
18
+ /**
19
+ * Callback fired when the selected date range changes.
20
+ * Called with `[startDate, endDate]` when a range is applied, or `[undefined, undefined]` when cleared.
21
+ *
22
+ * @param dates - The new selected date range.
23
+ */
24
+ onChange?: (dates: DateRangeInput) => void;
25
+ /**
26
+ * Whether the calendar popover is open (controlled).
27
+ */
28
+ open?: boolean;
29
+ /**
30
+ * Callback fired when the open state of the calendar changes.
31
+ *
32
+ * @param open - The new open state.
33
+ */
34
+ onOpenStateChanged?: (open: boolean) => void;
35
+ /**
36
+ * The display format for the selected dates.
37
+ * Follows dayjs format tokens.
38
+ *
39
+ * @default 'YYYY/MM/DD'
40
+ */
41
+ format?: string;
42
+ /**
43
+ * Props for the clear button, shown automatically when a date range is selected.
44
+ *
45
+ * @property aria-label - The alternative text for the clear button. Default is '値をクリアする'.
46
+ */
47
+ clearButtonProps?: {
48
+ 'aria-label'?: string;
49
+ };
50
+ /**
51
+ * Whether the component is disabled.
52
+ *
53
+ * @default false
54
+ */
55
+ disabled?: boolean;
56
+ /**
57
+ * Additional class name applied to the trigger button.
58
+ */
59
+ className?: string;
60
+ /**
61
+ * The target DOM node to render the calendar popover into.
62
+ */
63
+ targetDOMNode?: PopoverProps['targetDOMNode'];
64
+ /**
65
+ * The minimum selectable date.
66
+ */
67
+ minDate?: Date;
68
+ /**
69
+ * The maximum selectable date.
70
+ */
71
+ maxDate?: Date;
72
+ /**
73
+ * The locale of the calendar (affects navigation labels and month display format).
74
+ *
75
+ * @default 'ja'
76
+ */
77
+ calendarLocale?: BasePickerProps['calendarLocale'];
78
+ };
@@ -0,0 +1,2 @@
1
+ export { FilterDateRangePicker } from './FilterDateRangePicker';
2
+ export type { FilterDateRangePickerProps } from './FilterDateRangePicker.types';
@@ -0,0 +1 @@
1
+ export { FilterDateRangePicker } from './FilterDateRangePicker';
@@ -0,0 +1,14 @@
1
+ import { type FilterMonthPickerProps } from './FilterMonthPicker.types';
2
+ /**
3
+ * FilterMonthPicker component.
4
+ *
5
+ * A filter trigger component that allows selecting a month via a month picker.
6
+ * Displays the label alone when no month is selected, and "label:formatted month"
7
+ * after a month is selected. Conforms to the FilterSelectBox visual style.
8
+ *
9
+ * Note: The trigger markup intentionally does not reuse FilterTrigger because
10
+ * FilterTrigger hardcodes the DropdownIcon and adds a hidden <input> element,
11
+ * neither of which are appropriate here. FilterMonthPicker uses CalendarIcon and
12
+ * has no form-field semantics.
13
+ */
14
+ export declare function FilterMonthPicker({ label, value, defaultValue, onChange, open, onOpenStateChanged, format, clearButtonProps, disabled, className, targetDOMNode, checkDisabledMonth, minMonth, maxMonth, calendarLocale, }: FilterMonthPickerProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,75 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useId, useRef, useState } from 'react';
4
+ import { BasePickerProvider } from '../shared/BasePicker/BasePickerProvider';
5
+ import { useDisclosure } from '../../utilities/state/useDisclosure';
6
+ import { dayjs } from '../../utilities/date/dayjs';
7
+ import { filterMonthPickerSlotRecipe } from '../../../styled-system/recipes';
8
+ import { InternalMonthPickerPopover } from './MonthPickerPopover/MonthPickerPopover';
9
+ const DEFAULT_FORMAT = 'YYYY/MM';
10
+ /**
11
+ * FilterMonthPicker component.
12
+ *
13
+ * A filter trigger component that allows selecting a month via a month picker.
14
+ * Displays the label alone when no month is selected, and "label:formatted month"
15
+ * after a month is selected. Conforms to the FilterSelectBox visual style.
16
+ *
17
+ * Note: The trigger markup intentionally does not reuse FilterTrigger because
18
+ * FilterTrigger hardcodes the DropdownIcon and adds a hidden <input> element,
19
+ * neither of which are appropriate here. FilterMonthPicker uses CalendarIcon and
20
+ * has no form-field semantics.
21
+ */
22
+ export function FilterMonthPicker({ label, value, defaultValue, onChange, open, onOpenStateChanged, format = DEFAULT_FORMAT, clearButtonProps, disabled = false, className, targetDOMNode, checkDisabledMonth, minMonth, maxMonth, calendarLocale = 'ja', }) {
23
+ const classes = filterMonthPickerSlotRecipe();
24
+ const triggerRef = useRef(null);
25
+ const dialogId = useId();
26
+ const [internalValue, setInternalValue] = useState(defaultValue);
27
+ const currentValue = value !== undefined ? value : internalValue;
28
+ // When a controlled value transitions back to undefined, clear any stale internal value
29
+ // so the component doesn't show a previously-selected uncontrolled month.
30
+ const previousValueRef = useRef(value);
31
+ useEffect(() => {
32
+ const previousValue = previousValueRef.current;
33
+ previousValueRef.current = value;
34
+ if (previousValue !== undefined && value === undefined) {
35
+ setInternalValue(undefined);
36
+ }
37
+ }, [value]);
38
+ const { isOpen: isPickerOpen, toggle: togglePicker, close: closePicker, } = useDisclosure({
39
+ // disabled takes precedence: never open the picker when the trigger is disabled.
40
+ value: disabled ? false : open,
41
+ // onToggle fires for Escape and outside-click (via togglePicker); focus trigger on close.
42
+ onToggle: (nextOpen) => {
43
+ if (!nextOpen) {
44
+ triggerRef.current?.focus();
45
+ }
46
+ onOpenStateChanged?.(nextOpen);
47
+ },
48
+ // onClose fires for programmatic close (month selection); focus trigger there too.
49
+ onClose: () => {
50
+ triggerRef.current?.focus();
51
+ },
52
+ });
53
+ const handleValueChange = useCallback((date) => {
54
+ if (value === undefined) {
55
+ setInternalValue(date);
56
+ }
57
+ onChange?.(date);
58
+ // closePicker() fires useDisclosure.onClose (returns focus to trigger) but intentionally
59
+ // does NOT fire onToggle. The explicit onOpenStateChanged call below is the single path
60
+ // for notifying the parent when a month selection closes the popover.
61
+ closePicker();
62
+ onOpenStateChanged?.(false);
63
+ }, [value, onChange, closePicker, onOpenStateChanged]);
64
+ const handleClear = useCallback((event) => {
65
+ event.stopPropagation();
66
+ if (value === undefined) {
67
+ setInternalValue(undefined);
68
+ }
69
+ onChange?.();
70
+ triggerRef.current?.focus();
71
+ }, [value, onChange, triggerRef]);
72
+ const formattedValue = currentValue ? dayjs(currentValue).format(format) : undefined;
73
+ const hasValue = !!formattedValue;
74
+ return (_jsx(BasePickerProvider, { value: currentValue, baseFormat: "YYYY-MM", children: _jsx(InternalMonthPickerPopover, { currentValue: currentValue, defaultValue: defaultValue, isPickerOpen: isPickerOpen, togglePicker: togglePicker, handleValueChange: handleValueChange, handleClear: handleClear, disabled: disabled, className: className, classes: classes, formattedValue: formattedValue, hasValue: hasValue, label: label, clearButtonProps: clearButtonProps, checkDisabledMonth: checkDisabledMonth, minMonth: minMonth, maxMonth: maxMonth, calendarLocale: calendarLocale, triggerRef: triggerRef, targetDOMNode: targetDOMNode, dialogId: dialogId }) }));
75
+ }
@@ -0,0 +1,83 @@
1
+ import { type PopoverProps } from '../../Popover';
2
+ import { type BasePickerProps } from '../shared/BasePicker';
3
+ export type FilterMonthPickerProps = {
4
+ /**
5
+ * The label to display for the filter.
6
+ */
7
+ label: string;
8
+ /**
9
+ * The selected month value (controlled).
10
+ */
11
+ value?: Date;
12
+ /**
13
+ * The default selected month value for uncontrolled usage.
14
+ */
15
+ defaultValue?: Date;
16
+ /**
17
+ * Callback fired when the selected month changes.
18
+ *
19
+ * @param value - The new selected month, or undefined when cleared.
20
+ */
21
+ onChange?: (value?: Date) => void;
22
+ /**
23
+ * Whether the month 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 the selected month.
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 month 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 picker popover into.
59
+ */
60
+ targetDOMNode?: PopoverProps['targetDOMNode'];
61
+ /**
62
+ * Function to check if a month should be disabled in the picker.
63
+ *
64
+ * @param date - The date to check (first day of the month).
65
+ *
66
+ * @returns true if the month should be disabled.
67
+ */
68
+ checkDisabledMonth?: (date: Date) => boolean;
69
+ /**
70
+ * The minimum selectable month.
71
+ */
72
+ minMonth?: Date;
73
+ /**
74
+ * The maximum selectable month.
75
+ */
76
+ maxMonth?: Date;
77
+ /**
78
+ * The locale of the calendar (affects navigation labels and month display format).
79
+ *
80
+ * @default 'ja'
81
+ */
82
+ calendarLocale?: BasePickerProps['calendarLocale'];
83
+ };
@@ -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;