Files
sui/src/lib/TimezoneInput.svelte
2025-07-17 11:35:47 -07:00

109 lines
2.5 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 type { ClassValue } from 'svelte/elements';
import Combobox, { type ComboboxOption } from './Combobox.svelte';
interface Props {
label?: string;
name?: string;
value?: string;
invalidMessage?: string;
required?: boolean;
class?: ClassValue | null | undefined;
}
let {
label,
name,
value = $bindable(''),
invalidMessage = 'Please select a timezone',
required,
class: classValue
}: Props = $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: ComboboxOption[] = 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, ComboboxOption>
);
let timezone = $state<ComboboxOption | undefined>(
optionsMap[Intl.DateTimeFormat().resolvedOptions().timeZone]
);
</script>
<Combobox
{label}
{name}
{invalidMessage}
{required}
bind:value={timezone}
{options}
matchWidth
placeholder="Select a timezone"
class={classValue}
/>
{#snippet timezoneLabel(item: ComboboxOption)}
<!-- eslint-disable svelte/no-at-html-tags -->
{@html wbr(item.label ?? 'Missing label')}
{/snippet}