diff --git a/apps/v4/app/components/docs/examples/calendar-locale.gts b/apps/v4/app/components/docs/examples/calendar-locale.gts new file mode 100644 index 0000000..97ffb26 --- /dev/null +++ b/apps/v4/app/components/docs/examples/calendar-locale.gts @@ -0,0 +1,27 @@ +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { nl } from 'date-fns/locale'; + +import { Calendar } from '@/components/ui/calendar'; + +import type { DateRange } from '@/components/ui/calendar'; + +export default class CalendarLocale extends Component { + locale = nl; + + @tracked date: Date | undefined = new Date(); + + handleSelect = (date: Date | DateRange | undefined) => { + this.date = date as Date | undefined; + }; + + +} diff --git a/apps/v4/app/components/docs/examples/index.ts b/apps/v4/app/components/docs/examples/index.ts index d4f609d..095b5b6 100644 --- a/apps/v4/app/components/docs/examples/index.ts +++ b/apps/v4/app/components/docs/examples/index.ts @@ -39,6 +39,7 @@ export { default as CalendarBookedDates } from './calendar-booked-dates'; export { default as CalendarCaption } from './calendar-caption'; export { default as CalendarCustomDays } from './calendar-custom-days'; export { default as CalendarDemo } from './calendar-demo'; +export { default as CalendarLocale } from './calendar-locale'; export { default as CalendarPresets } from './calendar-presets'; export { default as CalendarRange } from './calendar-range'; export { default as CalendarTime } from './calendar-time'; diff --git a/apps/v4/app/content/docs/components/calendar.md b/apps/v4/app/content/docs/components/calendar.md index 8cfbd26..f184d19 100644 --- a/apps/v4/app/content/docs/components/calendar.md +++ b/apps/v4/app/content/docs/components/calendar.md @@ -122,6 +122,26 @@ Use `@showWeekNumber` to show week numbers. +### Locale + +Use `@locale` to localize the calendar. Pass a [date-fns locale](https://date-fns.org/docs/Locale) to translate month names, weekday labels, and set the first day of the week. Use `@weekStartsOn` to override the week start day. + +```gts showLineNumbers +import { nl } from 'date-fns/locale'; +``` + +```hbs showLineNumbers + +``` + + + ## API Reference | Argument | Type | Default | Description | @@ -141,6 +161,8 @@ Use `@showWeekNumber` to show week numbers. | `@showWeekNumber` | `boolean` | `false` | Show week numbers. | | `@fixedWeeks` | `boolean` | `false` | Always show 6 weeks. | | `@buttonVariant` | `'default' \| 'ghost' \| 'outline'` | `'ghost'` | Variant for nav buttons. | +| `@locale` | `Locale` | — | date-fns locale for i18n. | +| `@weekStartsOn` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6` | `0` | First day of the week (0=Sun). | | `@startMonth` | `Date` | — | Earliest month navigable. | | `@endMonth` | `Date` | — | Latest month navigable. | | `@class` | `string` | — | Additional CSS classes. | diff --git a/apps/v4/registry/new-york-v4/ui/calendar.gts b/apps/v4/registry/new-york-v4/ui/calendar.gts index 3bc5d6a..bff7464 100644 --- a/apps/v4/registry/new-york-v4/ui/calendar.gts +++ b/apps/v4/registry/new-york-v4/ui/calendar.gts @@ -22,6 +22,7 @@ import { buttonVariants } from '@/components/ui/button'; import { cn } from '@/lib/utils'; import type Owner from '@ember/owner'; +import type { Locale } from 'date-fns/locale'; import ChevronDown from '~icons/lucide/chevron-down'; import ChevronLeft from '~icons/lucide/chevron-left'; @@ -51,6 +52,8 @@ interface CalendarSignature { fixedWeeks?: boolean; class?: string; buttonVariant?: 'default' | 'ghost' | 'outline'; + locale?: Locale; + weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6; timeZone?: string; startMonth?: Date; endMonth?: Date; @@ -88,8 +91,8 @@ interface MonthData { weeks: WeekInfo[]; } -const WEEKDAY_LABELS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']; -const MONTH_NAMES_SHORT = [ +const DEFAULT_WEEKDAY_LABELS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']; +const DEFAULT_MONTH_NAMES_SHORT = [ 'Jan', 'Feb', 'Mar', @@ -122,8 +125,8 @@ function selectedSingle(day: DayInfo): boolean { ); } -function monthNameShort(index: number): string { - return MONTH_NAMES_SHORT[index] ?? ''; +function monthNameShort(index: number, monthNames: string[]): string { + return monthNames[index] ?? ''; } class Calendar extends Component { @@ -153,6 +156,42 @@ class Calendar extends Component { return this.args.buttonVariant ?? 'ghost'; } + get weekStartsOn(): 0 | 1 | 2 | 3 | 4 | 5 | 6 { + return ( + this.args.weekStartsOn ?? this.args.locale?.options?.weekStartsOn ?? 0 + ); + } + + get weekOptions(): { weekStartsOn: 0 | 1 | 2 | 3 | 4 | 5 | 6 } { + return { weekStartsOn: this.weekStartsOn }; + } + + get weekdayLabels(): string[] { + if (this.args.locale) { + const refDate = startOfWeek(new Date(), this.weekOptions); + return Array.from({ length: 7 }, (_, i) => + format(addDays(refDate, i), 'EEEEEE', { + locale: this.args.locale, + }) + ); + } + const wso = this.weekStartsOn; + if (wso === 0) return DEFAULT_WEEKDAY_LABELS; + return [ + ...DEFAULT_WEEKDAY_LABELS.slice(wso), + ...DEFAULT_WEEKDAY_LABELS.slice(0, wso), + ]; + } + + get monthNamesShort(): string[] { + if (this.args.locale) { + return Array.from({ length: 12 }, (_, i) => + format(new Date(2024, i, 1), 'MMM', { locale: this.args.locale }) + ); + } + return DEFAULT_MONTH_NAMES_SHORT; + } + get displayMonth(): Date { if (this.args.month) { return startOfMonth(this.args.month); @@ -190,7 +229,7 @@ class Calendar extends Component { } get monthOptions(): { value: number; label: string }[] { - return MONTH_NAMES_SHORT.map((label, i) => ({ value: i, label })); + return this.monthNamesShort.map((label, i) => ({ value: i, label })); } get months(): MonthData[] { @@ -217,8 +256,8 @@ class Calendar extends Component { buildMonthData(monthDate: Date): MonthData { const monthStart = startOfMonth(monthDate); const monthEnd = endOfMonth(monthDate); - const calendarStart = startOfWeek(monthStart); - const calendarEnd = endOfWeek(monthEnd); + const calendarStart = startOfWeek(monthStart, this.weekOptions); + const calendarEnd = endOfWeek(monthEnd, this.weekOptions); const allDays = eachDayOfInterval({ start: calendarStart, @@ -262,7 +301,7 @@ class Calendar extends Component { }); weeks.push({ - weekNumber: getWeek(weekDays[0]!), + weekNumber: getWeek(weekDays[0]!, this.weekOptions), days, }); } @@ -270,8 +309,10 @@ class Calendar extends Component { return { month: monthDate.getMonth(), year: monthDate.getFullYear(), - label: format(monthDate, 'MMMM yyyy'), - labelShort: format(monthDate, 'MMM'), + label: format(monthDate, 'MMMM yyyy', { + locale: this.args.locale, + }), + labelShort: format(monthDate, 'MMM', { locale: this.args.locale }), weeks, }; } @@ -471,7 +512,7 @@ class Calendar extends Component { - {{monthNameShort monthData.month}} + {{monthNameShort monthData.month this.monthNamesShort}}