From 50b5bae16f6c730ce7e307778cf8031f3e4b2075 Mon Sep 17 00:00:00 2001 From: ayushnirwal <53055971+ayushnirwal@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:30:20 +0530 Subject: [PATCH 1/3] feat: add monthpicker component Signed-off-by: ayushnirwal <53055971+ayushnirwal@users.noreply.github.com> --- .../src/components/monthPicker/index.ts | 3 + .../monthPicker/monthPicker.stories.tsx | 79 +++++++++ .../components/monthPicker/monthPicker.tsx | 152 ++++++++++++++++++ .../src/components/monthPicker/types.ts | 19 +++ 4 files changed, 253 insertions(+) create mode 100644 packages/frappe-ui-react/src/components/monthPicker/index.ts create mode 100644 packages/frappe-ui-react/src/components/monthPicker/monthPicker.stories.tsx create mode 100644 packages/frappe-ui-react/src/components/monthPicker/monthPicker.tsx create mode 100644 packages/frappe-ui-react/src/components/monthPicker/types.ts diff --git a/packages/frappe-ui-react/src/components/monthPicker/index.ts b/packages/frappe-ui-react/src/components/monthPicker/index.ts new file mode 100644 index 00000000..671d7dc3 --- /dev/null +++ b/packages/frappe-ui-react/src/components/monthPicker/index.ts @@ -0,0 +1,3 @@ +export { default as MonthPicker } from "./monthPicker"; +export * from "./monthPicker"; +export * from "./types"; diff --git a/packages/frappe-ui-react/src/components/monthPicker/monthPicker.stories.tsx b/packages/frappe-ui-react/src/components/monthPicker/monthPicker.stories.tsx new file mode 100644 index 00000000..86ff895c --- /dev/null +++ b/packages/frappe-ui-react/src/components/monthPicker/monthPicker.stories.tsx @@ -0,0 +1,79 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; + +import MonthPicker from "./monthPicker"; +import type { MonthPickerProps } from "./types"; + +export default { + title: "Components/MonthPicker", + component: MonthPicker, + tags: ["autodocs"], + argTypes: { + value: { + control: "text", + description: + "Selected month value in 'Month Year' format (e.g., 'January 2026').", + }, + placeholder: { + control: "text", + description: "Placeholder text for the MonthPicker button.", + }, + className: { + control: "text", + description: "CSS class names to apply to the button.", + }, + placement: { + control: "select", + options: [ + "top-start", + "top", + "top-end", + "bottom-start", + "bottom", + "bottom-end", + "left-start", + "left", + "left-end", + "right-start", + "right", + "right-end", + ], + description: "Popover placement relative to the target.", + }, + onChange: { + action: "onChange", + description: "Callback fired when the month value changes.", + }, + }, + parameters: { docs: { source: { type: "dynamic" } }, layout: "centered" }, +} as Meta; + +type Story = StoryObj; + +export const Default: Story = { + render: (args) => { + const [value, setValue] = useState(""); + return ( +
+ +
+ ); + }, + args: { + placeholder: "Select month", + }, +}; + +export const FitWidth: Story = { + render: (args) => { + const [value, setValue] = useState(""); + return ( +
+ +
+ ); + }, + args: { + placeholder: "Select month", + }, +}; diff --git a/packages/frappe-ui-react/src/components/monthPicker/monthPicker.tsx b/packages/frappe-ui-react/src/components/monthPicker/monthPicker.tsx new file mode 100644 index 00000000..ae9c93b6 --- /dev/null +++ b/packages/frappe-ui-react/src/components/monthPicker/monthPicker.tsx @@ -0,0 +1,152 @@ +/** + * External dependencies. + */ +import { useCallback, useMemo, useState } from "react"; +import { ChevronLeft, ChevronRight, Calendar } from "lucide-react"; +import clsx from "clsx"; + +/** + * Internal dependencies. + */ +import { dayjs } from "../../utils/dayjs"; +import { Popover } from "../popover"; +import { Button } from "../button"; +import type { MonthPickerProps } from "./types"; + +const MONTHS = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +]; + +const parseValue = (val: string | undefined) => { + if (!val) return null; + const parsed = dayjs(val, "MMMM YYYY"); + if (parsed.isValid()) { + return { month: parsed.format("MMMM"), year: parsed.year() }; + } + return null; +}; + +const MonthPicker = ({ + value, + placeholder = "Select month", + className, + placement, + onChange, +}: MonthPickerProps) => { + const [open, setOpen] = useState(false); + const [viewMode, setViewMode] = useState<"month" | "year">("month"); + const [currentYear, setCurrentYear] = useState( + parseValue(value)?.year ?? new Date().getFullYear() + ); + + const yearRangeStart = useMemo( + () => currentYear - (currentYear % 12), + [currentYear] + ); + + const yearRange = useMemo( + () => Array.from({ length: 12 }, (_, i) => yearRangeStart + i), + [yearRangeStart] + ); + + const pickerList = useMemo( + () => (viewMode === "year" ? yearRange : MONTHS), + [viewMode, yearRange] + ); + + const toggleViewMode = useCallback(() => { + setViewMode((prevMode) => (prevMode === "month" ? "year" : "month")); + }, []); + + const prev = useCallback(() => { + setCurrentYear((y) => (viewMode === "year" ? y - 12 : y - 1)); + }, [viewMode]); + + const next = useCallback(() => { + setCurrentYear((y) => (viewMode === "year" ? y + 12 : y + 1)); + }, [viewMode]); + + const handleOpenChange = useCallback((isOpen: boolean) => { + setOpen(isOpen); + if (!isOpen) setViewMode("month"); + }, []); + + const handleOnClick = useCallback( + (v: string | number) => { + const parts = (value || "").split(" "); + const indexToModify = viewMode === "year" ? 1 : 0; + parts[indexToModify] = String(v); + const newValue = parts.join(" "); + onChange?.(newValue); + }, + [value, viewMode, onChange] + ); + + return ( + ( + + )} + popoverClass="w-min!" + body={() => ( +
+
+ + + + + +
+ +
+ +
+ {pickerList.map((month, index) => ( + + ))} +
+
+ )} + /> + ); +}; + +export default MonthPicker; diff --git a/packages/frappe-ui-react/src/components/monthPicker/types.ts b/packages/frappe-ui-react/src/components/monthPicker/types.ts new file mode 100644 index 00000000..bee6d040 --- /dev/null +++ b/packages/frappe-ui-react/src/components/monthPicker/types.ts @@ -0,0 +1,19 @@ +export interface MonthPickerProps { + value?: string; + placeholder?: string; + className?: string; + placement?: + | "top-start" + | "top" + | "top-end" + | "bottom-start" + | "bottom" + | "bottom-end" + | "left-start" + | "left" + | "left-end" + | "right-start" + | "right" + | "right-end"; + onChange?: (value: string) => void; +} From 8ac7a4a27118cc3ba675cc4476a10ee5140c37bd Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Wed, 11 Feb 2026 19:56:33 +0530 Subject: [PATCH 2/3] Improve MonthPicker component --- .../frappe-ui-react/src/components/index.ts | 1 + .../src/components/monthPicker/index.ts | 1 - .../components/monthPicker/monthPicker.tsx | 95 +++++++++++-------- .../src/components/monthPicker/types.ts | 21 ++-- 4 files changed, 66 insertions(+), 52 deletions(-) diff --git a/packages/frappe-ui-react/src/components/index.ts b/packages/frappe-ui-react/src/components/index.ts index 78e22fca..e2823c1e 100644 --- a/packages/frappe-ui-react/src/components/index.ts +++ b/packages/frappe-ui-react/src/components/index.ts @@ -21,6 +21,7 @@ export * from "./gridLayout"; export * from "./hooks"; export * from "./label"; export * from "./listview"; +export * from "./monthPicker"; export * from "./multiSelect"; export * from "./password"; export * from "./progress"; diff --git a/packages/frappe-ui-react/src/components/monthPicker/index.ts b/packages/frappe-ui-react/src/components/monthPicker/index.ts index 671d7dc3..66068c88 100644 --- a/packages/frappe-ui-react/src/components/monthPicker/index.ts +++ b/packages/frappe-ui-react/src/components/monthPicker/index.ts @@ -1,3 +1,2 @@ export { default as MonthPicker } from "./monthPicker"; -export * from "./monthPicker"; export * from "./types"; diff --git a/packages/frappe-ui-react/src/components/monthPicker/monthPicker.tsx b/packages/frappe-ui-react/src/components/monthPicker/monthPicker.tsx index ae9c93b6..e01a4e28 100644 --- a/packages/frappe-ui-react/src/components/monthPicker/monthPicker.tsx +++ b/packages/frappe-ui-react/src/components/monthPicker/monthPicker.tsx @@ -1,9 +1,8 @@ /** * External dependencies. */ -import { useCallback, useMemo, useState } from "react"; +import React, { useCallback, useMemo, useState } from "react"; import { ChevronLeft, ChevronRight, Calendar } from "lucide-react"; -import clsx from "clsx"; /** * Internal dependencies. @@ -12,6 +11,7 @@ import { dayjs } from "../../utils/dayjs"; import { Popover } from "../popover"; import { Button } from "../button"; import type { MonthPickerProps } from "./types"; +import { cn } from "../../utils"; const MONTHS = [ "January", @@ -44,65 +44,66 @@ const MonthPicker = ({ placement, onChange, }: MonthPickerProps) => { - const [open, setOpen] = useState(false); const [viewMode, setViewMode] = useState<"month" | "year">("month"); const [currentYear, setCurrentYear] = useState( parseValue(value)?.year ?? new Date().getFullYear() ); - const yearRangeStart = useMemo( - () => currentYear - (currentYear % 12), - [currentYear] - ); + const yearRangeStart = currentYear - (currentYear % 12); const yearRange = useMemo( () => Array.from({ length: 12 }, (_, i) => yearRangeStart + i), [yearRangeStart] ); - const pickerList = useMemo( - () => (viewMode === "year" ? yearRange : MONTHS), - [viewMode, yearRange] - ); + const pickerList = viewMode === "year" ? yearRange : MONTHS; const toggleViewMode = useCallback(() => { setViewMode((prevMode) => (prevMode === "month" ? "year" : "month")); }, []); - const prev = useCallback(() => { + const handlePrev = useCallback(() => { setCurrentYear((y) => (viewMode === "year" ? y - 12 : y - 1)); }, [viewMode]); - const next = useCallback(() => { + const handleNext = useCallback(() => { setCurrentYear((y) => (viewMode === "year" ? y + 12 : y + 1)); }, [viewMode]); - const handleOpenChange = useCallback((isOpen: boolean) => { - setOpen(isOpen); - if (!isOpen) setViewMode("month"); - }, []); - const handleOnClick = useCallback( - (v: string | number) => { - const parts = (value || "").split(" "); - const indexToModify = viewMode === "year" ? 1 : 0; - parts[indexToModify] = String(v); - const newValue = parts.join(" "); - onChange?.(newValue); + (e: React.MouseEvent, v: string | number) => { + e.stopPropagation(); + const parsed = parseValue(value); + let month = parsed?.month ?? "January"; + let year = parsed?.year ?? currentYear; + + if (viewMode === "month") { + month = String(v); + year = currentYear; + } else { + year = Number(v); + } + if (viewMode === "year") { + toggleViewMode(); + } + onChange?.(`${month} ${year}`); }, - [value, viewMode, onChange] + [value, viewMode, onChange, toggleViewMode, currentYear] ); return ( setViewMode("month")} target={({ togglePopover }) => ( - - @@ -130,16 +149,16 @@ const MonthPicker = ({
- {pickerList.map((month, index) => ( + {pickerList.map((item) => ( ))}
diff --git a/packages/frappe-ui-react/src/components/monthPicker/types.ts b/packages/frappe-ui-react/src/components/monthPicker/types.ts index bee6d040..5623877d 100644 --- a/packages/frappe-ui-react/src/components/monthPicker/types.ts +++ b/packages/frappe-ui-react/src/components/monthPicker/types.ts @@ -1,19 +1,14 @@ +import type { Placement } from "@popperjs/core"; + export interface MonthPickerProps { + /** Selected month value in 'Month Year' format (e.g., 'January 2026') */ value?: string; + /** Placeholder text for the MonthPicker button */ placeholder?: string; + /** CSS class names to apply to the button */ className?: string; - placement?: - | "top-start" - | "top" - | "top-end" - | "bottom-start" - | "bottom" - | "bottom-end" - | "left-start" - | "left" - | "left-end" - | "right-start" - | "right" - | "right-end"; + /** Popover placement relative to the target */ + placement?: Placement; + /** Callback fired when the month value changes */ onChange?: (value: string) => void; } From 64e7397d4049eb0ab7d88d12d7dbad9714f88e56 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Thu, 12 Feb 2026 14:29:13 +0530 Subject: [PATCH 3/3] Add interaction tests --- .../monthPicker.interactions.stories.tsx | 402 ++++++++++++++++++ .../components/monthPicker/monthPicker.tsx | 37 +- 2 files changed, 425 insertions(+), 14 deletions(-) create mode 100644 packages/frappe-ui-react/src/components/monthPicker/monthPicker.interactions.stories.tsx diff --git a/packages/frappe-ui-react/src/components/monthPicker/monthPicker.interactions.stories.tsx b/packages/frappe-ui-react/src/components/monthPicker/monthPicker.interactions.stories.tsx new file mode 100644 index 00000000..87489347 --- /dev/null +++ b/packages/frappe-ui-react/src/components/monthPicker/monthPicker.interactions.stories.tsx @@ -0,0 +1,402 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { useState } from "react"; +import { userEvent, expect, within, waitFor, fn, screen } from "storybook/test"; + +import MonthPicker from "./monthPicker"; +import type { MonthPickerProps } from "./types"; + +const meta: Meta = { + title: "Components/MonthPicker/Interactions", + component: MonthPicker, + parameters: { + docs: { source: { type: "dynamic" } }, + layout: "centered", + }, + argTypes: { + value: { + control: "text", + description: + "Selected month value in 'Month Year' format (e.g., 'January 2026').", + }, + placeholder: { + control: "text", + description: "Placeholder text for the MonthPicker button.", + }, + className: { + control: "text", + description: "CSS class names to apply to the button.", + }, + placement: { + control: "select", + options: [ + "top-start", + "top", + "top-end", + "bottom-start", + "bottom", + "bottom-end", + "left-start", + "left", + "left-end", + "right-start", + "right", + "right-end", + ], + description: "Popover placement relative to the target.", + }, + onChange: { + action: "onChange", + description: "Callback fired when the month value changes.", + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const SelectMonth: Story = { + args: { + placeholder: "Select month", + onChange: fn(), + }, + render: function Render(args) { + const [value, setValue] = useState(""); + const handleChange = (newValue: string) => { + setValue(newValue); + args.onChange?.(newValue); + }; + return ( +
+ +
+ ); + }, + play: async ({ canvasElement, args }) => { + const canvas = within(canvasElement); + + // Initial state - should show placeholder + const trigger = canvas.getByRole("button", { name: /select month/i }); + expect(trigger).toBeInTheDocument(); + expect(trigger).toHaveTextContent("Select month"); + + // Open the picker + await userEvent.click(trigger); + + // Wait for popover to open and months to be visible + const janButton = await screen.findByRole("button", { name: "Jan" }); + expect(janButton).toBeInTheDocument(); + + // Verify current year is displayed + const currentYear = new Date().getFullYear(); + const yearButton = await screen.findByRole("button", { + name: "Toggle between month and year selection", + }); + expect(yearButton).toHaveTextContent(String(currentYear)); + + // Select March + const marchButton = await screen.findByRole("button", { name: "Mar" }); + await userEvent.click(marchButton); + + // Verify onChange was called with correct format + await waitFor(() => { + expect(args.onChange).toHaveBeenCalledWith(`March ${currentYear}`); + }); + + // Verify trigger displays the selected value + await waitFor(() => { + expect(trigger).toHaveTextContent(`March ${currentYear}`); + }); + }, +}; + +export const SelectYearThenMonth: Story = { + args: { + placeholder: "Select month", + onChange: fn(), + }, + render: function Render(args) { + const [value, setValue] = useState(""); + const handleChange = (newValue: string) => { + setValue(newValue); + args.onChange?.(newValue); + }; + return ( +
+ +
+ ); + }, + play: async ({ canvasElement, args }) => { + const canvas = within(canvasElement); + + // Open the picker + const trigger = canvas.getByRole("button", { name: /select month/i }); + await userEvent.click(trigger); + + // Wait for popover to open + await screen.findByRole("button", { name: "Jan" }); + + // Click on year to toggle to year view + const currentYear = new Date().getFullYear(); + const yearToggleButton = await screen.findByRole("button", { + name: "Toggle between month and year selection", + }); + await userEvent.click(yearToggleButton); + + // Verify year view is displayed - should show year range + const yearRangeStart = currentYear - (currentYear % 12); + const rangeText = `${yearRangeStart} - ${yearRangeStart + 11}`; + await waitFor(async () => { + const rangeButton = await screen.findByRole("button", { + name: "Toggle between month and year selection", + }); + expect(rangeButton).toHaveTextContent(rangeText); + }); + + // Select a next year from current + const targetYear = currentYear + 1; + const targetYearButton = await screen.findByRole("button", { + name: String(targetYear), + }); + await userEvent.click(targetYearButton); + + // Verify it switches back to month view with target year displayed + await waitFor(async () => { + const yearBtn = await screen.findByRole("button", { + name: "Toggle between month and year selection", + }); + expect(yearBtn).toHaveTextContent(String(targetYear)); + expect( + await screen.findByRole("button", { name: "Jan" }) + ).toBeInTheDocument(); + }); + + // Now select June + const juneButton = await screen.findByRole("button", { name: "Jun" }); + await userEvent.click(juneButton); + + // Verify onChange was called with June and target year + await waitFor(() => { + expect(args.onChange).toHaveBeenCalledWith(`June ${targetYear}`); + }); + + // Verify trigger displays the selected value + await waitFor(() => { + expect(trigger).toHaveTextContent(`June ${targetYear}`); + }); + }, +}; + +export const NavigateBetweenYears: Story = { + args: { + placeholder: "Select month", + }, + render: function Render(args) { + const [value, setValue] = useState(""); + return ( +
+ +
+ ); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + // Open the picker + const trigger = canvas.getByRole("button", { name: /select month/i }); + await userEvent.click(trigger); + + // Wait for popover to open + await screen.findByRole("button", { name: "Jan" }); + + const currentYear = new Date().getFullYear(); + + // Click next year button + const nextButton = await screen.findByLabelText("Next month"); + await userEvent.click(nextButton); + + // Verify year incremented + await waitFor(async () => { + const yearBtn = await screen.findByRole("button", { + name: "Toggle between month and year selection", + }); + expect(yearBtn).toHaveTextContent(String(currentYear + 1)); + }); + + // Click previous year button twice + const prevButton = await screen.findByLabelText("Previous month"); + await userEvent.click(prevButton); + await userEvent.click(prevButton); + + // Verify year decremented by 2 (net -1 from original) + await waitFor(async () => { + const yearBtn = await screen.findByRole("button", { + name: "Toggle between month and year selection", + }); + expect(yearBtn).toHaveTextContent(String(currentYear - 1)); + }); + + // Select a month in the past year + const septemberButton = await screen.findByRole("button", { name: "Sep" }); + await userEvent.click(septemberButton); + + // Verify the selected value shows the correct past year + await waitFor(() => { + expect(trigger).toHaveTextContent(`September ${currentYear - 1}`); + }); + }, +}; + +export const NavigateYearRanges: Story = { + args: { + placeholder: "Select month", + }, + render: function Render(args) { + const [value, setValue] = useState(""); + return ( +
+ +
+ ); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + // Open the picker + const trigger = canvas.getByRole("button", { name: /select month/i }); + await userEvent.click(trigger); + + // Wait for popover to open + await screen.findByRole("button", { name: "Jan" }); + + // Toggle to year view + const yearToggleButton = await screen.findByRole("button", { + name: "Toggle between month and year selection", + }); + await userEvent.click(yearToggleButton); + + const currentYear = new Date().getFullYear(); + const yearRangeStart = currentYear - (currentYear % 12); + + // Verify initial year range + await waitFor(async () => { + const rangeButton = await screen.findByRole("button", { + name: "Toggle between month and year selection", + }); + expect(rangeButton).toHaveTextContent( + `${yearRangeStart} - ${yearRangeStart + 11}` + ); + }); + + // Navigate to next year range (12 years forward) + const nextButton = await screen.findByLabelText("Next years"); + await userEvent.click(nextButton); + + // Verify year range advanced by 12 + await waitFor(async () => { + const rangeButton = await screen.findByRole("button", { + name: "Toggle between month and year selection", + }); + expect(rangeButton).toHaveTextContent( + `${yearRangeStart + 12} - ${yearRangeStart + 23}` + ); + }); + + // Navigate to previous year range twice (24 years back total) + const prevButton = await screen.findByLabelText("Previous years"); + await userEvent.click(prevButton); + await userEvent.click(prevButton); + + // Verify year range went back 12 years net from original + await waitFor(async () => { + const rangeButton = await screen.findByRole("button", { + name: "Toggle between month and year selection", + }); + expect(rangeButton).toHaveTextContent( + `${yearRangeStart - 12} - ${yearRangeStart - 1}` + ); + }); + }, +}; + +export const ReopenPicker: Story = { + args: { + placeholder: "Select month", + }, + render: function Render(args) { + const currentYear = new Date().getFullYear(); + const initialValue = `March ${currentYear + 1}`; + const [value, setValue] = useState(initialValue); + return ( +
+ +
+ ); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const currentYear = new Date().getFullYear(); + const targetYear = currentYear + 1; + const initialValue = `March ${targetYear}`; + + const trigger = canvas.getByRole("button", { + name: new RegExp(initialValue, "i"), + }); + + // Open picker + await userEvent.click(trigger); + + // Verify it opens in month view with correct year + const yearBtn = await screen.findByRole("button", { + name: "Toggle between month and year selection", + }); + expect(yearBtn).toHaveTextContent(String(targetYear)); + await screen.findByRole("button", { name: "Jan" }); + + // Verify March is highlighted + const marchButton = await screen.findByRole("button", { name: "Mar" }); + expect(marchButton).toHaveClass("bg-surface-gray-7"); + + // Toggle to year view + const yearToggleButton = await screen.findByRole("button", { + name: "Toggle between month and year selection", + }); + await userEvent.click(yearToggleButton); + + // Verify year view is shown + await waitFor(async () => { + const targetYearButton = await screen.findByRole("button", { + name: String(targetYear), + }); + expect(targetYearButton).toHaveClass("bg-surface-gray-7"); + }); + + // Close picker by clicking trigger again + await userEvent.click(trigger); + + // Wait for popover to close + await waitFor(() => { + expect( + screen.queryByRole("button", { name: "Jan" }) + ).not.toBeInTheDocument(); + }); + + // Reopen picker + await userEvent.click(trigger); + + // Verify it resets to month view + await screen.findByRole("button", { name: "Jan" }); + const reopenedYearBtn = await screen.findByRole("button", { + name: "Toggle between month and year selection", + }); + expect(reopenedYearBtn).toHaveTextContent(String(targetYear)); + + // Verify year range is not shown (confirming we're in month view) + const yearRangeStart = targetYear - (targetYear % 12); + expect( + screen.queryByText( + new RegExp(`${yearRangeStart} - ${yearRangeStart + 11}`) + ) + ).not.toBeInTheDocument(); + }, +}; diff --git a/packages/frappe-ui-react/src/components/monthPicker/monthPicker.tsx b/packages/frappe-ui-react/src/components/monthPicker/monthPicker.tsx index e01a4e28..913b50f1 100644 --- a/packages/frappe-ui-react/src/components/monthPicker/monthPicker.tsx +++ b/packages/frappe-ui-react/src/components/monthPicker/monthPicker.tsx @@ -79,9 +79,9 @@ const MonthPicker = ({ if (viewMode === "month") { month = String(v); - year = currentYear; } else { year = Number(v); + setCurrentYear(year); } if (viewMode === "year") { toggleViewMode(); @@ -91,11 +91,19 @@ const MonthPicker = ({ [value, viewMode, onChange, toggleViewMode, currentYear] ); + const handleOnOpen = useCallback(() => { + setViewMode("month"); + const parsed = parseValue(value); + if (parsed?.year) { + setCurrentYear(parsed.year); + } + }, [value]); + return ( setViewMode("month")} + onOpen={handleOnOpen} target={({ togglePopover }) => ( - ))} + {pickerList.map((item) => { + const isSelected = parseValue(value)?.[viewMode] === item; + return ( + + ); + })} )}