You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/web/routes/uix/+layout@.svelte

790 lines
26 KiB

<script lang="ts">
import { onDestroy } from 'svelte';
import { createActiveUix, setActiveUix } from '$active-uix';
import { createActivePrefsDomProjection, standardPrefsDimensions } from '$prefs';
import { page } from '$app/state';
import { Soma } from '$soma/core/soma.svelte';
import { siumLangs } from '$sium/langs/langs';
import { secsLangs } from '$libs/secs';
import { ActiveEidos } from '$uix/eidos';
import { dialogSema } from '$uix/sema/components/dialog';
import { drawerSema } from '$uix/sema/components/drawer';
import { editableSema } from '$uix/sema/components/editable';
import { fileUploadSema } from '$uix/sema/components/file-upload';
import { formSema } from '$uix/sema/components/form';
import { numberFieldSema } from '$uix/sema/components/number-field';
import { paginationSema } from '$uix/sema/components/pagination';
import { passwordFieldSema } from '$uix/sema/components/password-field';
import { popoverSema } from '$uix/sema/components/popover';
import { radioGroupSema } from '$uix/sema/components/radio-group';
import { ratingGroupSema } from '$uix/sema/components/rating-group';
import { searchFieldSema } from '$uix/sema/components/search-field';
import { selectSema } from '$uix/sema/components/select';
import { sliderSema } from '$uix/sema/components/slider';
import { splitterSema } from '$uix/sema/components/splitter';
import { stepperSema } from '$uix/sema/components/stepper';
import { tabsSema } from '$uix/sema/components/tabs';
import { tagGroupSema } from '$uix/sema/components/tag-group';
import { tagsInputSema } from '$uix/sema/components/tags-input';
import { textareaSema } from '$uix/sema/components/textarea';
import { toastSema } from '$uix/sema/components/toast';
import { toolbarSema } from '$uix/sema/components/toolbar';
import { wordsSema } from '$uix/sema/components/words';
import { accordionSema } from '$uix/sema/components/accordion';
import { checkboxSema } from '$uix/sema/components/checkbox';
import { calendarSema } from '$uix/sema/components/calendar';
import { colorPickerSema } from '$uix/sema/components/color-picker';
import { comboboxSema } from '$uix/sema/components/combobox';
import { dateFieldSema } from '$uix/sema/components/date-field';
import { Popover } from '$uix/eidos/components/popover';
import '@/uix/eidos/index.css';
import './uix.css';
let { children } = $props();
type Language = 'es' | 'en' | 'fr' | 'de';
type Currency = 'EUR' | 'USD' | 'GBP' | 'JPY';
type UnitSystem = 'metric' | 'imperial';
type NumberFormat = 'es-ES' | 'en-US' | 'en-GB' | 'de-DE';
const docsPrefsSchema = standardPrefsDimensions({
languages: ['es', 'en', 'fr', 'de'] satisfies readonly Language[],
locales: ['es-ES', 'en-US', 'en-GB', 'de-DE'] satisfies readonly NumberFormat[],
currencies: ['EUR', 'USD', 'GBP', 'JPY'] satisfies readonly Currency[],
defaults: {
language: 'es',
locale: 'es-ES',
currency: 'EUR'
}
});
const uix = createActiveUix({
langs: { schema: { sium: siumLangs, secs: secsLangs }, defaultLocale: 'es' as const },
prefs: { schema: docsPrefsSchema },
events: {
sound: true,
haptic: true,
components: [
accordionSema,
calendarSema,
checkboxSema,
colorPickerSema,
comboboxSema,
dateFieldSema,
dialogSema,
drawerSema,
editableSema,
fileUploadSema,
formSema,
numberFieldSema,
paginationSema,
passwordFieldSema,
popoverSema,
radioGroupSema,
ratingGroupSema,
searchFieldSema,
selectSema,
sliderSema,
splitterSema,
stepperSema,
tabsSema,
tagGroupSema,
tagsInputSema,
textareaSema,
toastSema,
toolbarSema,
wordsSema
]
}
});
setActiveUix(uix);
Soma.create();
const prefsProjection = createActivePrefsDomProjection({ prefs: uix.prefs, dom: uix.dom });
type Section =
| { kind: 'top'; slug: string; label: string }
| { kind: 'group'; heading: string; items: { slug: string; label: string; tag?: string }[] };
// Information architecture — single source of truth for sidebar +
// breadcrumb. Each leaf maps to a route under `/uix/...`.
const nav: Section[] = [
{
kind: 'group',
heading: 'Get started',
items: [
{ slug: '/uix', label: 'Overview' },
{ slug: '/uix/architecture', label: 'Architecture' },
{ slug: '/uix/getting-started', label: 'Installation' }
]
},
{
kind: 'group',
heading: 'Foundations',
items: [
{ slug: '/uix/tokens', label: 'Design tokens' },
{ slug: '/uix/morfo', label: 'Morfo · contracts' },
{ slug: '/uix/sema', label: 'Sema · perception' },
{ slug: '/uix/eidos', label: 'Eidos · visual layer' }
]
},
{
kind: 'group',
heading: 'Controls',
items: [
{ slug: '/uix/components/toggle', label: 'Toggle' },
{ slug: '/uix/components/toggle-group', label: 'Toggle group' },
{ slug: '/uix/components/switch', label: 'Switch' },
{ slug: '/uix/components/checkbox', label: 'Checkbox' },
{ slug: '/uix/components/radio-group', label: 'Radio group' },
{ slug: '/uix/components/rating-group', label: 'Rating group' },
{ slug: '/uix/components/slider', label: 'Slider' },
{ slug: '/uix/components/collapsible', label: 'Collapsible' },
{ slug: '/uix/components/accordion', label: 'Accordion' },
{ slug: '/uix/components/tabs', label: 'Tabs' },
{ slug: '/uix/components/toolbar', label: 'Toolbar' }
]
},
{
kind: 'group',
heading: 'Forms',
items: [
{ slug: '/uix/components/form', label: 'Form' },
{ slug: '/uix/components/field', label: 'Field' },
{ slug: '/uix/components/editable', label: 'Editable' },
{ slug: '/uix/components/words', label: 'Words' },
{ slug: '/uix/components/number-field', label: 'Number field' },
{ slug: '/uix/components/search-field', label: 'Search field' },
{ slug: '/uix/components/password-field', label: 'Password field' },
{ slug: '/uix/components/textarea', label: 'Text area' },
{ slug: '/uix/components/select', label: 'Select' },
{ slug: '/uix/components/combobox', label: 'Combobox' },
{ slug: '/uix/components/listbox', label: 'Listbox' },
{ slug: '/uix/components/grid-list', label: 'Grid list' },
{ slug: '/uix/components/calendar', label: 'Calendar' },
{ slug: '/uix/components/range-calendar', label: 'Range calendar' },
{ slug: '/uix/components/month-grid', label: 'Month grid' },
{ slug: '/uix/components/year-grid', label: 'Year grid' },
{ slug: '/uix/components/date-field', label: 'Date field' },
{ slug: '/uix/components/date-range-field', label: 'Date range field' },
{ slug: '/uix/components/date-picker', label: 'Date picker' },
{ slug: '/uix/components/date-range-picker', label: 'Date range picker' },
{ slug: '/uix/components/time-field', label: 'Time field' },
{ slug: '/uix/components/time-range-field', label: 'Time range field' },
{ slug: '/uix/components/time-picker', label: 'Time picker' },
{ slug: '/uix/components/time-range-picker', label: 'Time range picker' },
{ slug: '/uix/components/color-field', label: 'Color field' },
{ slug: '/uix/components/color-picker', label: 'Color picker' },
{ slug: '/uix/components/stepper', label: 'Stepper' },
{ slug: '/uix/components/tag-group', label: 'Tag group' },
{ slug: '/uix/components/tags-input', label: 'Tags input' },
{ slug: '/uix/components/pin-input', label: 'Pin input' },
{ slug: '/uix/components/file-upload', label: 'File upload' }
]
},
{
kind: 'group',
heading: 'Navigation',
items: [
{ slug: '/uix/components/breadcrumb', label: 'Breadcrumb' },
{ slug: '/uix/components/pagination', label: 'Pagination' },
{ slug: '/uix/components/navigation-menu', label: 'Navigation menu' }
]
},
{
kind: 'group',
heading: 'Media',
items: [
{ slug: '/uix/components/avatar', label: 'Avatar' },
{ slug: '/uix/components/icon', label: 'Icon' },
{ slug: '/uix/components/carousel', label: 'Carousel' }
]
},
{
kind: 'group',
heading: 'Status',
items: [
{ slug: '/uix/components/progress', label: 'Progress' },
{ slug: '/uix/components/meter', label: 'Meter' },
{ slug: '/uix/components/spinner', label: 'Spinner' },
{ slug: '/uix/components/skeleton', label: 'Skeleton' }
]
},
{
kind: 'group',
heading: 'Data',
items: [
{ slug: '/uix/components/table', label: 'Table' },
{ slug: '/uix/components/tree-view', label: 'Tree view' },
{ slug: '/uix/components/tree-grid', label: 'Tree grid' },
{ slug: '/uix/components/feed', label: 'Feed' },
{ slug: '/uix/components/virtual-list', label: 'Virtual list' },
{ slug: '/uix/components/virtual-grid', label: 'Virtual grid' }
]
},
{
kind: 'group',
heading: 'Utilities',
items: [
{ slug: '/uix/components/announce', label: 'Announce' },
{ slug: '/uix/components/clipboard', label: 'Clipboard' },
{ slug: '/uix/components/drag-drop', label: 'Drag & drop' }
]
},
{
kind: 'group',
heading: 'Overlays',
items: [
{ slug: '/uix/components/dialog', label: 'Dialog' },
{ slug: '/uix/components/alert-dialog', label: 'Alert dialog' },
{ slug: '/uix/components/drawer', label: 'Drawer' },
{ slug: '/uix/components/popover', label: 'Popover' },
{ slug: '/uix/components/dropdown-menu', label: 'Dropdown menu' },
{ slug: '/uix/components/context-menu', label: 'Context menu' },
{ slug: '/uix/components/menubar', label: 'Menubar' },
{ slug: '/uix/components/command', label: 'Command' },
{ slug: '/uix/components/link-preview', label: 'Link preview' },
{ slug: '/uix/components/toast', label: 'Toast' },
{ slug: '/uix/components/tooltip', label: 'Tooltip' }
]
},
{
kind: 'group',
heading: 'Typography',
items: [
{ slug: '/uix/components/text', label: 'Text' },
{ slug: '/uix/components/heading', label: 'Heading' },
{ slug: '/uix/components/display', label: 'Display' },
{ slug: '/uix/components/code', label: 'Code' },
{ slug: '/uix/components/code-block', label: 'CodeBlock' },
{ slug: '/uix/components/kbd', label: 'Kbd' },
{ slug: '/uix/components/mark', label: 'Mark' },
{ slug: '/uix/components/highlight', label: 'Highlight' },
{ slug: '/uix/components/link', label: 'Link' }
]
},
{
kind: 'group',
heading: 'Layout',
items: [
{ slug: '/uix/components/box', label: 'Box' },
{ slug: '/uix/components/flex', label: 'Flex' },
{ slug: '/uix/components/grid', label: 'Grid' },
{ slug: '/uix/components/stack', label: 'Stack' },
{ slug: '/uix/components/group', label: 'Group' },
{ slug: '/uix/components/wrap', label: 'Wrap' },
{ slug: '/uix/components/container', label: 'Container' },
{ slug: '/uix/components/section', label: 'Section' },
{ slug: '/uix/components/aspect-ratio', label: 'AspectRatio' },
{ slug: '/uix/components/auto-grid', label: 'AutoGrid' },
{ slug: '/uix/components/banner', label: 'Banner' },
{ slug: '/uix/components/float', label: 'Float' },
{ slug: '/uix/components/scroll-area', label: 'ScrollArea' },
{ slug: '/uix/components/separator', label: 'Separator' },
{ slug: '/uix/components/splitter', label: 'Splitter' }
]
}
];
const componentCount = $derived(
nav
.filter((s): s is Extract<Section, { kind: 'group' }> => s.kind === 'group')
.filter((g) => ['Controls', 'Media', 'Overlays'].includes(g.heading))
.reduce((acc, g) => acc + g.items.length, 0)
);
const current = $derived(page.url.pathname.replace(/\/$/, '') || '/uix');
// ── Theme ─────────────────────────────────────────────────────────────
let theme = $state<'light' | 'dark'>('light');
const eidosModeListeners = new Set<(value: 'light' | 'dark') => void>();
const activeEidos = ActiveEidos.create({
applyDom: true,
theme: 'base',
modeSource: {
get: () => theme,
onChange(handler) {
eidosModeListeners.add(handler);
return () => eidosModeListeners.delete(handler);
}
}
});
onDestroy(() => {
prefsProjection.dispose();
activeEidos.dispose();
uix.dispose();
});
$effect(() => {
if (typeof document === 'undefined') return;
const stored = localStorage.getItem('uix-docs-theme');
if (stored === 'dark' || stored === 'light') {
theme = stored;
return;
}
const prefersDark = window.matchMedia?.('(prefers-color-scheme: dark)').matches;
theme = prefersDark ? 'dark' : 'light';
});
$effect(() => {
if (typeof localStorage === 'undefined') return;
localStorage.setItem('uix-docs-theme', theme);
});
$effect(() => {
const currentTheme = theme;
for (const listener of eidosModeListeners) listener(currentTheme);
});
let search = $state('');
// ── Topbar prefs ──────────────────────────────────────────────────────
// All persisted to localStorage. The sema toggles will wire to the
// engine's channels once those expose runtime setEnabled APIs; today
// they store preference + reflect it in the UI.
let dir = $state<'ltr' | 'rtl'>('ltr');
let language = $state<Language>('es');
let currency = $state<Currency>('EUR');
let units = $state<UnitSystem>('metric');
let numberFormat = $state<NumberFormat>('es-ES');
let soundOn = $state(true);
let motionOn = $state(true);
let hapticOn = $state(true);
const LANGUAGE_LABELS: Record<Language, string> = {
es: 'Español',
en: 'English',
fr: 'Français',
de: 'Deutsch'
};
const CURRENCY_LABELS: Record<Currency, string> = {
EUR: 'Euro',
USD: 'US Dollar',
GBP: 'British Pound',
JPY: 'Japanese Yen'
};
const UNIT_LABELS: Record<UnitSystem, string> = { metric: 'Metric', imperial: 'Imperial' };
const NUMBER_LABELS: Record<NumberFormat, string> = {
'es-ES': 'Spain · 1.234,56',
'en-US': 'US · 1,234.56',
'en-GB': 'UK · 1,234.56',
'de-DE': 'Germany · 1.234,56'
};
// Persist all topbar prefs in one bag.
$effect(() => {
if (typeof localStorage === 'undefined') return;
const stored = localStorage.getItem('uix-docs-prefs');
if (!stored) return;
try {
const p = JSON.parse(stored);
if (p.dir === 'ltr' || p.dir === 'rtl') dir = p.dir;
if (p.language) language = p.language;
if (p.currency) currency = p.currency;
if (p.units) units = p.units;
if (p.numberFormat) numberFormat = p.numberFormat;
if (typeof p.soundOn === 'boolean') soundOn = p.soundOn;
if (typeof p.motionOn === 'boolean') motionOn = p.motionOn;
if (typeof p.hapticOn === 'boolean') hapticOn = p.hapticOn;
} catch {
/* ignore corrupt prefs blob */
}
});
$effect(() => {
if (typeof localStorage === 'undefined') return;
const blob = { dir, language, currency, units, numberFormat, soundOn, motionOn, hapticOn };
localStorage.setItem('uix-docs-prefs', JSON.stringify(blob));
});
$effect(() => {
uix.prefs.setIntent('language', language);
uix.prefs.setIntent('direction', dir);
uix.prefs.setIntent('locale', numberFormat);
uix.prefs.setIntent('currency', currency);
uix.prefs.setIntent('unitSystem', units);
uix.prefs.setIntent('sound', soundOn ? 'allow' : 'reduce');
uix.prefs.setIntent('motion', motionOn ? 'allow' : 'reduce');
uix.prefs.setIntent('haptic', hapticOn ? 'allow' : 'reduce');
});
const filteredNav = $derived.by(() => {
const q = search.trim().toLowerCase();
if (!q) return nav;
return nav
.map((s) => {
if (s.kind !== 'group') return s;
const items = s.items.filter((i) => i.label.toLowerCase().includes(q));
return items.length ? { ...s, items } : null;
})
.filter((s): s is Section => s !== null);
});
const currentBreakpoint = $derived(uix.dom.currentBreakpoint.current);
const compactShell = $derived(
currentBreakpoint === 'base' || currentBreakpoint === 'sm' || currentBreakpoint === 'md'
);
const phoneShell = $derived(currentBreakpoint === 'base');
</script>
<div
data-uix-docs
data-theme={theme}
data-mode={theme}
data-uix-breakpoint={currentBreakpoint}
data-uix-compact={compactShell ? 'true' : 'false'}
data-uix-phone={phoneShell ? 'true' : 'false'}
>
<div data-uix-shell>
<header data-uix-topbar>
<a
href="/uix"
data-uix-brand
data-sveltekit-preload-code="hover"
data-sveltekit-preload-data="hover"
>
<span data-uix-brand-mark>U</span>
<span>UIX</span>
<span data-uix-brand-version>v0.1</span>
</a>
<nav data-uix-topbar-nav aria-label="Primary">
<a
href="/uix"
data-sveltekit-preload-code="hover"
data-sveltekit-preload-data="hover"
data-active={current === '/uix' ||
current.startsWith('/uix/architecture') ||
current.startsWith('/uix/getting-started')
? 'true'
: 'false'}>Docs</a
>
<a
href="/uix/components"
data-sveltekit-preload-code="hover"
data-sveltekit-preload-data="hover"
data-active={current.startsWith('/uix/components') ? 'true' : 'false'}>Components</a
>
<a href="https://github.com/" target="_blank" rel="noreferrer">GitHub ↗</a>
</nav>
<div data-uix-topbar-tools>
<!-- DIR: simple toggle. RTL flips the entire document direction
so all eidos components (which use logical properties)
reverse correctly. -->
<button
type="button"
data-uix-icon-btn
title={dir === 'ltr' ? 'Switch to RTL' : 'Switch to LTR'}
aria-label="Toggle direction"
aria-pressed={dir === 'rtl'}
onclick={() => (dir = dir === 'ltr' ? 'rtl' : 'ltr')}
>
{#if dir === 'ltr'}
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M5 12h14M13 6l6 6-6 6" />
</svg>
{:else}
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M19 12H5M11 6l-6 6 6 6" />
</svg>
{/if}
</button>
<!-- LANG: short uppercase code + popover with full language list -->
<Popover>
<Popover.Trigger>
<span data-uix-label-btn>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<circle cx="12" cy="12" r="10" />
<path d="M2 12h20M12 2a15 15 0 0 1 0 20M12 2a15 15 0 0 0 0 20" />
</svg>
{language.toUpperCase()}
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
data-caret
aria-hidden="true"
>
<path d="m6 9 6 6 6-6" />
</svg>
</span>
</Popover.Trigger>
<Popover.Content side="bottom" align="end" sideOffset={6}>
<div data-uix-menu>
<div data-uix-menu-section>
<div data-uix-menu-label>Language</div>
{#each Object.entries(LANGUAGE_LABELS) as [code, label]}
<button
type="button"
data-uix-menu-item
data-checked={language === code ? 'true' : 'false'}
onclick={() => (language = code as Language)}
>
<span>{label}</span>
<span data-uix-menu-item-meta>{code.toUpperCase()}</span>
</button>
{/each}
</div>
</div>
</Popover.Content>
</Popover>
<!-- FORMATS: currency + units + number formatting in one popover -->
<Popover>
<Popover.Trigger>
<span data-uix-label-btn>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M12 1v22M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
</svg>
Formats
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
data-caret
aria-hidden="true"
>
<path d="m6 9 6 6 6-6" />
</svg>
</span>
</Popover.Trigger>
<Popover.Content side="bottom" align="end" sideOffset={6}>
<div data-uix-menu style="min-inline-size: 240px;">
<div data-uix-menu-section>
<div data-uix-menu-label>Currency</div>
{#each Object.entries(CURRENCY_LABELS) as [code, label]}
<button
type="button"
data-uix-menu-item
data-checked={currency === code ? 'true' : 'false'}
onclick={() => (currency = code as Currency)}
>
<span>{label}</span>
<span data-uix-menu-item-meta>{code}</span>
</button>
{/each}
</div>
<div data-uix-menu-section>
<div data-uix-menu-label>Units</div>
{#each Object.entries(UNIT_LABELS) as [code, label]}
<button
type="button"
data-uix-menu-item
data-checked={units === code ? 'true' : 'false'}
onclick={() => (units = code as UnitSystem)}
>
<span>{label}</span>
<span data-uix-menu-item-meta
>{code === 'metric' ? 'KM · KG · °C' : 'MI · LB · °F'}</span
>
</button>
{/each}
</div>
<div data-uix-menu-section>
<div data-uix-menu-label>Numbers</div>
{#each Object.entries(NUMBER_LABELS) as [code, label]}
<button
type="button"
data-uix-menu-item
data-checked={numberFormat === code ? 'true' : 'false'}
onclick={() => (numberFormat = code as NumberFormat)}
>
<span>{label}</span>
</button>
{/each}
</div>
</div>
</Popover.Content>
</Popover>
<!-- SEMANTICS: sound / motion / haptic toggles. These will hook
into the sema engine channels' enabled state once the
channels expose runtime setEnabled APIs; today they
persist preference + reflect in the UI. -->
<Popover>
<Popover.Trigger>
<span data-uix-label-btn>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path
d="M12 2a4 4 0 0 1 4 4v6a4 4 0 0 1-8 0V6a4 4 0 0 1 4-4zM19 10v2a7 7 0 0 1-14 0v-2M12 19v3"
/>
</svg>
Semantics
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
data-caret
aria-hidden="true"
>
<path d="m6 9 6 6 6-6" />
</svg>
</span>
</Popover.Trigger>
<Popover.Content side="bottom" align="end" sideOffset={6}>
<div data-uix-menu style="min-inline-size: 240px;">
<div data-uix-menu-section>
<div data-uix-menu-label>Perceptual channels</div>
<label data-uix-menu-toggle>
<input type="checkbox" bind:checked={soundOn} />
Sound
<span data-uix-menu-toggle-meta>{soundOn ? 'on' : 'off'}</span>
</label>
<label data-uix-menu-toggle>
<input type="checkbox" bind:checked={motionOn} />
Motion
<span data-uix-menu-toggle-meta>{motionOn ? 'on' : 'off'}</span>
</label>
<label data-uix-menu-toggle>
<input type="checkbox" bind:checked={hapticOn} />
Haptic
<span data-uix-menu-toggle-meta>{hapticOn ? 'on' : 'off'}</span>
</label>
</div>
</div>
</Popover.Content>
</Popover>
<!-- THEME -->
<button
type="button"
data-uix-icon-btn
title={theme === 'light' ? 'Switch to dark theme' : 'Switch to light theme'}
aria-label="Toggle theme"
onclick={() => (theme = theme === 'light' ? 'dark' : 'light')}
>
{#if theme === 'light'}
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
</svg>
{:else}
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<circle cx="12" cy="12" r="4" />
<path
d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"
/>
</svg>
{/if}
</button>
</div>
</header>
<aside data-uix-rail>
<div data-uix-rail-search>
<input type="search" placeholder="Search…" bind:value={search} />
</div>
{#each filteredNav as section}
{#if section.kind === 'group'}
<div data-uix-rail-group>
<div data-uix-rail-group-head>
<span>{section.heading}</span>
<span data-uix-rail-group-count>{section.items.length}</span>
</div>
<ul>
{#each section.items as item}
<li>
<a
href={item.slug}
data-uix-rail-link
data-sveltekit-preload-code="hover"
data-sveltekit-preload-data="hover"
data-active={current === item.slug ? 'true' : 'false'}
>
{item.label}
{#if item.tag}
<span data-uix-rail-link-tag>{item.tag}</span>
{/if}
</a>
</li>
{/each}
</ul>
</div>
{/if}
{/each}
{#if filteredNav.length === 0}
<div data-uix-rail-group-head>No matches for "{search}"</div>
{/if}
</aside>
<main data-uix-canvas>
{@render children?.()}
</main>
</div>
</div>
<style>
/* This layout owns its UIX context directly so components declared in
* the same layout hydrate with ActiveUix/Soma available. */
</style>

Powered by TurnKey Linux.