Files
sui/components/TimezoneInput.svelte
2025-04-13 07:56:23 -07:00

105 lines
2.3 KiB
Svelte

<script lang="ts" module>
const getTimeZonePart = (
timeZone: string,
timeZoneName: Intl.DateTimeFormatOptions['timeZoneName']
) => {
return new Intl.DateTimeFormat('en', {
timeZone,
timeZoneName
})
.formatToParts()
.find((part) => part.type === 'timeZoneName')?.value;
};
// wbr takes a string and adds a <wbr> tag after each forward slash
const wbr = (str: string): string => {
return str.replace(/\//g, '/<wbr />');
};
</script>
<script lang="ts">
import ExpandableCombobox, { type ComboboxItem } from './Combobox.svelte';
let {
label,
name,
value = $bindable(''),
invalidMessage,
required
}: {
label?: string;
name: string;
value?: string;
invalidMessage?: string;
required?: boolean;
} = $props();
const sortedTimeZones = Intl.supportedValuesOf('timeZone')
.map((timeZone) => {
// get short offset (e.g. GMT+1) for the timezone
const offset = getTimeZonePart(timeZone, 'shortOffset');
// if (!offset) return null;
// get long representation (e.g. Pacific Standard Time) for the timezone
const long = getTimeZonePart(timeZone, 'long');
// get short representation (e.g. PST) for the timezone
const short = getTimeZonePart(timeZone, 'short');
return {
timeZone,
offset,
long,
short
};
})
.filter((timeZone) => timeZone !== null)
.sort((a, b) => {
return a.timeZone.localeCompare(b.timeZone);
});
const options: ComboboxItem[] = sortedTimeZones.map((timeZone) => {
const infotext = [...new Set([timeZone.short, timeZone.offset])]
.filter((item) => item !== undefined)
.join(' · ');
return {
value: timeZone.timeZone,
label: `${timeZone.timeZone.replaceAll('_', ' ')}`,
preview: timeZone.long,
infotext: infotext,
render: timezoneLabel
};
});
const optionsMap = options.reduce(
(acc, option) => {
acc[option.value] = option;
return acc;
},
{} as Record<string, ComboboxItem>
);
let timezone = $state<ComboboxItem | undefined>(
optionsMap[Intl.DateTimeFormat().resolvedOptions().timeZone]
);
$effect(() => {
console.log(`timezone="${timezone?.value}"`);
});
</script>
<ExpandableCombobox
{label}
{name}
{invalidMessage}
{required}
bind:value={timezone}
items={options}
matchWidth
placeholder="Select a timezone"
/>
{#snippet timezoneLabel(item: ComboboxItem)}
{@html wbr(item.label ?? 'Missing label')}
{/snippet}