|
|
<script lang="ts">
|
|
|
import {
|
|
|
QrCode,
|
|
|
type QrCodeSize,
|
|
|
type QrCodeCellShape,
|
|
|
type ErrorCorrection
|
|
|
} from '$uix/eidos/components/qr-code';
|
|
|
import { compileMorfo } from '$uix/morfo';
|
|
|
import { qrCodeMorfo } from '@/uix/morfo/components/qr-code';
|
|
|
|
|
|
type Tab = 'live' | 'examples' | 'api' | 'morfo' | 'recipe' | 'a11y';
|
|
|
let tab = $state<Tab>('live');
|
|
|
|
|
|
let value = $state('https://uix.dev/qr-code');
|
|
|
let size = $state<QrCodeSize>('lg');
|
|
|
let cellShape = $state<QrCodeCellShape>('square');
|
|
|
let errorCorrection = $state<ErrorCorrection>('M');
|
|
|
let margin = $state(4);
|
|
|
let color = $state<string | undefined>(undefined);
|
|
|
let withLogo = $state(false);
|
|
|
|
|
|
const sizes: QrCodeSize[] = ['xs', 'sm', 'md', 'lg', 'xl'];
|
|
|
const shapes: QrCodeCellShape[] = ['square', 'rounded', 'dots'];
|
|
|
const eccs: ErrorCorrection[] = ['L', 'M', 'Q', 'H'];
|
|
|
const colors: { label: string; value: string | undefined }[] = [
|
|
|
{ label: 'default', value: undefined },
|
|
|
{ label: 'violet', value: '#7c3aed' },
|
|
|
{ label: 'teal', value: '#0d9488' },
|
|
|
{ label: 'rose', value: '#e11d48' }
|
|
|
];
|
|
|
|
|
|
const compiled = compileMorfo(qrCodeMorfo);
|
|
|
const partsList = $derived([...compiled.parts.byKebab.values()]);
|
|
|
|
|
|
const snippet = $derived(
|
|
|
[
|
|
|
"<script lang='ts'>",
|
|
|
" import { QrCode } from '$uix/eidos/components/qr-code';",
|
|
|
'</' + 'script>',
|
|
|
'',
|
|
|
'<QrCode',
|
|
|
` value="${value}"`,
|
|
|
size !== 'md' && ` size="${size}"`,
|
|
|
cellShape !== 'square' && ` cellShape="${cellShape}"`,
|
|
|
errorCorrection !== 'M' && ` errorCorrection="${errorCorrection}"`,
|
|
|
margin !== 4 && ` margin={${margin}}`,
|
|
|
color && ` color="${color}"`,
|
|
|
'>',
|
|
|
withLogo && ' {#snippet logo()}<img src={brand} alt="" />{/snippet}',
|
|
|
' <QrCode.DownloadTrigger format="png">Download</QrCode.DownloadTrigger>',
|
|
|
'</QrCode>'
|
|
|
]
|
|
|
.filter(Boolean)
|
|
|
.join('\n')
|
|
|
);
|
|
|
</script>
|
|
|
|
|
|
<svelte:head>
|
|
|
<meta name="perm-axes" content="" />
|
|
|
</svelte:head>
|
|
|
|
|
|
{#snippet brandLogo()}
|
|
|
<svg viewBox="0 0 24 24" fill={color ?? '#18181b'} aria-hidden="true">
|
|
|
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
|
|
|
</svg>
|
|
|
{/snippet}
|
|
|
|
|
|
<div data-uix-canvas-inner>
|
|
|
<header>
|
|
|
<div data-uix-eyebrow>Display · QR code</div>
|
|
|
<h1 data-uix-page-title>QR code</h1>
|
|
|
<p data-uix-page-lede>
|
|
|
Themeable QR rendered from an <strong>own zero-dependency encoder</strong> (`$libs/qr` —
|
|
|
ISO/IEC 18004, versions 1–40, all ECC levels). Cell shapes (square / rounded / dots) keep
|
|
|
the function patterns square so any scanner locks on; an optional logo clears the centre and
|
|
|
auto-boosts ECC to H; export to PNG / SVG. No npm encoder dependency.
|
|
|
</p>
|
|
|
<div data-uix-page-meta>
|
|
|
<span data-uix-meta-pill><span data-uix-meta-key>encoder</span>own · 0-dep</span>
|
|
|
<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>versions</span>1–40</span>
|
|
|
<span data-uix-meta-pill><span data-uix-meta-key>ecc</span>L · M · Q · H</span>
|
|
|
</div>
|
|
|
</header>
|
|
|
|
|
|
<div data-uix-stage>
|
|
|
<div data-uix-stage-area style="display: flex; align-items: center; gap: 1.5rem; flex-wrap: wrap;">
|
|
|
<QrCode {value} {size} {cellShape} {errorCorrection} {margin} {color} logo={withLogo ? brandLogo : undefined}>
|
|
|
<span style="display: inline-flex; gap: 0.5rem; margin-inline-start: 0.5rem;">
|
|
|
<QrCode.DownloadTrigger format="png" size="sm">PNG</QrCode.DownloadTrigger>
|
|
|
<QrCode.DownloadTrigger format="svg" size="sm" variant="solid" color="neutral">SVG</QrCode.DownloadTrigger>
|
|
|
</span>
|
|
|
</QrCode>
|
|
|
</div>
|
|
|
<div data-uix-stage-trace>
|
|
|
<span data-uix-stage-trace-key>encodes</span>
|
|
|
<span><span data-uix-stage-trace-event>{value || '∅'}</span></span>
|
|
|
<span style="margin-inline-start: auto;"><span data-uix-stage-trace-key>ecc</span> {withLogo ? 'H (logo)' : errorCorrection}</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 === 'examples'} onclick={() => (tab = 'examples')}>Examples</button>
|
|
|
<button data-uix-tab data-active={tab === 'api'} onclick={() => (tab = 'api')}>API</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</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>Pure <span data-uix-layer-badge="eidos">eidos</span> display — no soma. <code>value</code> → matrix → SVG.</p>
|
|
|
<div data-uix-controls>
|
|
|
<label data-uix-control>
|
|
|
<span data-uix-control-label>value</span>
|
|
|
<input type="text" bind:value />
|
|
|
</label>
|
|
|
<label data-uix-control>
|
|
|
<span data-uix-control-label>cellShape</span>
|
|
|
<span data-uix-chips role="radiogroup">
|
|
|
{#each shapes as s}
|
|
|
<button data-uix-chip data-active={cellShape === s} onclick={() => (cellShape = s)}>{s}</button>
|
|
|
{/each}
|
|
|
</span>
|
|
|
</label>
|
|
|
<label data-uix-control>
|
|
|
<span data-uix-control-label>size</span>
|
|
|
<span data-uix-chips role="radiogroup">
|
|
|
{#each sizes as s}
|
|
|
<button data-uix-chip data-active={size === s} onclick={() => (size = s)}>{s}</button>
|
|
|
{/each}
|
|
|
</span>
|
|
|
</label>
|
|
|
<label data-uix-control>
|
|
|
<span data-uix-control-label>errorCorrection</span>
|
|
|
<span data-uix-chips role="radiogroup">
|
|
|
{#each eccs as e}
|
|
|
<button data-uix-chip data-active={errorCorrection === e} onclick={() => (errorCorrection = e)}>{e}</button>
|
|
|
{/each}
|
|
|
</span>
|
|
|
</label>
|
|
|
<label data-uix-control>
|
|
|
<span data-uix-control-label>color</span>
|
|
|
<span data-uix-chips role="radiogroup">
|
|
|
{#each colors as c}
|
|
|
<button data-uix-chip data-active={color === c.value} onclick={() => (color = c.value)}>{c.label}</button>
|
|
|
{/each}
|
|
|
</span>
|
|
|
</label>
|
|
|
<label data-uix-control>
|
|
|
<span data-uix-control-label>margin (quiet zone)</span>
|
|
|
<span data-uix-chips role="radiogroup">
|
|
|
{#each [2, 4, 6] as m}
|
|
|
<button data-uix-chip data-active={margin === m} onclick={() => (margin = m)}>{m}</button>
|
|
|
{/each}
|
|
|
</span>
|
|
|
</label>
|
|
|
<label data-uix-control>
|
|
|
<span data-uix-control-label>logo (→ ECC H)</span>
|
|
|
<span data-uix-switch><input type="checkbox" bind:checked={withLogo} /><span data-uix-switch-label>{withLogo ? 'on' : 'off'}</span></span>
|
|
|
</label>
|
|
|
</div>
|
|
|
<div data-uix-code>
|
|
|
<div data-uix-code-head>
|
|
|
<span data-uix-layer-badge="eidos">eidos</span>
|
|
|
<span>display + DownloadTrigger</span>
|
|
|
<span data-uix-code-lang>svelte</span>
|
|
|
</div>
|
|
|
<pre><code>{snippet}</code></pre>
|
|
|
</div>
|
|
|
</section>
|
|
|
{/if}
|
|
|
|
|
|
{#if tab === 'examples'}
|
|
|
<section data-uix-section>
|
|
|
<h2 data-uix-section-title>Examples</h2>
|
|
|
<p data-uix-section-desc>Cell shapes, themed colour, logo — all stay scannable (function patterns square, ECC auto-boost).</p>
|
|
|
<div data-uix-stage-area style="display: flex; gap: 1.5rem; flex-wrap: wrap;">
|
|
|
<QrCode value="https://uix.dev" size="md" />
|
|
|
<QrCode value="https://uix.dev" size="md" cellShape="rounded" color="#7c3aed" />
|
|
|
<QrCode value="https://uix.dev" size="md" cellShape="dots" color="#0d9488" />
|
|
|
<QrCode value="https://uix.dev" size="md" cellShape="rounded" color="#e11d48" logo={brandLogo} />
|
|
|
</div>
|
|
|
</section>
|
|
|
{/if}
|
|
|
|
|
|
{#if tab === 'api'}
|
|
|
<section data-uix-section>
|
|
|
<h2 data-uix-section-title>API reference</h2>
|
|
|
<div data-uix-table-wrap>
|
|
|
<table data-uix-table>
|
|
|
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
|
<tbody>
|
|
|
<tr><td class="name">value</td><td class="type">string</td><td class="default">''</td><td>Data to encode.</td></tr>
|
|
|
<tr><td class="name">size</td><td class="type">ResponsiveProp<number | 'xs'…'xl'></td><td class="default">'md'</td><td>Box size (px or preset).</td></tr>
|
|
|
<tr><td class="name">cellShape</td><td class="type">'square' | 'rounded' | 'dots'</td><td class="default">'square'</td><td>Data-module shape; finders stay square.</td></tr>
|
|
|
<tr><td class="name">errorCorrection</td><td class="type">'L' | 'M' | 'Q' | 'H'</td><td class="default">'M'</td><td>Redundancy. Auto-boosted to 'H' with a logo.</td></tr>
|
|
|
<tr><td class="name">color / background</td><td class="type">string</td><td class="default">fixed tokens</td><td>Themeable (you own the contrast).</td></tr>
|
|
|
<tr><td class="name">margin</td><td class="type">number</td><td class="default">4</td><td>Quiet zone in modules.</td></tr>
|
|
|
<tr><td class="name">logo</td><td class="type">Snippet</td><td class="default empty">—</td><td>Centred overlay; clears cells + boosts ECC.</td></tr>
|
|
|
<tr><td class="name">alt</td><td class="type">string</td><td class="default">value</td><td>Accessible name.</td></tr>
|
|
|
</tbody>
|
|
|
</table>
|
|
|
</div>
|
|
|
</section>
|
|
|
{/if}
|
|
|
|
|
|
{#if tab === 'morfo'}
|
|
|
<section data-uix-section>
|
|
|
<h2 data-uix-section-title><span data-uix-layer-badge="morfo">morfo</span> · declarative contract</h2>
|
|
|
<p data-uix-section-desc>Source: <code>src/uix/morfo/components/qr-code.ts</code>. <code>scope:['eidos']</code> — a display primitive, no soma.</p>
|
|
|
<div data-uix-table-wrap>
|
|
|
<table data-uix-table>
|
|
|
<thead><tr><th>Part</th><th>Marker</th><th>Element</th><th>Role</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"><{part.defaultElement}></td>
|
|
|
<td class="type">{part.role ?? '—'}</td>
|
|
|
<td class="default">{part.optional ? 'yes' : 'no'}</td>
|
|
|
</tr>
|
|
|
{/each}
|
|
|
</tbody>
|
|
|
</table>
|
|
|
</div>
|
|
|
</section>
|
|
|
{/if}
|
|
|
|
|
|
{#if tab === 'recipe'}
|
|
|
<section data-uix-section>
|
|
|
<h2 data-uix-section-title>Eidos recipe</h2>
|
|
|
<p data-uix-section-desc>Tokens at <code>lib/recipes/base.ts</code>. Default fg/bg are <strong>fixed-tone</strong> (dark-on-light) for scannability — a QR can't invert in dark mode; theming goes through <code>color</code>/<code>background</code>.</p>
|
|
|
<div data-uix-table-wrap>
|
|
|
<table data-uix-table>
|
|
|
<thead><tr><th>Token</th><th>Role</th></tr></thead>
|
|
|
<tbody>
|
|
|
<tr><td class="name">--qr-code-fg / --qr-code-bg</td><td>module colour / quiet-zone (fixed-tone)</td></tr>
|
|
|
<tr><td class="name">--qr-code-size-{'{'}xs…xl{'}'}</td><td>size presets</td></tr>
|
|
|
<tr><td class="name">--qr-code-radius</td><td>frame radius</td></tr>
|
|
|
<tr><td class="name">--qr-code-overlay-bg / -padding / -radius</td><td>logo backing</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>Part</th><th>Attribute</th><th>Value</th></tr></thead>
|
|
|
<tbody>
|
|
|
<tr><td class="name">frame (svg)</td><td>role / aria-label</td><td class="type">"img" / alt ?? value</td></tr>
|
|
|
<tr><td class="name">pattern / overlay</td><td>aria-hidden</td><td class="type">"true" (decorative)</td></tr>
|
|
|
<tr><td class="name">download-trigger</td><td>type / aria-label</td><td class="type">"button" / "Download QR code"</td></tr>
|
|
|
</tbody>
|
|
|
</table>
|
|
|
</div>
|
|
|
</section>
|
|
|
{/if}
|
|
|
</div>
|