@moneyforward/mfui-components 3.4.0 → 3.6.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.
@@ -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, ...restProps }: MonthRangePickerProps): import("react/jsx-runtime").JSX.Element;
17
+ export declare function MonthRangePicker({ format, minMonth, maxMonth, startInputProps, endInputProps, initialDisplayedMonths, ...restProps }: MonthRangePickerProps): import("react/jsx-runtime").JSX.Element;
@@ -29,8 +29,17 @@ import { MonthRangePickerPanel } from './MonthRangePickerPanel';
29
29
  * />
30
30
  * ```
31
31
  */
32
- export function MonthRangePicker({ format = 'YYYY/MM', minMonth, maxMonth, startInputProps = {}, endInputProps = {}, ...restProps }) {
33
- return (_jsx(BaseRangePicker, { ...restProps, format: format, startInputProps: {
32
+ export function MonthRangePicker({ format = 'YYYY/MM', minMonth, maxMonth, startInputProps = {}, endInputProps = {}, initialDisplayedMonths, ...restProps }) {
33
+ // MonthRangePicker panels are grouped by year (left = viewingYear, right = viewingYear + 1).
34
+ // When `previousAndCurrent` is requested, we must shift by a full year so the left panel
35
+ // shows the previous year instead of the current year.
36
+ const initialViewingDate = initialDisplayedMonths === 'previousAndCurrent' && !restProps.value && !restProps.defaultValue
37
+ ? (() => {
38
+ const today = new Date();
39
+ return new Date(today.getFullYear() - 1, 0, 1);
40
+ })()
41
+ : undefined;
42
+ return (_jsx(BaseRangePicker, { ...restProps, format: format, initialDisplayedMonths: initialDisplayedMonths, initialViewingDate: initialViewingDate, startInputProps: {
34
43
  placeholder: '開始月',
35
44
  ...startInputProps,
36
45
  }, endInputProps: {
@@ -3,4 +3,4 @@ import { type BaseRangePickerProps } from './BaseRangePicker.types';
3
3
  * BaseRangePicker component
4
4
  * A generic component for selecting a range of dates with configurable format
5
5
  */
6
- export declare function BaseRangePicker({ value, defaultValue, disabled, invalid, targetDOMNode, onChange, format, onOpenStateChanged, disableAutoOpen, onBlur, allowedPlacements, enableViewportConstraint, enableClearButton, clearButtonProps, minDate, maxDate, enableAutoUnmount, minWidth, renderPopoverContent, startInputProps, endInputProps, calendarLocale, }: BaseRangePickerProps): import("react/jsx-runtime").JSX.Element;
6
+ export declare function BaseRangePicker({ value, defaultValue, disabled, invalid, targetDOMNode, onChange, format, onOpenStateChanged, disableAutoOpen, onBlur, allowedPlacements, enableViewportConstraint, enableClearButton, clearButtonProps, minDate, maxDate, enableAutoUnmount, minWidth, renderPopoverContent, startInputProps, endInputProps, calendarLocale, initialDisplayedMonths, initialViewingDate, }: BaseRangePickerProps): import("react/jsx-runtime").JSX.Element;
@@ -61,12 +61,12 @@ function InternalBaseRangePicker({ startInputRef, endInputRef, disabled, invalid
61
61
  * BaseRangePicker component
62
62
  * A generic component for selecting a range of dates with configurable format
63
63
  */
64
- export function BaseRangePicker({ value, defaultValue, disabled, invalid, targetDOMNode, onChange, format = 'YYYY/MM/DD', onOpenStateChanged, disableAutoOpen = false, onBlur, allowedPlacements, enableViewportConstraint, enableClearButton = false, clearButtonProps, minDate, maxDate, enableAutoUnmount = true, minWidth, renderPopoverContent, startInputProps, endInputProps, calendarLocale = 'ja', }) {
64
+ export function BaseRangePicker({ value, defaultValue, disabled, invalid, targetDOMNode, onChange, format = 'YYYY/MM/DD', onOpenStateChanged, disableAutoOpen = false, onBlur, allowedPlacements, enableViewportConstraint, enableClearButton = false, clearButtonProps, minDate, maxDate, enableAutoUnmount = true, minWidth, renderPopoverContent, startInputProps, endInputProps, calendarLocale = 'ja', initialDisplayedMonths, initialViewingDate, }) {
65
65
  const { isOpen, open, close } = useDisclosure({ value: false });
66
66
  const startInputRef = useRef(null);
67
67
  const endInputRef = useRef(null);
68
68
  // Default renderPopoverContent implementation for backward compatibility
69
69
  const defaultRenderPopoverContent = useCallback(() => _jsx(BaseRangePickerPopover, { calendarLocale: calendarLocale }), [calendarLocale]);
70
70
  const finalRenderPopoverContent = renderPopoverContent ?? defaultRenderPopoverContent;
71
- return (_jsx(BaseRangePickerProvider, { value: value, defaultValue: defaultValue, disabled: disabled, format: format, isOpen: isOpen, open: open, close: close, disableAutoOpen: disableAutoOpen, minDate: minDate, maxDate: maxDate, onChange: onChange, children: _jsx(InternalBaseRangePicker, { startInputRef: startInputRef, endInputRef: endInputRef, disabled: disabled, invalid: invalid, format: format, enableClearButton: enableClearButton, clearButtonProps: clearButtonProps, targetDOMNode: targetDOMNode, allowedPlacements: allowedPlacements, enableViewportConstraint: enableViewportConstraint, enableAutoUnmount: enableAutoUnmount, minWidth: minWidth, startInputProps: startInputProps, endInputProps: endInputProps, isOpen: isOpen, open: open, close: close, renderPopoverContent: finalRenderPopoverContent, calendarLocale: calendarLocale, onBlur: onBlur, onOpenStateChanged: onOpenStateChanged }) }));
71
+ return (_jsx(BaseRangePickerProvider, { value: value, defaultValue: defaultValue, disabled: disabled, format: format, isOpen: isOpen, open: open, close: close, disableAutoOpen: disableAutoOpen, minDate: minDate, maxDate: maxDate, initialDisplayedMonths: initialDisplayedMonths, initialViewingDate: initialViewingDate, onChange: onChange, children: _jsx(InternalBaseRangePicker, { startInputRef: startInputRef, endInputRef: endInputRef, disabled: disabled, invalid: invalid, format: format, enableClearButton: enableClearButton, clearButtonProps: clearButtonProps, targetDOMNode: targetDOMNode, allowedPlacements: allowedPlacements, enableViewportConstraint: enableViewportConstraint, enableAutoUnmount: enableAutoUnmount, minWidth: minWidth, startInputProps: startInputProps, endInputProps: endInputProps, isOpen: isOpen, open: open, close: close, renderPopoverContent: finalRenderPopoverContent, calendarLocale: calendarLocale, onBlur: onBlur, onOpenStateChanged: onOpenStateChanged }) }));
72
72
  }
@@ -270,4 +270,32 @@ export type BaseRangePickerProps = {
270
270
  * @default 'ja'
271
271
  */
272
272
  calendarLocale?: BasePickerProps['calendarLocale'];
273
+ /**
274
+ * Controls which months are initially displayed in the two-panel calendar view.
275
+ * - `'currentAndNext'`: Shows current month on the left and next month on the right (default behavior).
276
+ * - `'previousAndCurrent'`: Shows previous month on the left and current month on the right.
277
+ * Useful for log date range selection where showing future months is not meaningful.
278
+ *
279
+ * This prop only affects the initial view when no `value` or `defaultValue` is provided.
280
+ * When a `value` or `defaultValue` is set, the calendar will show the months containing the selected dates.
281
+ *
282
+ * @default 'currentAndNext'
283
+ *
284
+ * @example
285
+ * ```tsx
286
+ * // Show previous month + current month for log date selection
287
+ * <DateRangePicker initialDisplayedMonths="previousAndCurrent" />
288
+ * ```
289
+ */
290
+ initialDisplayedMonths?: 'currentAndNext' | 'previousAndCurrent';
291
+ /**
292
+ * Overrides the computed initial viewing date derived from `initialDisplayedMonths`.
293
+ * Use this when the component operates at a different granularity than months
294
+ * (e.g., MonthRangePicker uses years for panel navigation).
295
+ *
296
+ * This prop only affects the initial view when no `value` or `defaultValue` is provided.
297
+ *
298
+ * @internal
299
+ */
300
+ initialViewingDate?: Date;
273
301
  };
@@ -63,7 +63,7 @@ function autoCompletePartialRange(dates) {
63
63
  }
64
64
  return dates;
65
65
  }
66
- const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false, onChange, format = 'YYYY/MM/DD', isOpen = false, open = noop, close = noop, disableAutoOpen, minDate, maxDate, }) => {
66
+ const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false, onChange, format = 'YYYY/MM/DD', isOpen = false, open = noop, close = noop, disableAutoOpen, minDate, maxDate, initialDisplayedMonths = 'currentAndNext', initialViewingDate, }) => {
67
67
  const [dates, setDates] = useTransformedState(defaultValue ?? [undefined, undefined], normalizeToStartOfDay);
68
68
  const [dateStrings, setDateStrings] = useState(() => {
69
69
  const [startDate, endDate] = value ?? defaultValue ?? [undefined, undefined];
@@ -73,7 +73,12 @@ const useBaseRangePickerContextValue = ({ value, defaultValue, disabled = false,
73
73
  const [startDateString, endDateString] = dateStrings;
74
74
  // Add viewingMonth state
75
75
  const today = useMemo(() => new Date(), []);
76
- const [viewingMonth, setViewingMonth] = useTransformedState(value?.[0] ?? defaultValue?.[0] ?? new Date(today.getFullYear(), today.getMonth(), 1), normalizeToFirstOfMonth);
76
+ const [viewingMonth, setViewingMonth] = useTransformedState(value?.[0] ??
77
+ defaultValue?.[0] ??
78
+ initialViewingDate ??
79
+ (initialDisplayedMonths === 'previousAndCurrent'
80
+ ? new Date(today.getFullYear(), today.getMonth() - 1, 1)
81
+ : new Date(today.getFullYear(), today.getMonth(), 1)), normalizeToFirstOfMonth);
77
82
  // --- Selection/Preview State and Logic ---
78
83
  const [selecting, setSelecting] = useState('start');
79
84
  const [temporaryStart, setTemporaryStart] = useState(startDate);
@@ -77,4 +77,24 @@ export type BaseRangePickerProviderProps = {
77
77
  * @default undefined
78
78
  */
79
79
  maxDate?: Date;
80
+ /**
81
+ * Controls which months are initially displayed in the two-panel calendar view.
82
+ * - `'currentAndNext'`: Shows current month on the left and next month on the right (default behavior).
83
+ * - `'previousAndCurrent'`: Shows previous month on the left and current month on the right.
84
+ *
85
+ * This prop only affects the initial view when no `value` or `defaultValue` is provided.
86
+ *
87
+ * @default 'currentAndNext'
88
+ */
89
+ initialDisplayedMonths?: 'currentAndNext' | 'previousAndCurrent';
90
+ /**
91
+ * Overrides the computed initial viewing date derived from `initialDisplayedMonths`.
92
+ * Use this when the component operates at a different granularity than months
93
+ * (e.g., MonthRangePicker uses years).
94
+ *
95
+ * This prop only affects the initial view when no `value` or `defaultValue` is provided.
96
+ *
97
+ * @internal
98
+ */
99
+ initialViewingDate?: Date;
80
100
  };
@@ -1,6 +1,10 @@
1
1
  /**
2
- * The HelpMessage component
3
- * This component switches the variants of looks depends on the props: messageType.
2
+ * The HelpMessage component displays contextual messages with an icon and color
3
+ * that reflect the message's intent. Supports four variants via `messageType`:
4
+ * - `neutral` (default): informational, uses IconInfo. Icon is hidden by default; opt in with `showIcon`. No ARIA role.
5
+ * - `error`: validation or system error, uses IconError. Sets `role="alert"` — announces immediately to screen readers.
6
+ * - `caution`: warning or advisory, uses IconCaution. No ARIA role by default — add `role="status"` or `aria-live="polite"` when rendered dynamically.
7
+ * - `success`: confirmation or positive feedback, uses IconApproval. Sets `role="status"` — announces politely when the user is idle.
4
8
  *
5
9
  * Also extends the props of the `<div>` element.
6
10
  *
@@ -8,6 +12,7 @@
8
12
  */
9
13
  export declare const HelpMessage: import("react").ForwardRefExoticComponent<{
10
14
  messageType?: import("../../styled-system/recipes").HelpMessageRecipeVariant["messageType"];
15
+ showIcon?: boolean;
11
16
  messageAlign?: "left" | "center";
12
17
  children?: import("..").TypographyProps["children"];
13
18
  } & Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
@@ -1,20 +1,38 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useId } from 'react';
3
- import { Error } from '@moneyforward/mfui-icons-react';
3
+ import { Approval, Caution, Error, Info } from '@moneyforward/mfui-icons-react';
4
4
  import { cx } from '../../styled-system/css';
5
5
  import { helpMessageRecipe } from '../../styled-system/recipes';
6
6
  import { Typography } from '../Typography';
7
+ const messageTypeRoleMap = {
8
+ neutral: undefined,
9
+ error: 'alert',
10
+ caution: undefined,
11
+ success: 'status',
12
+ };
13
+ const messageTypeIconMap = {
14
+ neutral: Info,
15
+ error: Error,
16
+ caution: Caution,
17
+ success: Approval,
18
+ };
7
19
  /**
8
- * The HelpMessage component
9
- * This component switches the variants of looks depends on the props: messageType.
20
+ * The HelpMessage component displays contextual messages with an icon and color
21
+ * that reflect the message's intent. Supports four variants via `messageType`:
22
+ * - `neutral` (default): informational, uses IconInfo. Icon is hidden by default; opt in with `showIcon`. No ARIA role.
23
+ * - `error`: validation or system error, uses IconError. Sets `role="alert"` — announces immediately to screen readers.
24
+ * - `caution`: warning or advisory, uses IconCaution. No ARIA role by default — add `role="status"` or `aria-live="polite"` when rendered dynamically.
25
+ * - `success`: confirmation or positive feedback, uses IconApproval. Sets `role="status"` — announces politely when the user is idle.
10
26
  *
11
27
  * Also extends the props of the `<div>` element.
12
28
  *
13
29
  * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div
14
30
  */
15
- export const HelpMessage = forwardRef(({ messageType = 'neutral', messageAlign = 'left', children, className, ...props }, ref) => {
31
+ export const HelpMessage = forwardRef(({ messageType = 'neutral', messageAlign = 'left', showIcon, children, className, ...props }, ref) => {
16
32
  const classes = helpMessageRecipe({ messageType, messageAlign });
17
33
  const id = useId();
18
34
  const ariaLabelledByContentId = `help-message-content-${id}`;
19
- return (_jsxs("div", { ref: ref, role: messageType === 'error' ? 'alert' : undefined, "aria-labelledby": ariaLabelledByContentId, ...props, className: cx(classes.root, 'mfui-HelpMessage__root', className), children: [messageType === 'error' ? _jsx(Error, { "aria-hidden": true, className: cx(classes.icon, 'mfui-HelpMessage__icon') }) : null, _jsx(Typography, { id: ariaLabelledByContentId, variant: "helpMessage", className: cx(classes.message, 'mfui-HelpMessage__message'), children: children })] }));
35
+ const shouldShowIcon = showIcon ?? messageType !== 'neutral';
36
+ const Icon = messageTypeIconMap[messageType];
37
+ return (_jsxs("div", { ref: ref, role: messageTypeRoleMap[messageType], "aria-labelledby": ariaLabelledByContentId, ...props, className: cx(classes.root, 'mfui-HelpMessage__root', className), children: [shouldShowIcon ? _jsx(Icon, { "aria-hidden": true, className: cx(classes.icon, 'mfui-HelpMessage__icon') }) : null, _jsx(Typography, { id: ariaLabelledByContentId, variant: "helpMessage", className: cx(classes.message, 'mfui-HelpMessage__message'), children: children })] }));
20
38
  });
@@ -6,13 +6,20 @@ import { type TypographyProps } from '../Typography';
6
6
  */
7
7
  export type HelpMessageProps = {
8
8
  /**
9
- * The type of message to display.
10
- * The color of the message will change based on this value.
11
- * The error icon will be displayed next to the message if this value is 'error'.
9
+ * The intent of the message. Controls the icon and color.
10
+ * - `neutral`: informational (IconInfo)
11
+ * - `error`: validation or system error (IconError). Sets `role="alert"` for screen readers.
12
+ * - `caution`: warning or advisory (IconCaution). No ARIA role is set by default — add `role="status"` or `aria-live="polite"` when rendering dynamically.
13
+ * - `success`: confirmation or positive feedback (IconApproval)
12
14
  *
13
15
  * @default 'neutral'
14
16
  */
15
17
  messageType?: HelpMessageRecipeVariant['messageType'];
18
+ /**
19
+ * Whether to display the icon next to the message.
20
+ * Defaults to `false` for `neutral` (for backward compatibility) and `true` for all other types.
21
+ */
22
+ showIcon?: boolean;
16
23
  /**
17
24
  * The alignment of the message.
18
25
  *
@@ -65,6 +65,12 @@ function NavigationLabelGroup({ label, labelIcon, locked, lockIconProps, statusS
65
65
  */
66
66
  function NavigationLink({ navigationItem, customLinkComponent, classes, }) {
67
67
  const { label, href, isCurrent, isExternal, labelIcon, statusSlot, locked, lockIconProps } = navigationItem;
68
+ if (navigationItem.children === undefined && navigationItem.onClick) {
69
+ const { onClick } = navigationItem;
70
+ return (_jsx(FocusIndicator, { children: _jsx("button", { type: "button", className: cx(classes.link, 'mfui-SubNavigation__link'), "aria-current": isCurrent ? 'page' : undefined, onClick: () => {
71
+ onClick(navigationItem.href);
72
+ }, children: _jsx(NavigationLabelGroup, { label, labelIcon, locked, lockIconProps, statusSlot, classes }) }) }));
73
+ }
68
74
  if (customLinkComponent && !isExternal) {
69
75
  const Tag = customLinkComponent;
70
76
  return (_jsx(FocusIndicator, { children: _jsx(Tag, { href: href, className: cx(classes.link, 'mfui-SubNavigation__link'), "aria-current": isCurrent ? 'page' : undefined, children: _jsx(NavigationLabelGroup, { label, labelIcon, locked, lockIconProps, statusSlot, classes }) }) }));
@@ -68,6 +68,14 @@ type ChildlessNavigationItem = BaseNavigationItem & {
68
68
  * The path or URL to the linked page.
69
69
  */
70
70
  href: string;
71
+ /**
72
+ * If provided, the item is rendered as a button instead of a link.
73
+ * The handler is called with the item's `href` value as an argument.
74
+ *
75
+ * Use this for state-based navigation patterns where you want to update
76
+ * application state on click without performing browser navigation.
77
+ */
78
+ onClick?: (href: string) => void;
71
79
  };
72
80
  /**
73
81
  * The navigation item that has nested children.
@@ -4,7 +4,7 @@ import type { DistributiveOmit, Pretty } from '../types/system-types';
4
4
 
5
5
  interface HelpMessageRecipeVariant {
6
6
  messageAlign: "left" | "center"
7
- messageType: "neutral" | "error"
7
+ messageType: "neutral" | "error" | "caution" | "success"
8
8
  }
9
9
 
10
10
  type HelpMessageRecipeVariantMap = {
@@ -38,7 +38,9 @@ export const helpMessageRecipe = /* @__PURE__ */ Object.assign(helpMessageRecipe
38
38
  ],
39
39
  "messageType": [
40
40
  "neutral",
41
- "error"
41
+ "error",
42
+ "caution",
43
+ "success"
42
44
  ]
43
45
  },
44
46
  splitVariantProps(props) {
package/dist/styles.css CHANGED
@@ -2947,13 +2947,18 @@
2947
2947
  }
2948
2948
 
2949
2949
  .mfui-ljuOJV {
2950
+ border: none;
2950
2951
  text-decoration: none;
2951
2952
  padding-block: var(--mfui-spacing-mfui\.size\.padding\.sub-navigation\.vertical\.comfort);
2952
2953
  padding-inline: var(--mfui-spacing-mfui\.size\.padding\.sub-navigation\.horizontal\.comfort);
2953
2954
  border-radius: var(--mfui-radii-mfui\.size\.radius\.control-component\.comfort);
2955
+ background-color: transparent;
2956
+ cursor: pointer;
2957
+ text-align: left;
2954
2958
  display: flex;
2955
2959
  align-items: flex-start;
2956
2960
  color: var(--mfui-colors-mfui\.color\.base\.content\.none);
2961
+ width: 100%;
2957
2962
  }
2958
2963
 
2959
2964
  .mfui-ljuOJV[aria-current=page] {
@@ -5596,6 +5601,14 @@
5596
5601
  color: var(--mfui-colors-mfui\.color\.signal-red\.content\.none);
5597
5602
  }
5598
5603
 
5604
+ .mfui-LuZio {
5605
+ color: var(--mfui-colors-mfui\.color\.signal-yellow\.content\.none);
5606
+ }
5607
+
5608
+ .mfui-foXPig {
5609
+ color: var(--mfui-colors-mfui\.color\.signal-green\.content\.none);
5610
+ }
5611
+
5599
5612
  .mfui-dTaPGi {
5600
5613
  display: inline-flex;
5601
5614
  align-items: center;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moneyforward/mfui-components",
3
- "version": "3.4.0",
3
+ "version": "3.6.0",
4
4
  "description": "React UI Component Library for all Money Forward products",
5
5
  "repository": {
6
6
  "type": "git",
@@ -47,7 +47,7 @@
47
47
  "jsdom": "26.1.0",
48
48
  "react": "19.2.3",
49
49
  "react-dom": "19.2.3",
50
- "storybook": "9.1.17",
50
+ "storybook": "9.1.19",
51
51
  "ts-node": "10.9.2",
52
52
  "tsx": "4.21.0",
53
53
  "vitest": "3.2.4"
@@ -60,8 +60,8 @@
60
60
  "@floating-ui/react-dom": "^2.1.2",
61
61
  "@tanstack/react-virtual": "^3.13.18",
62
62
  "dayjs": "^1.11.13",
63
- "@moneyforward/mfui-design-tokens": "^3.0.0",
64
- "@moneyforward/mfui-icons-react": "^3.0.0"
63
+ "@moneyforward/mfui-icons-react": "^3.0.0",
64
+ "@moneyforward/mfui-design-tokens": "^3.0.0"
65
65
  },
66
66
  "scripts": {
67
67
  "prepare:panda": "panda codegen",