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/components/auto-grid/+page.svelte

473 lines
17 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<script lang="ts">
import { AutoGrid, type AutoGridProps } from '$uix/eidos/components/auto-grid';
import { Box } from '$uix/eidos/components/box';
import { compileMorfo } from '$uix/morfo';
import { autoGridMorfo } from '@/uix/morfo/components/auto-grid';
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
type TraceEntry = { event: string; family: string; intent?: string; at: number };
let tab = $state<Tab>('live');
let trace = $state<TraceEntry[]>([]);
let stageRef = $state<HTMLElement | null>(null);
// ── Live state ───────────────────────────────────────────────────────
type Mode = 'fluid' | 'fixed';
const modeOptions = ['fluid', 'fixed'] as const;
let mode = $state<Mode>('fluid');
let minChildWidth = $state<number>(180);
let columns = $state<number>(3);
let gap = $state<number>(3);
let itemCount = $state<number>(6);
const autoGridProps = $derived<Partial<AutoGridProps>>(
mode === 'fluid'
? { minChildWidth, gap }
: { columns, gap }
);
const items = $derived(
Array.from({ length: itemCount }, (_, i) => i + 1)
);
// ── Compiled morfo ───────────────────────────────────────────────────
const compiled = compileMorfo(autoGridMorfo);
const partsList = $derived([...compiled.parts.byKebab.values()]);
const events = $derived([...compiled.actions.byName.values()]);
$effect(() => {
const el = stageRef;
if (!el) return;
const obs = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.attributeName !== 'data-event') continue;
const target = m.target as Element;
const ev = target.getAttribute('data-event');
if (!ev) continue;
trace = [
{
event: ev,
family: target.getAttribute('data-event-family') ?? '—',
intent: target.getAttribute('data-event-intent') ?? undefined,
at: Date.now()
},
...trace
].slice(0, 6);
}
});
obs.observe(el, { attributes: true, subtree: true, attributeFilter: ['data-event'] });
return () => obs.disconnect();
});
// ── Snippets ─────────────────────────────────────────────────────────
const computedTemplate = $derived(
mode === 'fluid'
? `repeat(auto-fill, minmax(${minChildWidth}px, 1fr))`
: `repeat(${columns}, minmax(0, 1fr))`
);
const eidosSnippet = $derived(
[
"<script lang='ts'>",
" import { AutoGrid } from '$uix/eidos/components/auto-grid';",
'</' + 'script>',
'',
'<AutoGrid',
mode === 'fluid' && ` minChildWidth={${minChildWidth}}`,
mode === 'fixed' && ` columns={${columns}}`,
gap > 0 && ` gap={${gap}}`,
'>',
' <Card />',
' <Card />',
' <Card />',
'</AutoGrid>'
]
.filter(Boolean)
.join('\n')
);
</script>
<div data-uix-canvas-inner>
<header>
<div data-uix-eyebrow>Layout · AutoGrid</div>
<h1 data-uix-page-title>AutoGrid</h1>
<p data-uix-page-lede>
Responsive grid that fits as many columns as its inline size allows — no media queries. Composes
through <a href="/uix/components/grid">&lt;Grid&gt;</a> (and therefore
<a href="/uix/components/box">&lt;Box&gt;</a>), so every Grid / Box prop still works.
<code>minChildWidth</code> drives the fluid template; <code>columns</code> falls back to a
fixed grid when that's the simpler fit. Eidos-native: no soma backing, no semantic events.
</p>
<div data-uix-page-meta>
<span data-uix-meta-pill>
<span data-uix-meta-key>parts</span>{compiled.parts.order.length}
</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>events</span>0
</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>extends</span>Grid
</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>scope</span>eidos
</span>
</div>
</header>
<!-- Live preview always rendered -->
<div data-uix-stage>
<div data-uix-stage-area bind:this={stageRef}>
<AutoGrid
{...autoGridProps}
style="border: 1px dashed var(--color-border-default); border-radius: var(--radius-md); padding: var(--space-3); inline-size: 100%; min-inline-size: 0;"
>
{#each items as n}
<Box
padding={2}
style="border: 1px solid var(--color-primary-border); border-radius: var(--radius-sm); background: var(--color-primary-track); color: var(--color-primary-text); text-align: center; font-variant-numeric: tabular-nums;"
>
Cell {n}
</Box>
{/each}
</AutoGrid>
</div>
<div data-uix-stage-trace>
<span data-uix-stage-trace-key>trace</span>
<span>{trace.length === 0 ? 'no semantic events' : `${trace.length} event(s)`}</span>
<span style="color: var(--uix-text-faint)">·</span>
<span data-uix-stage-trace-key>mode</span>
<span>{mode}</span>
<span style="margin-inline-start: auto;">
<span data-uix-stage-trace-key>template</span> {computedTemplate}
</span>
</div>
</div>
<div data-uix-tabs role="tablist">
<button data-uix-tab data-active={tab === 'live'} onclick={() => (tab = 'live')}>Live</button>
<button data-uix-tab data-active={tab === 'api'} onclick={() => (tab = 'api')}>
API <span data-uix-tab-count>2 + Grid</span>
</button>
<button data-uix-tab data-active={tab === 'morfo'} onclick={() => (tab = 'morfo')}>
<span data-uix-layer-badge="morfo">morfo</span>
<span data-uix-tab-count>{partsList.length}p · 0e</span>
</button>
<button data-uix-tab data-active={tab === 'sema'} onclick={() => (tab = 'sema')}>
<span data-uix-layer-badge="sema">sema</span>
<span data-uix-tab-count>{events.length}</span>
</button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button>
<button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button>
</div>
{#if tab === 'live'}
<section data-uix-section>
<h2 data-uix-section-title>Controls</h2>
<p data-uix-section-desc>
AutoGrid is eidos-native — no <span data-uix-layer-badge="soma">soma</span> split. The
<code>minChildWidth</code> prop drives <code>repeat(auto-fill, minmax(MIN, 1fr))</code>;
the <code>columns</code> prop falls back to <code>repeat(N, minmax(0, 1fr))</code>. Resize
the viewport to see the fluid mode reflow.
</p>
<div data-uix-subsection-head>
<span data-uix-layer-badge="eidos">eidos</span> props · visual treatment
</div>
<div data-uix-controls>
<label data-uix-control>
<span data-uix-control-label>mode</span>
<span data-uix-chips role="radiogroup">
{#each modeOptions as opt}
<button data-uix-chip data-active={mode === opt} onclick={() => (mode = opt)}
>{opt}</button
>
{/each}
</span>
</label>
{#if mode === 'fluid'}
<label data-uix-control>
<span data-uix-control-label
>minChildWidth <span data-uix-control-hint
>pixels — minimum inline size per item before wrap</span
></span
>
<input type="range" min="120" max="320" step="10" bind:value={minChildWidth} />
<span data-uix-control-value>{minChildWidth}px</span>
</label>
{:else}
<label data-uix-control>
<span data-uix-control-label
>columns <span data-uix-control-hint>fixed column count</span></span
>
<input type="range" min="1" max="6" step="1" bind:value={columns} />
<span data-uix-control-value>{columns}</span>
</label>
{/if}
<label data-uix-control>
<span data-uix-control-label
>gap <span data-uix-control-hint>0–8 → var(--space-N)</span></span
>
<input type="number" min="0" max="8" step="1" bind:value={gap} style="inline-size: 6rem;" />
</label>
<label data-uix-control>
<span data-uix-control-label
>items <span data-uix-control-hint>number of cells in the demo</span></span
>
<input type="range" min="1" max="12" step="1" bind:value={itemCount} />
<span data-uix-control-value>{itemCount}</span>
</label>
</div>
<!-- ── Code snippet (eidos-native — no soma layer) ──────────────── -->
<div data-uix-code>
<div data-uix-code-head>
<span data-uix-layer-badge="eidos">eidos</span>
<span>visual · minChildWidth / columns computes templateColumns</span>
<span data-uix-code-lang>svelte</span>
</div>
<pre><code>{eidosSnippet}</code></pre>
</div>
</section>
{/if}
{#if tab === 'api'}
<section data-uix-section>
<h2 data-uix-section-title>API reference</h2>
<p data-uix-section-desc>
AutoGrid adds two props on top of <code>&lt;Grid&gt;</code>. When both are set,
<code>minChildWidth</code> wins — the fluid template is the AutoGrid use case.
<code>templateColumns</code> from Grid is intentionally not exposed: passing it would
conflict with AutoGrid's own computation; use <code>&lt;Grid&gt;</code> directly when a
custom track list is required.
</p>
<div data-uix-subsection-head>AutoGrid props</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Prop</th><th>Type</th><th>Notes</th></tr></thead>
<tbody>
<tr>
<td class="name">minChildWidth</td>
<td class="type">number | string</td>
<td>
Minimum inline size per item before wrapping. Number → px. Maps to
<code>repeat(auto-fill, minmax(MIN, 1fr))</code>. Responsive.
</td>
</tr>
<tr>
<td class="name">columns</td>
<td class="type">number</td>
<td>
Fixed column count. Maps to <code>repeat(N, minmax(0, 1fr))</code>. Ignored
when <code>minChildWidth</code> is set. Responsive.
</td>
</tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>Inherited from Grid</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Prop</th><th>Type</th><th>Notes</th></tr></thead>
<tbody>
<tr><td class="name">gap / rowGap / columnGap</td><td class="type">number | string</td><td>Track spacing.</td></tr>
<tr><td class="name">rows / templateRows</td><td class="type">number | string</td><td>Forwarded to Grid.</td></tr>
<tr><td class="name">autoRows / autoColumns / autoFlow</td><td class="type">string</td><td>Auto-track sizing.</td></tr>
<tr><td class="name">align / justify / alignContent</td><td class="type">enum</td><td>Container alignment.</td></tr>
<tr><td class="name">…</td><td class="type">GridProps</td><td>See <a href="/uix/components/grid">&lt;Grid&gt;</a>.</td></tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>Inherited from Box</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Prop</th><th>Type</th><th>Notes</th></tr></thead>
<tbody>
<tr>
<td class="name">padding / margin / maxWidth / …</td>
<td class="type">number | string</td>
<td>Every Box prop. See <a href="/uix/components/box">&lt;Box&gt;</a>.</td>
</tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>Reference comparison</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead>
<tr><th>Library</th><th>Closest equivalent</th><th>Difference</th></tr>
</thead>
<tbody>
<tr>
<td class="name">chakra-ui</td>
<td><code>&lt;SimpleGrid&gt;</code></td>
<td>
Chakra uses <code>auto-fit</code>; UIX uses <code>auto-fill</code> so single
items don't stretch across the whole inline size.
</td>
</tr>
<tr>
<td class="name">mantine</td>
<td><code>&lt;SimpleGrid&gt;</code></td>
<td>
Mantine names the spacing prop <code>spacing</code>; UIX keeps
<code>gap</code> consistent with Box / Flex / Grid.
</td>
</tr>
<tr>
<td class="name">radix-themes</td>
<td><code>&lt;Grid columns="repeat(auto-fill,…)" /&gt;</code></td>
<td>
Radix has no fluid SimpleGrid; the consumer writes the template by hand.
</td>
</tr>
</tbody>
</table>
</div>
</section>
{/if}
{#if tab === 'morfo'}
<section data-uix-section>
<h2 data-uix-section-title>Morfo contract</h2>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Field</th><th>Value</th></tr></thead>
<tbody>
<tr><td class="name">name</td><td>{autoGridMorfo.name}</td></tr>
<tr><td class="name">kebab</td><td><code>{autoGridMorfo.kebab}</code></td></tr>
<tr><td class="name">scope</td><td>{autoGridMorfo.scope.join(', ')}</td></tr>
<tr><td class="name">parts</td><td>{partsList.length}</td></tr>
<tr><td class="name">events</td><td>0</td></tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>Parts</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead>
<tr>
<th>kebab</th>
<th>marker</th>
<th>element</th>
<th>archetype</th>
<th>optional</th>
</tr>
</thead>
<tbody>
{#each partsList as part}
<tr>
<td class="name">{part.kebab}</td>
<td><code data-uix-part-marker>[{part.marker}]</code></td>
<td class="type">&lt;{part.defaultElement}&gt;</td>
<td class="type">{part.archetype}</td>
<td class="default">{part.optional ? 'yes' : 'no'}</td>
</tr>
{/each}
</tbody>
</table>
</div>
<p data-uix-section-desc style="margin-top: var(--uix-space-4);">
The Provider part emits only the <code>data-auto-grid</code> marker on top of the Grid
shell — no states, no data properties, no aria attributes, no keyboard. The recipe
delegates every visible style to the Grid recipe; the marker exists so consumers can
target AutoGrid specifically.
</p>
</section>
{/if}
{#if tab === 'sema'}
<section data-uix-section>
<h2 data-uix-section-title>
<span data-uix-layer-badge="sema">sema</span> · events
</h2>
<p data-uix-section-desc>
AutoGrid declares no semantic events. As a passive layout primitive, it only arranges
children; it does not commit, emerge, or react to anything. Components that animate items
on enter/exit should compose AutoGrid with an interactive primitive (popover, drawer,
collapsible) that owns the relevant sema verbs.
</p>
</section>
{/if}
{#if tab === 'recipe'}
<section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2>
<p data-uix-section-desc>
Recipe lives in <code>src/uix/eidos/components/auto-grid/auto-grid.css</code>. AutoGrid
delegates every visible style to the Grid recipe; the file declares only the
<code>[data-auto-grid]</code> marker so consumers can target it directly. The fluid /
fixed template is computed in JS and emitted as <code>--grid-template-columns</code>.
</p>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Selector</th><th>Owner</th><th>Purpose</th></tr></thead>
<tbody>
<tr>
<td class="name"><code>[data-auto-grid]</code></td>
<td><span data-uix-tag data-kind="morfo">morfo</span></td>
<td>Provider marker. Emitted on top of the Grid shell.</td>
</tr>
<tr>
<td class="name"><code>[data-box][data-grid][data-auto-grid]</code></td>
<td><span data-uix-tag data-kind="eidos">eidos</span></td>
<td>
Empty rule (placeholder). Every visible style reads from the Grid recipe
via the <code>--grid-template-columns</code> the component writes inline.
</td>
</tr>
</tbody>
</table>
</div>
</section>
{/if}
{#if tab === 'a11y'}
<section data-uix-section>
<h2 data-uix-section-title>Accessibility</h2>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Concern</th><th>Contract</th></tr></thead>
<tbody>
<tr>
<td class="name">Role</td>
<td>
None implicit. Default element is <code>&lt;div&gt;</code>. If the grid
carries a list (gallery, products), the consumer is responsible for using a
semantic <code>&lt;ul&gt;</code> wrapper around AutoGrid or each child.
</td>
</tr>
<tr>
<td class="name">Label</td>
<td>
Not applicable — AutoGrid has no content semantics. Labels belong to the
items it arranges.
</td>
</tr>
<tr>
<td class="name">Keyboard</td>
<td>AutoGrid is not focusable. Tab order follows DOM order of children.</td>
</tr>
<tr>
<td class="name">Focus visible</td>
<td>AutoGrid does not paint a focus ring.</td>
</tr>
<tr>
<td class="name">Reduced motion</td>
<td>
No motion of its own. Layout reflows are handled by the browser at full
speed regardless of <code>prefers-reduced-motion</code>.
</td>
</tr>
</tbody>
</table>
</div>
</section>
{/if}
</div>

Powered by TurnKey Linux.