fix(date-picker): improve calendar accessibility
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -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);
|
||||
});
|
||||
|
||||
@@ -423,7 +423,13 @@ export const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(function
|
||||
</div>
|
||||
</PopoverAnchor>
|
||||
|
||||
<PopoverContent className={datePickerContentVariants()} id={popupId} padding="sm" size="xl">
|
||||
<PopoverContent
|
||||
aria-label={`${props["aria-label"] ?? "Date picker"} calendar`}
|
||||
className={datePickerContentVariants()}
|
||||
id={popupId}
|
||||
padding="sm"
|
||||
size="xl"
|
||||
>
|
||||
<div className="grid gap-3">
|
||||
<div {...createSlot("header")} className={datePickerHeaderVariants()}>
|
||||
<div className={datePickerNavigationVariants()}>
|
||||
@@ -510,7 +516,7 @@ export const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(function
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className={datePickerGridVariants()} role="grid">
|
||||
<div className={datePickerGridVariants()}>
|
||||
{days.map((day, index) => {
|
||||
const outside = day.getMonth() !== visibleMonth.getMonth();
|
||||
const selected = sameDay(day, selectedDate);
|
||||
@@ -528,7 +534,6 @@ export const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(function
|
||||
today: isToday
|
||||
})}
|
||||
aria-label={formatValue(day, locale)}
|
||||
aria-pressed={selected}
|
||||
className={datePickerDayVariants()}
|
||||
disabled={dayDisabled}
|
||||
onClick={() => {
|
||||
@@ -539,7 +544,7 @@ export const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(function
|
||||
ref={(node) => {
|
||||
dayRefs.current[index] = node;
|
||||
}}
|
||||
role="gridcell"
|
||||
style={selected ? undefined : { color: "var(--color-foreground)" }}
|
||||
type="button"
|
||||
>
|
||||
{day.getDate()}
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user