partially refactor components to ui package
This commit is contained in:
104
components/TimezoneInput.svelte
Normal file
104
components/TimezoneInput.svelte
Normal file
@@ -0,0 +1,104 @@
|
||||
<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}
|
||||
Reference in New Issue
Block a user