Skip to content

Commit

Permalink
fix: auto add the calendar system to the given locale
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm committed Jan 26, 2025
1 parent afd8f22 commit 408081c
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 18 deletions.
43 changes: 39 additions & 4 deletions packages/core/src/i18n/useLocale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,54 @@ import { computed, MaybeRefOrGetter, toValue } from 'vue';
import { getConfig } from '../config';
import { getDirection } from './getDirection';
import { getWeekInfo } from './getWeekInfo';
import { Maybe } from '../types';
import { Maybe, Reactivify } from '../types';
import { getCalendar } from './getCalendar';
import { Temporal } from '@js-temporal/polyfill';
import { CalendarIdentifier } from '../useCalendar';

export type NumberLocaleExtension = `nu-${string}`;

export interface LocaleExtension {
number: Maybe<NumberLocaleExtension>;
calendar: Maybe<CalendarIdentifier>;
}

/**
* Composable that resolves the currently configured locale and direction.
*/
export function useLocale(localeCode?: MaybeRefOrGetter<Maybe<string>>) {
const localeInstance = computed(() => new Intl.Locale(toValue(localeCode) || getConfig().locale));
export function useLocale(
localeCode?: MaybeRefOrGetter<Maybe<string>>,
extensions: Partial<Reactivify<LocaleExtension>> = {},
) {
const localeString = computed(() => {
let code = toValue(localeCode) || getConfig().locale;
const calExt = toValue(extensions.calendar);
const numExt = toValue(extensions.number);

// Add the base locale extension if it's not already present
if (!code.includes('-u-') && (numExt || calExt)) {
code += '-u-';
}

// Add the number locale extension if it's not already present
if (!code.includes('-nu-') && numExt) {
code += `-nu-${numExt}`;
}

// Add the calendar locale extension if it's not already present
if (!code.includes('-ca-') && calExt) {
code += `-ca-${calExt}`;
}

code = code.replaceAll('--', '-');

return code;
});

const localeInstance = computed(() => new Intl.Locale(localeString.value));
const direction = computed(() => getDirection(localeInstance.value));
const weekInfo = computed(() => getWeekInfo(localeInstance.value));
const calendar = computed(() => new Temporal.Calendar(getCalendar(localeInstance.value)));

const locale = computed(() => localeInstance.value.toString());

return { locale, direction, weekInfo, calendar };
Expand Down
12 changes: 10 additions & 2 deletions packages/core/src/useCalendar/useCalendar.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { computed, InjectionKey, MaybeRefOrGetter, provide, Ref, toValue } from 'vue';
import { Temporal } from '@js-temporal/polyfill';
import { CalendarDay } from './types';
import { CalendarDay, CalendarIdentifier } from './types';
import { normalizeProps } from '../utils/common';
import { Reactivify } from '../types';
import { useDateFormatter, useLocale } from '../i18n';
Expand All @@ -21,6 +21,11 @@ export interface CalendarProps {
* The callback to call when a day is selected.
*/
onDaySelected?: (day: Temporal.PlainDate) => void;

/**
* The calendar type to use for the calendar, e.g. `gregory`, `islamic-umalqura`, etc.
*/
calendar?: CalendarIdentifier;
}

interface CalendarContext {
Expand All @@ -35,7 +40,10 @@ export const CalendarContextKey: InjectionKey<CalendarContext> = Symbol('Calenda

export function useCalendar(_props: Reactivify<CalendarProps, 'onDaySelected'> = {}) {
const props = normalizeProps(_props, ['onDaySelected']);
const { weekInfo, locale, calendar } = useLocale(props.locale);
const { weekInfo, locale, calendar } = useLocale(props.locale, {
calendar: () => toValue(props.calendar),
});

const currentDate = computed(() => toValue(props.currentDate) ?? Temporal.Now.plainDate(calendar.value));

const context: CalendarContext = {
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/useDateTimeField/useDateTimeField.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Maybe, Reactivify } from '../types';
import { CalendarProps } from '../useCalendar';
import { CalendarIdentifier, CalendarProps } from '../useCalendar';
import { createDescribedByProps, isNullOrUndefined, normalizeProps, useUniqId, withRefCapture } from '../utils/common';
import { computed, shallowRef, toValue } from 'vue';
import { exposeField, useFormField } from '../useFormField';
Expand Down Expand Up @@ -29,7 +29,10 @@ export interface DateTimeFieldProps {
export function useDateTimeField(_props: Reactivify<DateTimeFieldProps, 'schema'>) {
const props = normalizeProps(_props, ['schema']);
const controlEl = shallowRef<HTMLInputElement>();
const { locale } = useLocale(props.locale);
const { locale, direction } = useLocale(props.locale, {
calendar: () => toValue(props.formatOptions)?.calendar as CalendarIdentifier,
});

const formatter = useDateFormatter(locale, props.formatOptions);
const controlId = useUniqId(FieldTypePrefixes.DateTimeField);

Expand Down Expand Up @@ -101,6 +104,7 @@ export function useDateTimeField(_props: Reactivify<DateTimeFieldProps, 'schema'
segments,
errorMessageProps,
calendarProps,
direction,
},
field,
);
Expand Down
8 changes: 4 additions & 4 deletions packages/playground/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import DateField from '@/components/DateField.vue';
<DateField
name="date"
label="Date"
locale="ar-EG"
:format-options="{
calendar: 'islamic-umalqura',
day: '2-digit',
month: '2-digit',
month: 'long',
weekday: 'long',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true,
}"
/>
</div>
Expand Down
23 changes: 17 additions & 6 deletions packages/playground/src/components/DateField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,23 @@ import { useDateTimeField, DateTimeFieldProps, DateTimeSegment, useCalendar, Cal
const props = defineProps<DateTimeFieldProps>();
const { controlProps, isTouched, labelProps, errorMessageProps, errorMessage, segments, fieldValue, calendarProps } =
useDateTimeField(props);
const {
controlProps,
isTouched,
labelProps,
errorMessageProps,
errorMessage,
segments,
fieldValue,
calendarProps,
direction,
} = useDateTimeField(props);
const { days, daysOfTheWeek } = useCalendar(calendarProps);
</script>

<template>
<div class="InputDate" :class="{ touched: isTouched }">
<div class="InputDate" :class="{ touched: isTouched }" :dir="direction">
<span class="label" v-bind="labelProps">{{ label }}</span>

{{ fieldValue }}
Expand All @@ -24,7 +33,7 @@ const { days, daysOfTheWeek } = useCalendar(calendarProps);
</div>

<div id="calendar" popover class="bg-zinc-800 px-4 py-4">
<div class="grid grid-cols-7 gap-4">
<div class="grid grid-cols-7 gap-4" :dir="direction">
<div
v-for="day in daysOfTheWeek"
:key="day.long"
Expand All @@ -36,11 +45,13 @@ const { days, daysOfTheWeek } = useCalendar(calendarProps);
<CalendarCell
v-for="day in days"
v-bind="day"
:aria-checked="day.isToday"
class="flex flex-col items-center justify-center aria-checked:bg-blue-600 aria-checked:text-white aria-checked:font-medium"
:aria-checked="day.isSelected"
class="flex flex-col items-center justify-center aria-checked:bg-emerald-600 aria-checked:text-white aria-checked:font-medium border-2"
:class="{
'text-zinc-500': day.isOutsideMonth,
'text-white': !day.isOutsideMonth,
'border-transparent': !day.isToday,
'border-emerald-600': day.isToday,
}"
>
{{ day.dayOfMonth }}
Expand Down

0 comments on commit 408081c

Please sign in to comment.