From 3c172c411eba7e5abb5663f75b6d46359fc2f834 Mon Sep 17 00:00:00 2001 From: kurihada Date: Tue, 24 Mar 2026 18:00:20 +0800 Subject: [PATCH] fix(date-picker): improve calendar accessibility Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../ui/src/components/date-picker.test.tsx | 21 ++++++++++++------- packages/ui/src/components/date-picker.tsx | 13 ++++++++---- .../ui/src/components/date-picker.variants.ts | 6 +++--- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/packages/ui/src/components/date-picker.test.tsx b/packages/ui/src/components/date-picker.test.tsx index 12ef1ad..9466a86 100644 --- a/packages/ui/src/components/date-picker.test.tsx +++ b/packages/ui/src/components/date-picker.test.tsx @@ -3,6 +3,12 @@ import { describe, expect, it, vi } from "vitest"; import { DatePicker } from "./date-picker"; +function getCalendarDayButtons(dialogName: string) { + return within(screen.getByRole("dialog", { name: dialogName })) + .getAllByRole("button") + .filter((button) => button.getAttribute("data-slot") === "day"); +} + describe("DatePicker", () => { it("renders a placeholder and selects a date in uncontrolled mode", async () => { render( @@ -16,8 +22,7 @@ describe("DatePicker", () => { const field = screen.getByRole("combobox", { name: "Launch date" }); expect(field.closest('[data-slot="root"]')).toHaveAttribute("data-placeholder", ""); - const calendar = screen.getByRole("grid"); - const dayButton = within(calendar).getAllByRole("gridcell")[10]; + const dayButton = getCalendarDayButtons("Launch date calendar")[10]; fireEvent.click(dayButton); @@ -38,7 +43,7 @@ describe("DatePicker", () => { ); fireEvent.click( - screen.getByRole("gridcell", { + screen.getByRole("button", { name: /Apr 20, 2026|20 Apr 2026|Apr 20 2026/i }) ); @@ -96,7 +101,7 @@ describe("DatePicker", () => { /> ); - const firstDay = within(screen.getByRole("grid")).getAllByRole("gridcell")[0]; + const firstDay = getCalendarDayButtons("Monday-first date calendar")[0]; expect(firstDay).toHaveAccessibleName("Feb 23, 2026"); expect(screen.getByText("Mon")).toBeInTheDocument(); @@ -113,7 +118,7 @@ describe("DatePicker", () => { /> ); - const firstDay = within(screen.getByRole("grid")).getAllByRole("gridcell")[0]; + const firstDay = getCalendarDayButtons("Sunday-first date calendar")[0]; expect(firstDay).toHaveAccessibleName("Mar 1, 2026"); expect(screen.getByText("Sun")).toBeInTheDocument(); @@ -130,9 +135,9 @@ describe("DatePicker", () => { /> ); - const disabledDays = screen - .getAllByRole("gridcell") - .filter((cell) => cell.hasAttribute("data-disabled")); + const disabledDays = getCalendarDayButtons("Guardrailed date calendar").filter((cell) => + cell.hasAttribute("data-disabled") + ); expect(disabledDays.length).toBeGreaterThan(0); }); diff --git a/packages/ui/src/components/date-picker.tsx b/packages/ui/src/components/date-picker.tsx index bbfcd30..b4a75fb 100644 --- a/packages/ui/src/components/date-picker.tsx +++ b/packages/ui/src/components/date-picker.tsx @@ -423,7 +423,13 @@ export const DatePicker = forwardRef(function - +
@@ -510,7 +516,7 @@ export const DatePicker = forwardRef(function ))}
-
+
{days.map((day, index) => { const outside = day.getMonth() !== visibleMonth.getMonth(); const selected = sameDay(day, selectedDate); @@ -528,7 +534,6 @@ export const DatePicker = forwardRef(function today: isToday })} aria-label={formatValue(day, locale)} - aria-pressed={selected} className={datePickerDayVariants()} disabled={dayDisabled} onClick={() => { @@ -539,7 +544,7 @@ export const DatePicker = forwardRef(function ref={(node) => { dayRefs.current[index] = node; }} - role="gridcell" + style={selected ? undefined : { color: "var(--color-foreground)" }} type="button" > {day.getDate()} diff --git a/packages/ui/src/components/date-picker.variants.ts b/packages/ui/src/components/date-picker.variants.ts index 01918cf..b9bab0c 100644 --- a/packages/ui/src/components/date-picker.variants.ts +++ b/packages/ui/src/components/date-picker.variants.ts @@ -37,12 +37,12 @@ export const datePickerGridVariants = cva("grid grid-cols-7 gap-1"); export const datePickerDayVariants = cva( [ - "inline-flex h-9 items-center justify-center rounded-[var(--ui-control-radius)] text-sm font-medium outline-none", + "inline-flex h-9 items-center justify-center rounded-[var(--ui-control-radius)] text-sm font-medium text-[var(--color-foreground)] outline-none", "transition-[background-color,color,box-shadow,transform] duration-[var(--dur-fast)] ease-[var(--ease-standard)]", "focus-visible:ring-2 focus-visible:ring-[var(--color-ring)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ui-panel-bg)]", - "data-[outside=true]:text-[color-mix(in_oklch,var(--color-muted-foreground)_78%,transparent)]", + "data-[outside=true]:text-[var(--color-foreground)]", "data-[today=true]:shadow-[inset_0_0_0_1px_color-mix(in_oklch,var(--color-primary)_26%,transparent)]", - "data-[disabled=true]:pointer-events-none opacity-35", + "data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-35", "data-[selected=true]:bg-[var(--color-primary)] data-[selected=true]:text-[var(--color-primary-foreground)] data-[selected=true]:shadow-[var(--ui-control-shadow)]", "hover:bg-[var(--ui-control-bg)] hover:text-[var(--color-foreground)]", getMotionRecipeClassNames("ring")