Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions packages/ui/components/form/date-range-picker/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,24 @@ function Calendar({
nav: "flex items-center",
head: "",
head_row: "flex w-full items-center justify-between",
head_cell: "w-8 md:w-11 h-8 text-sm font-medium text-default text-center",
head_cell:
"w-8 md:w-11 h-8 text-sm font-medium text-default text-center",
nav_button: cn(buttonClasses({ color: "minimal", variant: "icon" })),
table: "w-full border-collapse stack-y-1",
row: "flex w-full mt-0.5 gap-0.5",
cell: "w-8 h-8 md:h-11 md:w-11 text-center text-sm p-0 relative focus-within:relative focus-within:z-20",
day: cn(
buttonClasses({ color: "minimal" }),
"w-8 h-8 md:h-11 md:w-11 p-0 text-sm font-medium aria-selected:opacity-100 inline-flex items-center justify-center"
"w-8 h-8 md:h-11 md:w-11 p-0 text-sm font-medium aria-selected:opacity-100 inline-flex items-center justify-center",
),
day_range_end: "hover:bg-inverted! text-inverted!",
day_range_start: "hover:bg-inverted! text-inverted!",
day_selected: "bg-inverted text-inverted",
day_today: "",
day_outside: "",
day_disabled: "text-muted opacity-50",
day_range_middle: "aria-selected:bg-emphasis aria-selected:text-emphasis",
day_disabled: "text-[#4A4E59] dark:text-[#A3A3A3]",
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

day_disabled now uses hard-coded hex colors. This bypasses the project’s semantic theme tokens (e.g., text-subtle, text-muted) and makes the disabled-day color harder to tune for custom themes/branding. Consider introducing a semantic token/class for disabled date text (or using an existing token if one matches the required contrast) instead of embedding hex values here.

Suggested change
day_disabled: "text-[#4A4E59] dark:text-[#A3A3A3]",
day_disabled: "text-subtle",

Copilot uses AI. Check for mistakes.
day_range_middle:
"aria-selected:bg-emphasis aria-selected:text-emphasis",
Comment on lines 40 to +51
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

day uses buttonClasses({ color: "minimal" }), which includes disabled:opacity-30 (see Button.tsx). Disabled days will therefore still render with reduced opacity even after changing day_disabled to a darker text color, likely reintroducing contrast failures. Consider overriding the disabled opacity for calendar day buttons (e.g., ensure disabled days render at full opacity while keeping a muted color) so the WCAG contrast fix is effective.

Copilot uses AI. Check for mistakes.
day_hidden: "invisible",
...classNames,
}}
Expand Down
43 changes: 30 additions & 13 deletions packages/ui/components/form/datepicker/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,24 @@ import { Button } from "../../button/Button";
import { Calendar } from "../date-range-picker/Calendar";

type Props = {
date: Date;
date: Date | null;
onDatesChange?: ((date: Date) => void) | undefined;
className?: string;
disabled?: boolean;
minDate?: Date | null;
label?: string;
};

const DatePicker = ({ minDate, disabled, date, onDatesChange, className, label }: Props) => {
const DatePicker = ({
minDate,
disabled,
date,
onDatesChange,
className,
label,
}: Props) => {
function handleDayClick(newDate: Date) {
onDatesChange?.(newDate ?? new Date());
onDatesChange?.(newDate);
}
const fromDate = minDate ?? new Date();
const calender = (
Expand All @@ -26,8 +33,8 @@ const DatePicker = ({ minDate, disabled, date, onDatesChange, className, label }
fromDate={minDate === null ? undefined : fromDate}
// toDate={maxDate}
mode="single"
defaultMonth={date}
selected={date}
defaultMonth={date ?? fromDate}
selected={date ?? undefined}
onDayClick={(day) => handleDayClick(day)}
numberOfMonths={1}
disabled={disabled}
Expand All @@ -43,18 +50,28 @@ const DatePicker = ({ minDate, disabled, date, onDatesChange, className, label }
data-testid="pick-date"
color="secondary"
EndIcon="calendar"
className={classNames("justify-between text-left font-normal", !date && "text-subtle")}>
{label ?? (date ? <>{format(date, "LLL dd, y")}</> : <span>Pick a date</span>)}
disabled={disabled}
className={classNames(
"justify-between text-left font-normal",
!date && "text-subtle",
)}
>
{label ??
(date ? (
<>{format(date, "LLL dd, y")}</>
) : (
<span>Pick a date</span>
))}
</Button>
</Popover.Trigger>
<Popover.Portal>
<Popover.Content
className="bg-default text-emphasis z-50 w-auto rounded-md border p-0 outline-none"
align="start"
sideOffset={4}
<Popover.Content
className="bg-default text-emphasis z-50 w-auto rounded-md border p-0 outline-none"
align="start"
sideOffset={4}
>
{calender}
</Popover.Content>
{calender}
</Popover.Content>
</Popover.Portal>
</Popover.Root>
</div>
Expand Down
56 changes: 40 additions & 16 deletions packages/ui/components/form/datepicker/datepicker.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { render, fireEvent } from "@testing-library/react";
import { format } from "date-fns";
import { vi } from "vitest";
import { beforeEach, vi } from "vitest";

import DatePicker from "./DatePicker";

Expand All @@ -9,6 +9,10 @@ const onChangeMock = vi.fn();
describe("Tests for DatePicker Component", () => {
const testDate = new Date("2024-02-20");

beforeEach(() => {
onChangeMock.mockClear();
});

test("Should render correctly with default date", () => {
const testDate = new Date("2024-02-20");
const { getByTestId } = render(<DatePicker date={testDate} />);
Expand All @@ -18,46 +22,66 @@ describe("Tests for DatePicker Component", () => {
});

test("Should show placeholder when no date is provided", () => {
const { getByTestId } = render(<DatePicker date={null as unknown as Date} />);
const { getByTestId } = render(<DatePicker date={null} />);

const dateButton = getByTestId("pick-date");
expect(dateButton).toHaveTextContent("Pick a date");
});

test("Should handle date selection correctly", async () => {
const testDate = new Date("2024-02-20");
const { getByTestId, getAllByRole } = render(<DatePicker date={testDate} onDatesChange={onChangeMock} />);
test("Should handle date selection correctly", () => {
const { getByTestId, getAllByRole } = render(
<DatePicker date={testDate} onDatesChange={onChangeMock} />,
);

const dateButton = getByTestId("pick-date");
fireEvent.click(dateButton);

const gridCells = getAllByRole("gridcell");
const selectedDate = gridCells.find((cell) => {
return cell.getAttribute("tabindex") === "0";
});

expect(selectedDate).toBeTruthy();
await expect(selectedDate).not.toHaveClass("opacity-50");
expect(selectedDate).not.toHaveAttribute("aria-disabled", "true");

fireEvent.click(selectedDate as HTMLElement);
expect(onChangeMock).toHaveBeenCalledTimes(1);
expect(onChangeMock).toHaveBeenCalledWith(expect.any(Date));
});
test("Should respect minDate prop", async () => {
const testDate = new Date("2024-02-20");

test("Should respect minDate prop", () => {
const minDate = new Date("2024-02-19");
const { getByTestId, getAllByRole } = render(
<DatePicker date={testDate} minDate={minDate} onDatesChange={onChangeMock} />
const { getByTestId, getByRole } = render(
<DatePicker
date={testDate}
minDate={minDate}
onDatesChange={onChangeMock}
/>,
);

const dateButton = getByTestId("pick-date");
fireEvent.click(dateButton);

const disabledDates = getAllByRole("gridcell").filter((cell) => cell.classList.contains("opacity-50"));
expect(disabledDates.length).toBeGreaterThan(0);
await expect(disabledDates[0]).toHaveAttribute("disabled");
const dayBeforeMinDate = getByRole("gridcell", {
name: /February 18.*2024/i,
});
const minDateCell = getByRole("gridcell", {
name: /February 19.*2024/i,
});

expect(dayBeforeMinDate).toHaveAttribute("aria-disabled", "true");
expect(minDateCell).not.toHaveAttribute("aria-disabled", "true");
});

test("Should respect disabled prop", () => {
const { getByTestId } = render(<DatePicker date={testDate} disabled={true} />);
const { getByTestId, queryByRole } = render(
<DatePicker date={testDate} disabled={true} />,
);

const dateButton = getByTestId("pick-date");
expect(dateButton.classList.toString()).toContain("disabled:cursor-not-allowed");
expect(dateButton.classList.toString()).toContain("disabled:opacity-30");
expect(dateButton).toBeDisabled();

fireEvent.click(dateButton);
expect(queryByRole("grid")).not.toBeInTheDocument();
});
});
Loading