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/qr-code/+page.svelte

300 lines
13 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 {
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');
// The encoder takes ANY UTF-8 string — not just URLs. The reader decides how
// to interpret it (vCard contact, WiFi join, geo, plain text, …); the QR is
// content-agnostic. Multi-line + accents/CJK go through byte (UTF-8) mode.
const presets: { label: string; value: string }[] = [
{ label: 'URL', value: 'https://uix.dev/qr-code' },
{
label: 'vCard',
value:
'BEGIN:VCARD\nVERSION:3.0\nFN:Juan Vicente\nORG:UIX\nTITLE:Maker\nTEL;TYPE=CELL:+34600123456\nEMAIL:hola@uix.dev\nURL:https://uix.dev\nEND:VCARD'
},
{ label: 'WiFi', value: 'WIFI:T:WPA;S:UIX-Guest;P:s3cret-pass;;' },
{ label: 'Text · unicode', value: 'Diseñando lo que ocurre — açaí · ñoño · 日本語 · 🎯' }
];
let value = $state(presets[0].value);
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(true);
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()}
<!-- Test logo: a brand monogram. Its own colour (not the QR fg) so it reads as a logo. -->
<svg viewBox="0 0 32 32" aria-hidden="true">
<rect width="32" height="32" rx="8" fill="#7c3aed" />
<path
d="M10 9v8a6 6 0 0 0 12 0V9"
fill="none"
stroke="#fff"
stroke-width="3.5"
stroke-linecap="round"
/>
</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>content preset</span>
<span data-uix-chips role="radiogroup">
{#each presets as p}
<button data-uix-chip data-active={value === p.value} onclick={() => (value = p.value)}>{p.label}</button>
{/each}
</span>
</label>
<label data-uix-control style="flex-basis: 100%;">
<span data-uix-control-label>value (any UTF-8 string)</span>
<textarea bind:value rows="3" style="resize: vertical; font-family: inherit;"></textarea>
</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>quiet zone · modules (spec min 4)</span>
<span data-uix-chips role="radiogroup">
{#each [4, 8, 16] 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&lt;number | 'xs'…'xl'&gt;</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">&lt;{part.defaultElement}&gt;</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 / -radius</td><td>logo backing (padding is proportional, 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>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>

Powered by TurnKey Linux.