Skip to content
Merged
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
7 changes: 7 additions & 0 deletions .changeset/feat-date-picker-show-week-numbers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@zag-js/date-utils": minor
"@zag-js/date-picker": minor
---

Add `showWeekNumbers` support to the date picker. When enabled, the day view displays an ISO 8601 week number column.
Added `getWeekOfYear` to date-utils for week number calculation.
2 changes: 1 addition & 1 deletion examples/next-ts/pages/compositions
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,26 +67,26 @@
]
},
"dependencies": {
"@axe-core/playwright": "4.11.0",
"@axe-core/playwright": "4.11.1",
"@changesets/changelog-github": "0.5.2",
"@changesets/cli": "2.29.8",
"@commitlint/cli": "20.4.1",
"@commitlint/config-conventional": "20.4.1",
"@eslint/eslintrc": "3.3.3",
"@internationalized/date": "3.11.0",
"@octokit/rest": "22.0.1",
"@playwright/test": "1.58.1",
"@playwright/test": "1.58.2",
"@swc/core": "1.15.11",
"@types/jsdom": "^27.0.0",
"@types/node": "25.2.0",
"@types/node": "25.2.3",
"@types/signale": "1.4.7",
"@typescript-eslint/eslint-plugin": "8.54.0",
"@typescript-eslint/parser": "8.54.0",
"@typescript-eslint/eslint-plugin": "8.55.0",
"@typescript-eslint/parser": "8.55.0",
"axe-core": "4.11.1",
"commitlint": "20.4.1",
"cross-env": "^10.1.0",
"dotenv": "17.2.3",
"eslint": "9.39.2",
"dotenv": "17.3.1",
"eslint": "10.0.0",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-prettier": "5.5.5",
Expand All @@ -112,5 +112,5 @@
"engines": {
"node": ">=18.0.0"
},
"packageManager": "pnpm@10.28.2"
"packageManager": "pnpm@10.29.3"
}
2 changes: 1 addition & 1 deletion packages/frameworks/svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@sveltejs/package": "2.5.7",
"clean-package": "2.2.0",
"clsx": "^2.1.1",
"svelte": "5.49.1",
"svelte": "5.50.3",
"vitest": "4.0.18"
},
"peerDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/frameworks/vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"@zag-js/utils": "workspace:*"
},
"devDependencies": {
"vue": "3.5.27",
"vue": "3.5.28",
"clean-package": "2.2.0"
},
"peerDependencies": {
Expand Down
31 changes: 31 additions & 0 deletions packages/machines/date-picker/src/date-picker.connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
getTodayDate,
getUnitDuration,
getWeekDays,
getWeekOfYear,
getYearsRange,
isDateEqual,
isDateOutsideRange,
Expand All @@ -39,6 +40,7 @@ import type {
TableCellProps,
TableCellState,
TableProps,
WeekNumberCellProps,
} from "./date-picker.types"
import {
adjustStartAndEndDate,
Expand Down Expand Up @@ -254,13 +256,18 @@ export function connect<T extends PropTypes>(
readOnly,
inline: !!prop("inline"),
numOfMonths: prop("numOfMonths"),
showWeekNumbers: !!prop("showWeekNumbers"),
selectionMode: prop("selectionMode"),
maxSelectedDates,
isMaxSelected,
view: context.get("view"),
getRangePresetValue(preset) {
return getDateRangePreset(preset, locale, timeZone)
},
getWeekNumber(week: DateValue[]) {
const firstDay = week[0]
return firstDay ? getWeekOfYear(firstDay, locale) : 0
},
getDaysInWeek(week, from = startValue) {
return getDaysInWeek(week, from, locale, startOfWeek)
},
Expand Down Expand Up @@ -548,6 +555,30 @@ export function connect<T extends PropTypes>(
})
},

getWeekNumberHeaderCellProps(props = {}) {
const { view = "day" } = props
return normalize.element({
...parts.tableCell.attrs,
scope: "col",
"aria-label": translations.weekColumnHeader,
"data-view": view,
"data-disabled": dataAttr(disabled),
})
},

getWeekNumberCellProps(props: WeekNumberCellProps) {
const { weekIndex, week } = props
const weekNumber = week[0] ? getWeekOfYear(week[0], locale) : 0
return normalize.element({
...parts.tableCell.attrs,
role: "rowheader",
"aria-label": translations.weekNumberCell?.(weekNumber),
"data-view": "day",
"data-week-index": weekIndex,
"data-disabled": dataAttr(disabled),
})
},

getDayTableCellState,

getDayTableCellProps(props) {
Expand Down
1 change: 1 addition & 0 deletions packages/machines/date-picker/src/date-picker.props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const props = createProps<DatePickerProps>()([
"readOnly",
"required",
"selectionMode",
"showWeekNumbers",
"startOfWeek",
"timeZone",
"translations",
Expand Down
22 changes: 22 additions & 0 deletions packages/machines/date-picker/src/date-picker.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ export interface IntlTranslations {
trigger: (open: boolean) => string
content: string
placeholder: (locale: string) => { year: string; month: string; day: string }
weekColumnHeader?: string | undefined
weekNumberCell?: ((weekNumber: number) => string) | undefined
}

export type ElementIds = Partial<{
Expand Down Expand Up @@ -197,6 +199,10 @@ export interface DatePickerProps extends DirectionProperty, CommonProperties {
* This renders the calendar with 6 weeks instead of 5 or 6.
*/
fixedWeeks?: boolean | undefined
/**
* Whether to show the week number column in the day view.
*/
showWeekNumbers?: boolean | undefined
/**
* Function called when the value changes.
*/
Expand Down Expand Up @@ -442,6 +448,11 @@ export interface DayTableCellProps {
visibleRange?: VisibleRange | undefined
}

export interface WeekNumberCellProps {
weekIndex: number
week: DateValue[]
}

export interface DayTableCellState {
invalid: boolean
disabled: boolean
Expand Down Expand Up @@ -557,6 +568,10 @@ export interface DatePickerApi<T extends PropTypes = PropTypes> {
* The number of months to display
*/
numOfMonths: number
/**
* Whether the week number column is shown in the day view
*/
showWeekNumbers: boolean
/**
* The selection mode (single, multiple, or range)
*/
Expand All @@ -573,6 +588,10 @@ export interface DatePickerApi<T extends PropTypes = PropTypes> {
* The current view of the date picker
*/
view: DateView
/**
* Returns the ISO 8601 week number (1-53) for the given week (array of dates).
*/
getWeekNumber: (week: DateValue[]) => number
/**
* Returns an array of days in the week index counted from the provided start date, or the first visible date if not given.
*/
Expand Down Expand Up @@ -730,6 +749,9 @@ export interface DatePickerApi<T extends PropTypes = PropTypes> {
getTableBodyProps: (props?: TableProps) => T["element"]
getTableRowProps: (props?: TableProps) => T["element"]

getWeekNumberHeaderCellProps: (props?: TableProps) => T["element"]
getWeekNumberCellProps: (props: WeekNumberCellProps) => T["element"]

getDayTableCellProps: (props: DayTableCellProps) => T["element"]
getDayTableCellTriggerProps: (props: DayTableCellProps) => T["element"]

Expand Down
4 changes: 4 additions & 0 deletions packages/machines/date-picker/src/date-picker.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ export const defaultTranslations: IntlTranslations = {
monthSelect: "Select month",
yearSelect: "Select year",
clearTrigger: "Clear selected dates",
weekColumnHeader: "Wk",
weekNumberCell(weekNumber) {
return `Week ${weekNumber}`
},
}

// 0 – day, 1 – month, 2 – year;
Expand Down
1 change: 1 addition & 0 deletions packages/machines/date-picker/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export type {
VisibleRangeChangeDetails,
VisibleRangeText,
WeekDay,
WeekNumberCellProps,
YearGridProps,
ZonedDateTime,
PresetTriggerValue,
Expand Down
19 changes: 19 additions & 0 deletions packages/utilities/date-utils/src/date-month.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,22 @@ export function getMonthNames(locale: string, format: Intl.DateTimeFormatOptions
}
return monthNames
}

export function getWeekOfYear(date: DateValue, locale: string): number {
const mondayOfWeek = startOfWeek(date, locale, "mon")
const year = mondayOfWeek.year
const jan4 = mondayOfWeek.set({ month: 1, day: 4 })
const week1Monday = startOfWeek(jan4, locale, "mon")

const julianMonday = mondayOfWeek.calendar.toJulianDay(mondayOfWeek)
const julianWeek1 = week1Monday.calendar.toJulianDay(week1Monday)

if (julianMonday >= julianWeek1) {
return 1 + Math.floor((julianMonday - julianWeek1) / 7)
}

const prevJan4 = mondayOfWeek.set({ year: year - 1, month: 1, day: 4 })
const prevWeek1Monday = startOfWeek(prevJan4, locale, "mon")
const julianPrevWeek1 = prevWeek1Monday.calendar.toJulianDay(prevWeek1Monday)
return 1 + Math.floor((julianMonday - julianPrevWeek1) / 7)
}
19 changes: 19 additions & 0 deletions packages/utilities/date-utils/tests/get-week-of-year.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { CalendarDate } from "@internationalized/date"
import { getWeekOfYear } from "../src"

describe("getWeekOfYear", () => {
it("returns 1 for Jan 4 (always in week 1 per ISO 8601)", () => {
expect(getWeekOfYear(new CalendarDate(2023, 1, 4), "en")).toBe(1)
expect(getWeekOfYear(new CalendarDate(2024, 1, 4), "en")).toBe(1)
})

it("returns week 1 for a date in the first week of the year", () => {
expect(getWeekOfYear(new CalendarDate(2023, 1, 2), "en")).toBe(1)
})

it("returns 52 or 53 for last days of December", () => {
const week52Or53 = getWeekOfYear(new CalendarDate(2023, 12, 31), "en")
expect(week52Or53).toBeGreaterThanOrEqual(52)
expect(week52Or53).toBeLessThanOrEqual(53)
})
})
Loading
Loading