fix(redesign): EntitySelect for language pickers + portal Timezone picker
- Template editors (notification & command) now use EntitySelect for locale switching and default to the configured primary locale instead of always 'en' when opening, editing, or cloning a config. - LocaleSelector's add-flow uses EntitySelect for catalog pick; custom BCP-47 codes (e.g. de-CH) keep a small dedicated input. - TimezoneSelector dropdown was being clipped by Card's overflow:hidden and backdrop-filter; portalled to <body> with an overlay backdrop and styled as a centered modal palette (same pattern as EntitySelect). - Removed top padding on the timezone scroll list so sticky region group headers no longer leak rows above them. - Extracted shared locale catalog to lib/locales.ts.
This commit is contained in:
@@ -21,6 +21,8 @@
|
||||
import Modal from '$lib/components/Modal.svelte';
|
||||
import JinjaEditor from '$lib/components/JinjaEditor.svelte';
|
||||
import CollapsibleSlot from '$lib/components/CollapsibleSlot.svelte';
|
||||
import EntitySelect, { type EntityItem } from '$lib/components/EntitySelect.svelte';
|
||||
import { getLocaleMeta } from '$lib/locales';
|
||||
import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
|
||||
import { highlightFromUrl } from '$lib/highlight';
|
||||
import { globalProviderFilter } from '$lib/stores/provider-filter.svelte';
|
||||
@@ -71,7 +73,24 @@
|
||||
let showPreviewFor = $state<Set<string>>(new Set());
|
||||
|
||||
let LOCALES = $derived(supportedLocalesCache.items);
|
||||
let activeLocale = $state<string>('en');
|
||||
let primaryLocale = $derived(LOCALES[0] || 'en');
|
||||
let activeLocale = $state<string>('');
|
||||
const localeItems = $derived<EntityItem[]>(LOCALES.map((code, i) => {
|
||||
const m = getLocaleMeta(code);
|
||||
return {
|
||||
value: code,
|
||||
label: m.native,
|
||||
desc: i === 0 ? `${code.toUpperCase()} · ${t('locales.primary')}` : code.toUpperCase(),
|
||||
};
|
||||
}));
|
||||
/**
|
||||
* Promote primary to be the active locale once the supported-locales
|
||||
* cache loads (covers initial mount before openNew/edit ran). Without
|
||||
* this, opening a form before fetch resolves would stay on '' / 'en'.
|
||||
*/
|
||||
$effect(() => {
|
||||
if (!activeLocale && LOCALES.length > 0) activeLocale = primaryLocale;
|
||||
});
|
||||
|
||||
function toggleSlot(key: string) {
|
||||
const next = new Set(expandedSlots);
|
||||
@@ -272,7 +291,7 @@
|
||||
function openNew() {
|
||||
form = defaultForm();
|
||||
if (providerTypes.length > 0) form.provider_type = providerTypes[0];
|
||||
editing = null; showForm = true; activeLocale = 'en'; slotPreview = {}; slotErrors = {}; dateFormatPreview = {}; expandedSlots = new Set(); showPreviewFor = new Set(); slotFilter = '';
|
||||
editing = null; showForm = true; activeLocale = primaryLocale; slotPreview = {}; slotErrors = {}; dateFormatPreview = {}; expandedSlots = new Set(); showPreviewFor = new Set(); slotFilter = '';
|
||||
refreshDateFormatPreview();
|
||||
}
|
||||
function edit(c: TemplateConfig) {
|
||||
@@ -285,7 +304,7 @@
|
||||
date_format: c.date_format || '%d.%m.%Y, %H:%M UTC',
|
||||
date_only_format: c.date_only_format || '%d.%m.%Y',
|
||||
};
|
||||
editing = c.id; showForm = true; activeLocale = 'en';
|
||||
editing = c.id; showForm = true; activeLocale = primaryLocale;
|
||||
slotPreview = {}; slotErrors = {}; dateFormatPreview = {};
|
||||
expandedSlots = new Set(); showPreviewFor = new Set(); slotFilter = '';
|
||||
setTimeout(() => refreshAllPreviews(), 100);
|
||||
@@ -372,7 +391,7 @@
|
||||
};
|
||||
editing = null;
|
||||
showForm = true;
|
||||
activeLocale = 'en';
|
||||
activeLocale = primaryLocale;
|
||||
slotPreview = {};
|
||||
slotErrors = {};
|
||||
setTimeout(() => refreshAllPreviews(), 100);
|
||||
@@ -447,15 +466,19 @@
|
||||
<IconGridSelect items={previewTargetTypeItems()} bind:value={previewTargetType} columns={2} />
|
||||
</div>
|
||||
|
||||
<!-- Locale tabs -->
|
||||
<div class="flex items-center gap-1 mb-3 border-b border-[var(--color-border)]">
|
||||
{#each LOCALES as loc}
|
||||
<button type="button"
|
||||
class="px-3 py-1.5 text-xs font-medium rounded-t-md transition-colors {activeLocale === loc ? 'bg-[var(--color-primary)] text-[var(--color-primary-foreground)]' : 'text-[var(--color-muted-foreground)] hover:bg-[var(--color-muted)]'}"
|
||||
onclick={() => { activeLocale = loc; refreshAllPreviews(); }}>
|
||||
{loc.toUpperCase()}
|
||||
</button>
|
||||
{/each}
|
||||
<!-- Language picker -->
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<span class="text-xs font-medium text-[var(--color-muted-foreground)] shrink-0">
|
||||
{t('templateConfig.language')}
|
||||
</span>
|
||||
<div class="flex-1 max-w-xs">
|
||||
<EntitySelect
|
||||
items={localeItems}
|
||||
value={activeLocale}
|
||||
size="sm"
|
||||
onselect={(v) => { activeLocale = (v as string) || primaryLocale; refreshAllPreviews(); }}
|
||||
/>
|
||||
</div>
|
||||
{#if form.provider_type}
|
||||
<button type="button" onclick={resetAllToDefaults}
|
||||
title={t('templateConfig.resetAllToDefaults')}
|
||||
|
||||
Reference in New Issue
Block a user