Skip to content

feat(Calendar): add month/year picker modes and view switching#5981

Open
onmax wants to merge 3 commits intonuxt:v4from
onmax:feat/calendar-month-year-picker
Open

feat(Calendar): add month/year picker modes and view switching#5981
onmax wants to merge 3 commits intonuxt:v4from
onmax:feat/calendar-month-year-picker

Conversation

@onmax
Copy link
Contributor

@onmax onmax commented Feb 2, 2026

🔗 Linked issue

Resolves #5842, resolves #3652
Related #3094

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

  1. Add supports for calendar month picker modes via type prop
  2. Add support for year picker modes via type prop
  3. Interactive view switching (day -> month -> year). Maybe too opinionated?

Changes:

  • Add type prop (date | month | year) for standalone pickers
  • Add view / defaultView props for view state control
  • Clickable heading to switch views (day -> month -> year) when type="date"
  • New theme slots: monthGrid, monthCell, yearGrid, yearCell
  • New slots: month-cell, year-cell for custom rendering

📸 Screenshots

Feature Screenshot
Month Picker (type="month") image
Year Picker (type="year") image
View Switching (heading click) video below
Cap.2026-02-04.at.14.31.40.mp4

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@github-actions github-actions bot added the v4 #4488 label Feb 2, 2026
@onmax onmax force-pushed the feat/calendar-month-year-picker branch from d181f50 to 28da0df Compare February 2, 2026 15:41
@onmax
Copy link
Contributor Author

onmax commented Feb 2, 2026

@benjamincanac what do you think about this PR? Do you like the API?

There are some styles I would like to improve and nail down. But I think is mostly ready :)

Let me know your thoughts! Thank you!

Once we decide to move forward I will ammend PR fixing CI and fixing UI issues

Copy link
Member

Thanks for the PR! I do like the API with the type prop approach, it's consistent with other components.

However, it adds lots of code since there are no primitives for this in Reka UI. @J-Michalek what do you think about this? Are you aware of such thing being added in Reka UI any time soon?

@J-Michalek
Copy link
Contributor

Well there is an issue open for 8 months unovue/reka-ui#1933 and the activity in the repo is mild at best, but I think we should rely on RekaUI to provide these inputs...

Perhaps the author of this PR would be interested in implementing in RekaUI?

@caiotarifa
Copy link

caiotarifa commented Feb 3, 2026

I’m the author of the feature request in Reka UI (unovue/reka-ui#1933), and it has already gathered meaningful community interest (19+ reactions).

As mentioned above, this PR adds a fair amount of code mainly because Reka UI doesn’t provide primitives for month/year picking yet.

@onmax, if you’re open to it, migrating this PR to add MonthPicker and YearPicker primitives in Reka UI would be an excellent win for the ecosystem (as @J-Michalek suggested).

@onmax
Copy link
Contributor Author

onmax commented Feb 3, 2026

Thanks for the feedback. I will prepare a PR for Reka UI.

@benjamincanac, should I keep the behaviour shown in the video? I am not 100% this is the best ux 🤔

@benjamincanac
Copy link
Member

I think I like it yes, it avoids having to implement popover and lets us render a Calendar only for months at the same time as selecting a month for a normal calendar.

@onmax
Copy link
Contributor Author

onmax commented Feb 3, 2026

Ok, i won't bother you again 😬,

once reka ui is released and this pr is ready I will mark this pr ready and i will ping you.

Feel free to post any feedback though. Most of the code is ready. I am just testing it throughly :)

@onmax onmax force-pushed the feat/calendar-month-year-picker branch 2 times, most recently from 4d64993 to ae0edf7 Compare February 3, 2026 17:54
@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 3, 2026

npm i https://pkg.pr.new/@nuxt/ui@5981

commit: f655135

@onmax
Copy link
Contributor Author

onmax commented Feb 4, 2026

For reference: much better approach imho

image

@sewalsh
Copy link

sewalsh commented Feb 4, 2026

Great to see work on this! I've long missed it since version 2.

Just my 2c. I think a 3x4 grid looks neater as was used in v2: https://ui2.nuxt.com/components/date-picker#datepicker

@onmax
Copy link
Contributor Author

onmax commented Mar 4, 2026

Hi Benjamin, this PR is ready for review.

A few notes before you dive in:

  • I included a reka-ui latest-version bump while working on this. I suspect that should probably be split out and not be part of this PR.
  • In the month picker example, I used a 3-year window input because it feels practical for my own use case. I understand the example is long, so I’m open to simplifying it or even removing it.
  • I know the diff is large. If you prefer, we can use the same strategy we used in nuxt-ui-templates/saas: I prepare smaller focused PRs and you review/approve them one by one.
  • The default UI could be better with different spacing/values, so feel free to redesign my implementation :)
  • I have tested locally only the playground and the documentation

Let me know how to proceed :)

Tested playground and docs.

@onmax onmax marked this pull request as ready for review March 4, 2026 11:47
@onmax onmax requested a review from benjamincanac as a code owner March 4, 2026 11:47
@onmax
Copy link
Contributor Author

onmax commented Mar 4, 2026

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7fc753c86e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds two example Vue components: CalendarMonthPickerExample.vue and CalendarYearPickerExample.vue. Extends src/runtime/components/Calendar.vue to support multi-view calendars by adding exported types CalendarType and CalendarView, new props (type, view, defaultView), emits ('update:placeholder', 'update:view'), expanded slot signatures (heading, month-cell, year-cell), view resolution and navigation/placeholder synchronization, and scaffolding for month/year rendering. Updates src/theme/calendar.ts to expose month/year grid/cell/trigger slots and expands variant/size mappings and compound variants. Documentation (calendar.md) is updated with month/year picker examples. Tests (Calendar.spec.ts) are expanded to cover type/view behaviors, slots, and emission semantics.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main feature additions: month/year picker modes and view switching for the Calendar component.
Linked Issues check ✅ Passed The PR successfully implements the core coding requirements from issue #5842: adds month/year picker support via type prop, introduces view switching and new slots, and enables calendar view modes.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing month/year picker modes and view switching as specified in the linked issues; no unrelated modifications detected.
Description check ✅ Passed The pull request description clearly details the changes: adding month/year picker support via a new type prop, implementing interactive view switching, and introducing new theme and render slots.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
src/theme/calendar.ts (1)

145-176: Reduce duplication in month/year trigger variant maps.

monthCellTrigger and yearCellTrigger are repeated with identical class logic across each color/variant branch. This doubles maintenance surface and makes future style updates easy to desync.

Also applies to: 206-237

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/theme/calendar.ts` around lines 145 - 176, monthCellTrigger and
yearCellTrigger class strings are duplicated across each color/variant branch;
extract a small helper (e.g., buildTriggerClass(color:string,
variantKind:string) or buildTriggerClasses(color:string) that returns the
template string) and use it for both monthCellTrigger and yearCellTrigger in the
maps for variants 'solid','outline','soft','subtle' (refer to
options.theme.colors usage and the monthCellTrigger/yearCellTrigger keys); apply
the same refactor to the second block noted (lines ~206-237) so both triggers
consume the single source of truth for the class string.
src/runtime/components/Calendar.vue (1)

558-778: Consider extracting shared month/year picker panel markup.

The month/year navigation and standalone branches repeat nearly the same header/grid/cell structure. Extracting shared subcomponents/helpers would significantly reduce maintenance overhead.

Also applies to: 780-962

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Calendar.vue` around lines 558 - 778, The MonthPicker
and YearPicker branches duplicate the same header/grid/cell markup; extract the
shared panel pieces into reusable subcomponents (e.g., PickerPanel,
PickerHeader, PickerGrid, PickerCell) and use them inside both MonthPicker and
YearPicker to avoid repetition. Move shared logic/props/slots (heading slot
using setView/setMonth/setYear, prev/next controls, grid rows/cols rendering,
getPickerColumnAlignment, formatMonthLabel/formatYearLabel, and the v-slot props
like { grid, date } ) into these new components and have MonthPicker/YearPicker
pass their specific props (monthGrid/yearGrid,
onMonthSelectForNavigation/onYearNavigationModelValueUpdate,
MonthPickerCellTriggerComp/YearPickerCellTriggerComp,
MonthPickerHeadingComp/YearPickerHeadingComp, etc.) so the duplicated header and
grid rendering is centralized and reused.
test/components/Calendar.spec.ts (1)

112-114: Tighten update:placeholder assertions to catch duplicate emits.

These tests currently validate presence/payload, but duplicate emissions would still pass. Prefer asserting exact count where deterministic.

🧪 Example assertion pattern
-      expect(wrapper.emitted('update:placeholder')).toBeTruthy()
-      expect(wrapper.emitted('update:placeholder')![0]![0]).toMatchObject({ month: 6, day: 1 })
+      const placeholderEvents = wrapper.emitted('update:placeholder') ?? []
+      expect(placeholderEvents).toHaveLength(1)
+      expect(placeholderEvents[0]?.[0]).toMatchObject({ month: 6, day: 1 })

Also applies to: 130-132, 313-316, 335-338, 350-353

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/components/Calendar.spec.ts` around lines 112 - 114, The test currently
only checks that wrapper.emitted('update:placeholder') exists and payload
matches, which won't catch duplicate emits; update each assertion (e.g., the
block using wrapper.emitted('update:placeholder') and payload checks around
lines with expect(...).toBeTruthy()) to assert the exact emit count
(expect(wrapper.emitted('update:placeholder')).toHaveLength(1)) before
inspecting the payload (then assert emitted(...)[0][0] matches the expected {
month, day }); apply this pattern to all occurrences mentioned (around the other
assertions at the referenced places) so duplicate emits will fail the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@docs/app/components/content/examples/calendar/CalendarMonthPickerExample.vue`:
- Around line 56-63: The two icon-only UButton controls used for year navigation
(the one calling shiftYears(-1) and the one calling shiftYears(1)) lack
accessible labels; update those UButton instances to include descriptive
accessible text (e.g., aria-label="Previous year" and aria-label="Next year" or
use a localized string) so screen readers can identify them, and ensure the
label prop (or title) is set on both the left chevron and the right chevron
buttons to match their shiftYears(-1) and shiftYears(1) behavior.

In `@docs/content/docs/2.components/calendar.md`:
- Around line 341-345: The new example blocks using the ::component-example
wrapper (name: 'calendar-month-picker-example') trigger markdownlint MD003
heading-style warnings; fix by either adjusting the block to use proper heading
markup or, more simply, add targeted markdownlint suppression comments around
each example block (e.g., insert <!-- markdownlint-disable MD003 --> immediately
before the ::component-example block and <!-- markdownlint-enable MD003 -->
immediately after) and do the same for the other occurrence referenced (lines
351-355) so the linter is satisfied without changing content.
- Line 269: Update the sentence describing calendar view transitions to
accurately reflect behavior: change the line that currently states "Selecting a
month or year navigates back to the day view" so it instead says that selecting
a month navigates to the day view, while selecting a year navigates to the month
view first (and from there selecting a month goes to the day view). Reference
the sentence in the calendar docs heading description to ensure the month vs.
year flow is clarified.

In `@src/runtime/components/Calendar.vue`:
- Around line 220-221: The template is double-emitting update:placeholder
because calendarRootProps (from useForwardPropsEmits) already forwards
update:placeholder from DayCalendar.Root; remove the explicit
`@update`:placeholder="updatePlaceholder" listener on DayCalendar.Root so only the
forwarded event is emitted. Locate DayCalendar.Root usages that spread
calendarRootProps and remove the explicit updatePlaceholder handler (also apply
the same removal for the other occurrence referenced around the second block),
leaving calendarRootProps to handle forwarding.
- Around line 215-219: The month/year labels become stale because
useDateFormatter(code.value) captures the locale at init; update the formatter
when locale changes by adding a watch on code.value that calls
formatter.setLocale(newCode) so formatMonthLabel/formatYearLabel use the current
locale; locate useDateFormatter and formatter in Calendar.vue and add a watch(()
=> code.value, (newCode) => formatter.setLocale(newCode)) (also apply the same
change where useDateFormatter is used around lines 341-352).

---

Nitpick comments:
In `@src/runtime/components/Calendar.vue`:
- Around line 558-778: The MonthPicker and YearPicker branches duplicate the
same header/grid/cell markup; extract the shared panel pieces into reusable
subcomponents (e.g., PickerPanel, PickerHeader, PickerGrid, PickerCell) and use
them inside both MonthPicker and YearPicker to avoid repetition. Move shared
logic/props/slots (heading slot using setView/setMonth/setYear, prev/next
controls, grid rows/cols rendering, getPickerColumnAlignment,
formatMonthLabel/formatYearLabel, and the v-slot props like { grid, date } )
into these new components and have MonthPicker/YearPicker pass their specific
props (monthGrid/yearGrid,
onMonthSelectForNavigation/onYearNavigationModelValueUpdate,
MonthPickerCellTriggerComp/YearPickerCellTriggerComp,
MonthPickerHeadingComp/YearPickerHeadingComp, etc.) so the duplicated header and
grid rendering is centralized and reused.

In `@src/theme/calendar.ts`:
- Around line 145-176: monthCellTrigger and yearCellTrigger class strings are
duplicated across each color/variant branch; extract a small helper (e.g.,
buildTriggerClass(color:string, variantKind:string) or
buildTriggerClasses(color:string) that returns the template string) and use it
for both monthCellTrigger and yearCellTrigger in the maps for variants
'solid','outline','soft','subtle' (refer to options.theme.colors usage and the
monthCellTrigger/yearCellTrigger keys); apply the same refactor to the second
block noted (lines ~206-237) so both triggers consume the single source of truth
for the class string.

In `@test/components/Calendar.spec.ts`:
- Around line 112-114: The test currently only checks that
wrapper.emitted('update:placeholder') exists and payload matches, which won't
catch duplicate emits; update each assertion (e.g., the block using
wrapper.emitted('update:placeholder') and payload checks around lines with
expect(...).toBeTruthy()) to assert the exact emit count
(expect(wrapper.emitted('update:placeholder')).toHaveLength(1)) before
inspecting the payload (then assert emitted(...)[0][0] matches the expected {
month, day }); apply this pattern to all occurrences mentioned (around the other
assertions at the referenced places) so duplicate emits will fail the test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5a5e3435-b8d2-4612-9fca-d9b292cbbdd6

📥 Commits

Reviewing files that changed from the base of the PR and between 6dd0fc4 and 7fc753c.

⛔ Files ignored due to path filters (2)
  • test/components/__snapshots__/Calendar-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Calendar.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (6)
  • docs/app/components/content/examples/calendar/CalendarMonthPickerExample.vue
  • docs/app/components/content/examples/calendar/CalendarYearPickerExample.vue
  • docs/content/docs/2.components/calendar.md
  • src/runtime/components/Calendar.vue
  • src/theme/calendar.ts
  • test/components/Calendar.spec.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
src/runtime/components/Calendar.vue (2)

218-219: ⚠️ Potential issue | 🟠 Major

Sync formatter locale when code changes at runtime.

useDateFormatter(code.value) is created once, but month/year labels continue using that formatter later. If locale switches dynamically, labels can stay stale unless the formatter locale is updated.

💡 Proposed fix
 const formatter = useDateFormatter(code.value)
+watch(() => code.value, (newCode) => {
+  formatter.setLocale(newCode)
+})
#!/bin/bash
# Verify formatter initialization and whether locale sync exists
rg -n "useDateFormatter\\(|setLocale\\(|watch\\(\\(\\) => code\\.value" src/runtime/components/Calendar.vue -C2

Also applies to: 341-352

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Calendar.vue` around lines 218 - 219, The date
formatter created with useDateFormatter(code.value) is only initialized once so
when code.value (locale) changes at runtime the formatter stays stale; update
the implementation around the formatter variable (the const formatter =
useDateFormatter(code.value) in Calendar.vue) to react to code changes—either by
watching code.value and calling formatter.setLocale(newLocale) if the formatter
exposes setLocale, or by recreating the formatter inside a watch on code.value
(e.g., replace the single initialization with a watch that reassigns formatter
using useDateFormatter(code.value)); ensure the same approach is applied to the
other affected block around lines referenced (341-352).

220-221: ⚠️ Potential issue | 🟠 Major

Avoid duplicate update:placeholder emission in day mode.

useForwardPropsEmits(..., emits) already forwards emitted updates; keeping explicit @update:placeholder="updatePlaceholder" on DayCalendar.Root can emit twice for one user action.

💡 Proposed fix
-const calendarRootProps = useForwardPropsEmits(reactiveOmit(props, 'type', 'view', 'defaultView', 'range', 'modelValue', 'defaultValue', 'placeholder', 'color', 'variant', 'size', 'monthControls', 'yearControls', 'class', 'ui'), emits)
+const _calendarRootProps = useForwardPropsEmits(
+  reactiveOmit(props, 'type', 'view', 'defaultView', 'range', 'modelValue', 'defaultValue', 'placeholder', 'color', 'variant', 'size', 'monthControls', 'yearControls', 'class', 'ui'),
+  emits
+)
+const calendarRootProps = computed(() => ({
+  ..._calendarRootProps.value,
+  'onUpdate:placeholder': updatePlaceholder
+}))
-    `@update`:placeholder="updatePlaceholder"
#!/bin/bash
# Verify possible duplicate placeholder update wiring in day view
rg -n "useForwardPropsEmits|@update:placeholder|updatePlaceholder\\(|'update:placeholder'" src/runtime/components/Calendar.vue -C2

Also applies to: 431-432

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Calendar.vue` around lines 220 - 221, Duplicate
placeholder updates occur because useForwardPropsEmits(calendarRootProps, emits)
already forwards emits and DayCalendar.Root still has an explicit
`@update`:placeholder="updatePlaceholder"; remove the explicit `@update`:placeholder
binding from DayCalendar.Root (and the similar binding around lines ~431-432) so
only useForwardPropsEmits handles the emit, keeping calendarRootProps and the
emits object intact and ensuring updatePlaceholder helper (if still referenced
elsewhere) is not hooked twice.
🧹 Nitpick comments (1)
src/runtime/components/Calendar.vue (1)

576-874: Consider extracting shared month/year panel structure.

Month/year panel markup is duplicated across navigation and standalone branches, which increases maintenance cost and drift risk. A shared internal subcomponent/composable for header+grid rendering would simplify future changes.

Also applies to: 689-965

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Calendar.vue` around lines 576 - 874, The month/year
panel header+grid markup is duplicated between the MonthPicker and YearPicker
branches (see MonthPickerHeaderComp/MonthPickerGridComp/MonthPickerCellComp and
YearPickerHeaderComp/YearPickerGridComp/YearPickerCellComp usage plus helpers
like pickerPanelClass, pickerHeaderStyle, pickerHeaderHeadingStyle,
getPickerColumnAlignment, setView, setMonth, setYear, yearControls); extract
that shared structure into a single internal component or composable (e.g.,
CalendarPanel) that accepts props for the heading slot/value, grid rows, cell
renderers, navigation controls, aria labels and emits necessary events
(update:model-value, update:placeholder) so MonthPicker/YearPicker/standalone
Month branches simply render <CalendarPanel :grid="..." :date="..." ...> and
forward slots/props, keeping behavior (buttons, click handlers, classes, and
styles) identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/runtime/components/Calendar.vue`:
- Around line 218-219: The date formatter created with
useDateFormatter(code.value) is only initialized once so when code.value
(locale) changes at runtime the formatter stays stale; update the implementation
around the formatter variable (the const formatter =
useDateFormatter(code.value) in Calendar.vue) to react to code changes—either by
watching code.value and calling formatter.setLocale(newLocale) if the formatter
exposes setLocale, or by recreating the formatter inside a watch on code.value
(e.g., replace the single initialization with a watch that reassigns formatter
using useDateFormatter(code.value)); ensure the same approach is applied to the
other affected block around lines referenced (341-352).
- Around line 220-221: Duplicate placeholder updates occur because
useForwardPropsEmits(calendarRootProps, emits) already forwards emits and
DayCalendar.Root still has an explicit `@update`:placeholder="updatePlaceholder";
remove the explicit `@update`:placeholder binding from DayCalendar.Root (and the
similar binding around lines ~431-432) so only useForwardPropsEmits handles the
emit, keeping calendarRootProps and the emits object intact and ensuring
updatePlaceholder helper (if still referenced elsewhere) is not hooked twice.

---

Nitpick comments:
In `@src/runtime/components/Calendar.vue`:
- Around line 576-874: The month/year panel header+grid markup is duplicated
between the MonthPicker and YearPicker branches (see
MonthPickerHeaderComp/MonthPickerGridComp/MonthPickerCellComp and
YearPickerHeaderComp/YearPickerGridComp/YearPickerCellComp usage plus helpers
like pickerPanelClass, pickerHeaderStyle, pickerHeaderHeadingStyle,
getPickerColumnAlignment, setView, setMonth, setYear, yearControls); extract
that shared structure into a single internal component or composable (e.g.,
CalendarPanel) that accepts props for the heading slot/value, grid rows, cell
renderers, navigation controls, aria labels and emits necessary events
(update:model-value, update:placeholder) so MonthPicker/YearPicker/standalone
Month branches simply render <CalendarPanel :grid="..." :date="..." ...> and
forward slots/props, keeping behavior (buttons, click handlers, classes, and
styles) identical.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ab468c0c-8f73-4a93-ad60-2443c92496d4

📥 Commits

Reviewing files that changed from the base of the PR and between 7fc753c and 2183508.

📒 Files selected for processing (1)
  • src/runtime/components/Calendar.vue

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/runtime/components/Calendar.vue`:
- Around line 267-281: The current effectivePlaceholder always falls back to
today(getLocalTimeZone()) which causes MonthPicker/YearPicker to open on today
even when modelValue/defaultValue are provided; change effectivePlaceholder to
derive from value when props.placeholder is not provided by computing:
localPlaceholder.value ?? props.modelValue ?? props.defaultValue ??
today(getLocalTimeZone()); keep dayViewPlaceholder behavior as-is (it already
respects explicit placeholder/localPlaceholder), and ensure the bindings to
MonthPicker and YearPicker use this updated effectivePlaceholder (references:
localPlaceholder, effectivePlaceholder, dayViewPlaceholder, props.placeholder,
props.modelValue, props.defaultValue, MonthPicker, YearPicker).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 112db6dc-50df-4a41-aef4-73caa0ce38fc

📥 Commits

Reviewing files that changed from the base of the PR and between 2183508 and 5581f82.

📒 Files selected for processing (5)
  • docs/app/components/content/examples/calendar/CalendarMonthPickerExample.vue
  • docs/content/docs/2.components/calendar.md
  • src/runtime/components/Calendar.vue
  • src/theme/calendar.ts
  • test/components/Calendar.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/content/docs/2.components/calendar.md

@benjamincanac
Copy link
Member

@onmax I think the resolved issue is wrong!

@onmax
Copy link
Contributor Author

onmax commented Mar 4, 2026

fixed

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/runtime/components/Calendar.vue (1)

208-212: Consider using setLocale() instead of recreating the formatter.

Reka UI's useDateFormatter provides a setLocale() method specifically for updating the locale. This is more efficient than recreating the formatter instance on each change:

💡 Suggested refactor
-const formatter = shallowRef(useDateFormatter(code.value))
-
-watch(() => code.value, (value) => {
-  formatter.value = useDateFormatter(value)
-})
+const formatter = useDateFormatter(code.value)
+
+watch(() => code.value, (value) => {
+  formatter.setLocale(value)
+})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Calendar.vue` around lines 208 - 212, The current code
recreates the date formatter on each locale change by assigning formatter.value
= useDateFormatter(value); instead use the formatter's built-in updater: keep
formatter as shallowRef(useDateFormatter(code.value)) and in the watch callback
call formatter.value.setLocale(value) (ensure setLocale exists on the returned
object) instead of replacing the whole formatter instance to improve efficiency
and preserve any internal state or subscriptions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/runtime/components/Calendar.vue`:
- Around line 208-212: The current code recreates the date formatter on each
locale change by assigning formatter.value = useDateFormatter(value); instead
use the formatter's built-in updater: keep formatter as
shallowRef(useDateFormatter(code.value)) and in the watch callback call
formatter.value.setLocale(value) (ensure setLocale exists on the returned
object) instead of replacing the whole formatter instance to improve efficiency
and preserve any internal state or subscriptions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6c23c1df-28da-43ee-8187-b64d3c7ae764

📥 Commits

Reviewing files that changed from the base of the PR and between 5581f82 and 6959644.

⛔ Files ignored due to path filters (2)
  • test/components/__snapshots__/Calendar-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Calendar.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (6)
  • docs/app/components/content/examples/calendar/CalendarMonthPickerExample.vue
  • docs/app/components/content/examples/calendar/CalendarYearPickerExample.vue
  • docs/content/docs/2.components/calendar.md
  • src/runtime/components/Calendar.vue
  • src/theme/calendar.ts
  • test/components/Calendar.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/app/components/content/examples/calendar/CalendarYearPickerExample.vue

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/runtime/components/Calendar.vue`:
- Around line 619-635: The inline branch that renders <component
:is="picker.root"> is dropping root-level props by passing a hand-picked set
(pickerValueProps, placeholder, locale, dir, minValue, maxValue, disabled,
readonly, class) instead of reusing the same root prop assembly used by the
day-calendar path; fix this by using the same calendarRootProps construction
(the same prop spread used for day mode) when rendering picker.root and then
explicitly override only the value/placeholder wiring and event handlers (keep
picker.onUpdate and onPickerPlaceholderUpdate) so documented root props like as
and other root-level options are preserved across type !== 'date' modes.
- Around line 241-245: The watcher currently re-applies props.defaultView after
mount, turning it into live state; change the logic so defaultView only seeds
internalView once and is not watched thereafter. Concretely: stop watching
props.defaultView (remove it from the watcher), initialize internalView from
defaultView on mount (or only when internalView is undefined), and update the
watcher to only react to props.type and props.view so that internalView is
updated when the component is controlled (props.view changes) or when props.type
changes (in which case call getDefaultView(type, defaultView) to compute a new
seed if internalView is undefined). Ensure you reference and update the existing
watcher and the initialization for internalView and use getDefaultView(type as
CalendarType, defaultView as CalendarView | undefined) where needed.
- Around line 364-379: Remove the no-op expressions causing the linter error by
deleting the stray `code.value` lines in the functions `formatMonthLabel` and
`formatYearLabel`; leave the rest of each function intact so they continue to
call `formatter.value.custom(date.toDate(getLocalTimeZone()), {...})` and fall
back to `String(date.month)` / appropriate fallback — this removes the
unused-expression warnings from `@typescript-eslint/no-unused-expressions`
without changing reactivity (the locale sync is already handled by the watcher).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ba04acfe-5bd5-4986-bc24-479bb70b5631

📥 Commits

Reviewing files that changed from the base of the PR and between 6959644 and abb210e.

📒 Files selected for processing (1)
  • src/runtime/components/Calendar.vue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v4 #4488

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add <UMonthPicker> and <UYearPicker> components (requires RekaUI Calendar view modes) Calendar: improve month and year select

5 participants