diff --git a/apps/docs/src/components/date-picker.stories.tsx b/apps/docs/src/components/date-picker.stories.tsx index 69852b3..f7574c7 100644 --- a/apps/docs/src/components/date-picker.stories.tsx +++ b/apps/docs/src/components/date-picker.stories.tsx @@ -37,7 +37,7 @@ function DatePickerPlayground() { function DatePickerScenarios() { return ( -
+
Empty state @@ -48,6 +48,24 @@ function DatePickerScenarios() { + + + Sunday-first calendar + + Opt into Sunday-through-Saturday when matching US-style calendar conventions. + + + + + + + Guardrailed window @@ -76,7 +94,7 @@ const meta = { docs: { description: { component: - "A single-date picker for launch windows, review deadlines, and operator scheduling surfaces. This first slice stays intentionally narrow: one date, one popover calendar, no range or timezone API." + "A single-date picker for launch windows, review deadlines, and operator scheduling surfaces. It defaults to a Monday-through-Sunday calendar, with an opt-in Sunday-first mode for teams that need that convention. This first slice stays intentionally narrow: one date, one popover calendar, no range or timezone API." } }, layout: "centered" diff --git a/packages/ui/src/components/date-picker.test.tsx b/packages/ui/src/components/date-picker.test.tsx index 287ba3c..12ef1ad 100644 --- a/packages/ui/src/components/date-picker.test.tsx +++ b/packages/ui/src/components/date-picker.test.tsx @@ -86,6 +86,39 @@ describe("DatePicker", () => { expect(screen.getByText("April 2028")).toBeInTheDocument(); }); + it("defaults to a monday-first calendar grid", () => { + render( + + ); + + const firstDay = within(screen.getByRole("grid")).getAllByRole("gridcell")[0]; + + expect(firstDay).toHaveAccessibleName("Feb 23, 2026"); + expect(screen.getByText("Mon")).toBeInTheDocument(); + }); + + it("supports sunday-first calendar grids when requested", () => { + render( + + ); + + const firstDay = within(screen.getByRole("grid")).getAllByRole("gridcell")[0]; + + expect(firstDay).toHaveAccessibleName("Mar 1, 2026"); + expect(screen.getByText("Sun")).toBeInTheDocument(); + }); + it("respects min and max dates", async () => { render( (function DatePicker( @@ -220,6 +227,7 @@ export const DatePicker = forwardRef(function placeholder = "Select date", todayLabel = "Today", value, + weekStartsOn = "monday", ...props }, ref @@ -260,15 +268,18 @@ export const DatePicker = forwardRef(function const monthLabel = formatMonthLabel(visibleMonth, locale); const weekdays = useMemo(() => { const formatter = new Intl.DateTimeFormat(locale, { weekday: "short" }); - const base = new Date(2025, 0, 5); + const base = new Date(2025, 0, 5 + getWeekStartIndex(weekStartsOn)); return Array.from({ length: 7 }, (_, index) => { const day = new Date(base); day.setDate(base.getDate() + index); return formatter.format(day); }); - }, [locale]); - const days = useMemo(() => buildMonthGrid(visibleMonth), [visibleMonth]); + }, [locale, weekStartsOn]); + const days = useMemo( + () => buildMonthGrid(visibleMonth, weekStartsOn), + [visibleMonth, weekStartsOn] + ); const yearOptions = useMemo( () => getYearOptions(visibleMonth, selectedDate), [selectedDate, visibleMonth] @@ -420,161 +431,165 @@ export const DatePicker = forwardRef(function -
-
- -
- -
- - - -
- -
- -
-
- -

{monthLabel}

- -
- {weekdays.map((weekday) => ( - {weekday} - ))} -
- -
- {days.map((day, index) => { - const outside = day.getMonth() !== visibleMonth.getMonth(); - const selected = sameDay(day, selectedDate); - const isToday = sameDay(day, today); - const dayDisabled = isDateDisabled(day, minDate, maxDate); - - return ( - - ); - })} -
+ + +
-
- -
+
+ + + +
+ +
+ +
+
+ +
+

{monthLabel}

+ +
+ {weekdays.map((weekday) => ( + {weekday} + ))} +
+ +
+ {days.map((day, index) => { + const outside = day.getMonth() !== visibleMonth.getMonth(); + const selected = sameDay(day, selectedDate); + const isToday = sameDay(day, today); + const dayDisabled = isDateDisabled(day, minDate, maxDate); + + return ( + + ); + })} +
+
+ +
- +
+ + +
diff --git a/packages/ui/src/components/date-picker.variants.ts b/packages/ui/src/components/date-picker.variants.ts index 7f5eda1..01918cf 100644 --- a/packages/ui/src/components/date-picker.variants.ts +++ b/packages/ui/src/components/date-picker.variants.ts @@ -8,13 +8,13 @@ export const datePickerFieldVariants = cva("relative"); export const datePickerTriggerVariants = cva("w-full"); export const datePickerContentVariants = cva([ - "relative z-50 w-[21rem] overflow-hidden rounded-[var(--ui-panel-radius)] border border-[var(--ui-panel-border)] bg-[var(--ui-panel-bg)] p-0 text-[var(--color-card-foreground)] shadow-[var(--ui-panel-shadow)] outline-none", + "relative z-50 w-[22rem] overflow-hidden rounded-[var(--ui-panel-radius)] border border-[var(--ui-panel-border)] bg-[var(--ui-panel-bg)] text-[var(--color-card-foreground)] shadow-[var(--ui-panel-shadow)] outline-none", "[border-width:var(--ui-panel-border-width)] backdrop-blur-[var(--ui-panel-backdrop-blur)]", "data-[state=open]:motion-enter-rise data-[state=closed]:motion-exit-drop" ]); export const datePickerHeaderVariants = cva( - "grid gap-3 sm:grid-cols-[auto_minmax(0,1fr)_auto]" + "grid items-center gap-3 sm:grid-cols-[auto_minmax(0,1fr)_auto]" ); export const datePickerNavigationVariants = cva("flex items-center gap-2"); @@ -26,7 +26,7 @@ export const datePickerMonthLabelVariants = cva( ); export const datePickerCaptionVariants = cva( - "px-1 text-sm leading-6 text-[var(--color-muted-foreground)]" + "text-sm leading-6 text-[var(--color-muted-foreground)]" ); export const datePickerWeekdayVariants = cva( @@ -50,5 +50,5 @@ export const datePickerDayVariants = cva( ); export const datePickerFooterVariants = cva( - "flex flex-wrap items-center justify-between gap-3 border-t border-[var(--ui-panel-border)] pt-3" + "mt-1 flex flex-wrap items-center justify-between gap-3 border-t border-[var(--ui-panel-border)] pt-3" ); diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 0f7e18f..7ccb899 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -148,7 +148,11 @@ export { dataTableTableVariants, dataTableToolbarVariants } from "./components/data-table.variants"; -export { DatePicker, type DatePickerProps } from "./components/date-picker"; +export { + DatePicker, + type DatePickerProps, + type DatePickerWeekStartsOn +} from "./components/date-picker"; export { datePickerContentVariants, datePickerDayVariants,