Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Format last qol for next #203

Merged
merged 2 commits into from
Jan 9, 2024
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
49 changes: 49 additions & 0 deletions packages/svelte-ux/src/lib/components/LanguageSelect.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<script lang="ts">
import Button from './Button.svelte';
import Menu from './Menu.svelte';
import MenuItem from './MenuItem.svelte';
import { cls } from '../utils/styles';
import { getSettings } from './settings';
const { locale } = getSettings();

let open = false;

type Language = {
name: string;
code: string;
flag: string;
};

export let languagesDemo: Language[] = [
{ name: 'English', code: 'en', flag: '🇺🇸' },
{ name: 'Français', code: 'fr', flag: '🇫🇷' },
// add more for the demo
];
$: languageSelected = languagesDemo.find((c) => c.code === $locale)!;
</script>

<Button on:click={() => (open = !open)}>
jycouet marked this conversation as resolved.
Show resolved Hide resolved
{languageSelected.flag}
<Menu bind:open on:close={() => (open = false)} offset={4} explicitClose resize>
<div class="grid gap-2 p-2 border-b border-surface-content/10">
{#each languagesDemo as language}
<MenuItem
on:click={() => {
languageSelected = language;
locale.set(language.code);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is changing the locale, but when I'm on the DatePicker I don't see any change.

Copy link
Owner

Choose a reason for hiding this comment

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

hmm.. I noticed this as well (same for the other Date* components).. Have you looked into it yet?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

A bit but not much. Couldn't find what's going on. I'll go off now, maybe you have a chance to look at this?

}}
class={cls(
'bg-surface-100 text-surface-content font-semibold border shadow',
languageSelected === language && 'ring-2 ring-surface-content'
)}
>
{language.flag} - {language.name}
</MenuItem>
{/each}
</div>

<div class="p-2 grid grid-cols-[auto,1fr] gap-2 items-center text-xs">
<span class="font-medium">Affect dates & numbers formats</span>
</div>
</Menu>
</Button>
74 changes: 46 additions & 28 deletions packages/svelte-ux/src/lib/utils/date.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
import { describe, it, expect } from 'vitest';
import {
PeriodType,
formatDate,
getMonthDaysByWeek,
localToUtcDate,
utcToLocalDate,
DayOfWeek,
formatIntl,
type CustomIntlDateTimeFormatOptions,
type FormatDateOptions,
DateToken,
formatDateWithLocale,
} from './date';
import { getSettings } from '$lib/components';
import { format, formatWithLocale } from '.';
import { formatWithLocale } from '.';
import { createLocaleSettings, defaultLocale } from './locale';
import {
PeriodType,
type FormatDateOptions,
DayOfWeek,
type CustomIntlDateTimeFormatOptions,
DateToken,
} from './date_types';
import { getWeekStartsOnFromIntl } from './dateInternal';

const DATE = '2023-11-21'; // "good" default date as the day (21) is bigger than 12 (number of months). And november is a good month1 (because why not?)
const dt_2M_2d = new Date(2023, 10, 21);
const dt_2M_1d = new Date(2023, 10, 7);
const dt_1M_1d = new Date(2023, 2, 7);
const dt_first = new Date(2024, 1, 1);

const dt_1M_1d_time_pm = new Date(2023, 2, 7, 14, 2, 3, 4);
const dt_1M_1d_time_am = new Date(2023, 2, 7, 1, 2, 3, 4);
Expand All @@ -30,21 +33,21 @@ const fr = createLocaleSettings({
dates: {
ordinalSuffixes: {
one: 'er',
two: '',
few: '',
other: '',
},
},
},
});

describe('formatDate()', () => {
it('should return empty string for null or undefined date', () => {
// @ts-expect-error
Copy link
Owner

Choose a reason for hiding this comment

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

Should this error?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, because the number of args doesn't match anymore.

expect(formatDate(null)).equal('');
// @ts-expect-error
expect(formatDate(undefined)).equal('');
});

it('should return empty string for invalid date', () => {
// @ts-expect-error
expect(formatDate('invalid date')).equal('');
});

Expand Down Expand Up @@ -125,7 +128,7 @@ describe('formatDate()', () => {
}
});

describe('should format date for PeriodType.WeekSun / Mon', () => {
describe('should format date for PeriodType.WeekSun / Mon no mather the locale', () => {
const combi = [
[PeriodType.WeekSun, 'short', defaultLocale, '11/19 - 11/25'],
[PeriodType.WeekSun, 'short', fr, '19/11 - 25/11'],
Expand All @@ -138,32 +141,28 @@ describe('formatDate()', () => {
for (const c of combi) {
const [periodType, variant, locales, expected] = c;
it(c.toString(), () => {
expect(formatDateWithLocale(locales, DATE, periodType, { variant, locales })).equal(
expected
);
expect(formatDateWithLocale(locales, DATE, periodType, { variant })).equal(expected);
});
}
});

describe('should format date for PeriodType.Week', () => {
describe('should format date for PeriodType.Week with the good weekstarton locale', () => {
const combi = [
[PeriodType.Week, 'short', defaultLocale, DayOfWeek.Sunday, '11/19 - 11/25'],
[PeriodType.Week, 'short', fr, DayOfWeek.Sunday, '19/11 - 25/11'],
[PeriodType.Week, 'long', defaultLocale, DayOfWeek.Sunday, '11/19/2023 - 11/25/2023'],
[PeriodType.Week, 'long', fr, DayOfWeek.Sunday, '19/11/2023 - 25/11/2023'],

[PeriodType.Week, 'short', defaultLocale, DayOfWeek.Monday, '11/20 - 11/26'],
[PeriodType.Week, 'short', fr, DayOfWeek.Monday, '20/11 - 26/11'],
[PeriodType.Week, 'long', defaultLocale, DayOfWeek.Monday, '11/20/2023 - 11/26/2023'],
[PeriodType.Week, 'long', fr, DayOfWeek.Monday, '20/11/2023 - 26/11/2023'],
[PeriodType.Week, 'short', defaultLocale, '11/19 - 11/25'],
[PeriodType.Week, 'short', fr, '20/11 - 26/11'],
[PeriodType.Week, 'long', defaultLocale, '11/19/2023 - 11/25/2023'],
[PeriodType.Week, 'long', fr, '20/11/2023 - 26/11/2023'],

[PeriodType.Week, 'short', defaultLocale, '11/19 - 11/25'],
[PeriodType.Week, 'short', fr, '20/11 - 26/11'],
[PeriodType.Week, 'long', defaultLocale, '11/19/2023 - 11/25/2023'],
[PeriodType.Week, 'long', fr, '20/11/2023 - 26/11/2023'],
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updates test to show Week with automatic start of the week. 🎉

Copy link
Owner

Choose a reason for hiding this comment

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

Nice!

] as const;

for (const c of combi) {
const [periodType, variant, locales, weekStartsOn, expected] = c;
const [periodType, variant, locales, expected] = c;
it(c.toString(), () => {
expect(formatDateWithLocale(locales, DATE, periodType, { variant, weekStartsOn })).equal(
expected
);
expect(formatDateWithLocale(locales, DATE, periodType, { variant })).equal(expected);
});
}
});
Expand Down Expand Up @@ -286,6 +285,7 @@ describe('formatDate()', () => {
for (const c of combi) {
const [variant, locales] = c;
it(c.toString(), () => {
// @ts-expect-error
expect(formatDateWithLocale(locales, DATE, undefined, { variant })).equal(expected);
});
}
Expand Down Expand Up @@ -316,6 +316,7 @@ describe('formatIntl() tokens', () => {
[dt_2M_2d, { dateStyle: 'medium', withOrdinal: true }, ['Nov 21st, 2023', '21 nov. 2023']],
[dt_2M_2d, { dateStyle: 'short' }, ['11/21/23', '21/11/2023']],
[dt_1M_1d, { dateStyle: 'short' }, ['3/7/23', '07/03/2023']],
[dt_first, DateToken.DayOfMonth_withOrdinal, ['1st', '1er']],

// time
[dt_1M_1d_time_pm, [DateToken.Hour_numeric, DateToken.Minute_numeric], ['2:02 PM', '14:02']],
Expand Down Expand Up @@ -500,3 +501,20 @@ describe('getMonthDaysByWeek()', () => {
`);
});
});

describe('getWeekStartsOnFromIntl() tokens', () => {
it('by default, sunday', () => {
const val = getWeekStartsOnFromIntl();
expect(val).toBe(DayOfWeek.Sunday);
});

it('For en it should be synday', () => {
const val = getWeekStartsOnFromIntl('en');
expect(val).toBe(DayOfWeek.Sunday);
});

it('For fr it should be monday', () => {
const val = getWeekStartsOnFromIntl('fr');
expect(val).toBe(DayOfWeek.Monday);
});
});
74 changes: 41 additions & 33 deletions packages/svelte-ux/src/lib/utils/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,15 @@ import {

import { hasKeyOf } from '../types/typeGuards';
import { chunk } from './array';
import type { DateRange } from './dateRange';
import { PeriodType, DayOfWeek, DateToken } from './date_types';
import {
PeriodType,
DayOfWeek,
DateToken,
type SelectedDate,
type CustomIntlDateTimeFormatOptions,
type FormatDateOptions,
type DateFormatVariantPreset,
} from './date_types';
import { defaultLocale, type LocaleSettings } from './locale';

export * from './date_types';
Expand Down Expand Up @@ -676,6 +683,7 @@ export function formatDateWithLocale(
}

const weekStartsOn = options.weekStartsOn ?? settings.formats.dates.weekStartsOn;

const { day, dayTime, timeOnly, week, month, monthsYear, year } = settings.formats.dates.presets;

if (periodType === PeriodType.Week) {
Expand Down Expand Up @@ -723,80 +731,80 @@ export function formatDateWithLocale(

switch (periodType) {
case PeriodType.Custom:
return formatIntl(settings, date, options.custom);
return formatIntl(settings, date, options.custom!);

case PeriodType.Day:
return formatIntl(settings, date, rv(day));
return formatIntl(settings, date, rv(day!)!);

case PeriodType.DayTime:
return formatIntl(settings, date, rv(dayTime));
return formatIntl(settings, date, rv(dayTime!)!);

case PeriodType.TimeOnly:
return formatIntl(settings, date, rv(timeOnly));
return formatIntl(settings, date, rv(timeOnly!)!);

case PeriodType.WeekSun:
return range(settings, date, 0, rv(week));
return range(settings, date, 0, rv(week!)!);
case PeriodType.WeekMon:
return range(settings, date, 1, rv(week));
return range(settings, date, 1, rv(week!)!);
case PeriodType.WeekTue:
return range(settings, date, 2, rv(week));
return range(settings, date, 2, rv(week!)!);
case PeriodType.WeekWed:
return range(settings, date, 3, rv(week));
return range(settings, date, 3, rv(week!)!);
case PeriodType.WeekThu:
return range(settings, date, 4, rv(week));
return range(settings, date, 4, rv(week!)!);
case PeriodType.WeekFri:
return range(settings, date, 5, rv(week));
return range(settings, date, 5, rv(week!)!);
case PeriodType.WeekSat:
return range(settings, date, 6, rv(week));
return range(settings, date, 6, rv(week!)!);

case PeriodType.Month:
return formatIntl(settings, date, rv(month));
return formatIntl(settings, date, rv(month!)!);

case PeriodType.MonthYear:
return formatIntl(settings, date, rv(monthsYear));
return formatIntl(settings, date, rv(monthsYear!)!);

case PeriodType.Quarter:
return [
formatIntl(settings, startOfQuarter(date), rv(month)),
formatIntl(settings, endOfQuarter(date), rv(monthsYear)),
formatIntl(settings, startOfQuarter(date), rv(month!)!),
formatIntl(settings, endOfQuarter(date), rv(monthsYear!)!),
].join(' - ');

case PeriodType.CalendarYear:
return formatIntl(settings, date, rv(year));
return formatIntl(settings, date, rv(year!)!);

case PeriodType.FiscalYearOctober:
const fDate = new Date(getFiscalYear(date), 0, 1);
return formatIntl(settings, fDate, rv(year));
return formatIntl(settings, fDate, rv(year!)!);

case PeriodType.BiWeek1Sun:
return range(settings, date, 0, rv(week), 1);
return range(settings, date, 0, rv(week!)!, 1);
case PeriodType.BiWeek1Mon:
return range(settings, date, 1, rv(week), 1);
return range(settings, date, 1, rv(week!)!, 1);
case PeriodType.BiWeek1Tue:
return range(settings, date, 2, rv(week), 1);
return range(settings, date, 2, rv(week!)!, 1);
case PeriodType.BiWeek1Wed:
return range(settings, date, 3, rv(week), 1);
return range(settings, date, 3, rv(week!)!, 1);
case PeriodType.BiWeek1Thu:
return range(settings, date, 4, rv(week), 1);
return range(settings, date, 4, rv(week!)!, 1);
case PeriodType.BiWeek1Fri:
return range(settings, date, 5, rv(week), 1);
return range(settings, date, 5, rv(week!)!, 1);
case PeriodType.BiWeek1Sat:
return range(settings, date, 6, rv(week), 1);
return range(settings, date, 6, rv(week!)!, 1);

case PeriodType.BiWeek2Sun:
return range(settings, date, 0, rv(week), 2);
return range(settings, date, 0, rv(week!)!, 2);
case PeriodType.BiWeek2Mon:
return range(settings, date, 1, rv(week), 2);
return range(settings, date, 1, rv(week!)!, 2);
case PeriodType.BiWeek2Tue:
return range(settings, date, 2, rv(week), 2);
return range(settings, date, 2, rv(week!)!, 2);
case PeriodType.BiWeek2Wed:
return range(settings, date, 3, rv(week), 2);
return range(settings, date, 3, rv(week!)!, 2);
case PeriodType.BiWeek2Thu:
return range(settings, date, 4, rv(week), 2);
return range(settings, date, 4, rv(week!)!, 2);
case PeriodType.BiWeek2Fri:
return range(settings, date, 5, rv(week), 2);
return range(settings, date, 5, rv(week!)!, 2);
case PeriodType.BiWeek2Sat:
return range(settings, date, 6, rv(week), 2);
return range(settings, date, 6, rv(week!)!, 2);

default:
return formatISO(date);
Expand Down
11 changes: 11 additions & 0 deletions packages/svelte-ux/src/lib/utils/dateInternal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { DayOfWeek } from './date_types';

export function getWeekStartsOnFromIntl(locales?: string): DayOfWeek {
if (!locales) {
return DayOfWeek.Sunday;
}

const info = new Intl.Locale(locales);
// @ts-ignore
return (info.weekInfo.firstDay ?? 0) % 7; // (in Intl, sunday is 7 not 0, so we need to mod 7)
}
Comment on lines +1 to +11
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't know why, but if I put this function in date.ts, I can't call it from locale.ts!

I have a message like: getWeekStartsOnFromIntl is not a function.

Copy link
Owner

Choose a reason for hiding this comment

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

hmm, that is odd. I'll try to look at it locally, but we can merge as is for now.

3 changes: 2 additions & 1 deletion packages/svelte-ux/src/lib/utils/dateRange.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { startOfDay, isLeapYear, isAfter, isBefore, subYears } from 'date-fns';

import { getDateFuncsByPeriodType, PeriodType } from './date';
import { getDateFuncsByPeriodType } from './date';
import { PeriodType } from './date_types';

export type DateRange = {
from: Date | null;
Expand Down
12 changes: 7 additions & 5 deletions packages/svelte-ux/src/lib/utils/date_types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { DateRange } from './dateRange';

export type SelectedDate = Date | Date[] | DateRange | null;

export type Period = {
Expand Down Expand Up @@ -110,15 +112,15 @@ export enum DateToken {
}

export type OrdinalSuffixes = {
one: string;
two: string;
few: string;
other: string;
one?: string;
two?: string;
few?: string;
other?: string;
zero?: string;
many?: string;
};
export type DateFormatVariant = 'short' | 'default' | 'long';
type DateFormatVariantPreset = {
export type DateFormatVariantPreset = {
short?: CustomIntlDateTimeFormatOptions;
default?: CustomIntlDateTimeFormatOptions;
long?: CustomIntlDateTimeFormatOptions;
Expand Down
Loading