diff --git a/src/uix/eidos/components/qr-code/README.md b/src/uix/eidos/components/qr-code/README.md index 6efcc3058..4d66715c2 100644 --- a/src/uix/eidos/components/qr-code/README.md +++ b/src/uix/eidos/components/qr-code/README.md @@ -53,6 +53,12 @@ decorative shape. - **Fixed-tone default colours** (dark-on-light) — a QR can't invert in dark mode and stay scannable, so the default fg/bg are theme-independent; theming is opt-in via `color`/`background` (you own the contrast). +- **Quiet zone is in modules, not pixels** — `margin` measures the quiet zone in + QR *modules* (spec minimum 4 — ISO/IEC 18004), the canonical unit: it scales + with the symbol so the clear ring stays proportional at any rendered size, + which is what guarantees scannability. There is deliberately no pixel margin + prop. For fixed pixel spacing in a layout, pad the container in CSS using the + QR's `background` colour — the symbol's quiet zone stays canonical in modules. - **Export bakes resolved colours** — a serialized SVG loses the document's CSS custom properties, so `DownloadTrigger` resolves `getComputedStyle` fills into the clone before writing PNG/SVG. @@ -87,7 +93,7 @@ a `$derived`, rendered to SVG. Download is imperative via `$adom`. | `color` | `AffirmativeColorRole \| string` | `currentColor`/token | module colour | | `background` | `string` | token / transparent | quiet-zone + module background | | `cellShape` | `'square' \| 'rounded' \| 'dots'` | `'square'` | module shape | -| `margin` | `number` | `4` | quiet zone in modules (spec minimum 4) | +| `margin` | `number` | `4` | quiet zone in modules, not pixels (spec min 4 — see Scannability) | | `logo` | `Snippet` | — | centred overlay; clears the centre + boosts ECC to `H` | | `alt` | `string` | `value` | accessible name for the Frame | diff --git a/src/uix/eidos/components/qr-code/qr-code.css b/src/uix/eidos/components/qr-code/qr-code.css index f76ca8035..cc074eba3 100644 --- a/src/uix/eidos/components/qr-code/qr-code.css +++ b/src/uix/eidos/components/qr-code/qr-code.css @@ -11,21 +11,26 @@ /* Logo overlay — fills the cleared centre (a foreignObject), centres the * media, paints its own light backing so it reads over the modules. */ +/* width/height (physical) — not inline-size/block-size: logical props don't + * size an element (it uses width/height as geometry) nor resolve cleanly + * for HTML inside a viewBox-scaled foreignObject. */ [data-qr-code-overlay] { box-sizing: border-box; - inline-size: 100%; - block-size: 100%; + width: 100%; + height: 100%; display: flex; align-items: center; justify-content: center; overflow: hidden; background: var(--qr-code-overlay-bg); border-radius: var(--qr-code-overlay-radius); - padding: var(--qr-code-overlay-padding); + /* Padding is set inline (proportional to the cleared centre): a fixed px + * padding equals that many *modules* inside a viewBox-scaled foreignObject + * and would collapse the content box. */ } [data-qr-code-overlay] :where(img, svg) { - inline-size: 100%; - block-size: 100%; + width: 100%; + height: 100%; object-fit: contain; } diff --git a/src/uix/eidos/components/qr-code/qr-code.svelte b/src/uix/eidos/components/qr-code/qr-code.svelte index 42266ddec..306631be0 100644 --- a/src/uix/eidos/components/qr-code/qr-code.svelte +++ b/src/uix/eidos/components/qr-code/qr-code.svelte @@ -149,7 +149,13 @@ {#if logo} -
{@render logo()}
+
{@render logo()}
{/if}
diff --git a/src/uix/eidos/generated/base.css b/src/uix/eidos/generated/base.css index 2a67cf8c5..a6e1b8de8 100644 --- a/src/uix/eidos/generated/base.css +++ b/src/uix/eidos/generated/base.css @@ -1907,7 +1907,6 @@ --qr-code-size-xl: 256px; --qr-code-radius: var(--radius-md); --qr-code-overlay-bg: #ffffff; - --qr-code-overlay-padding: var(--space-1); --qr-code-overlay-radius: var(--radius-sm); --radio-cards-gap: var(--space-3); --radio-cards-horizontal-basis: 12rem; diff --git a/src/uix/eidos/lib/recipes/base.ts b/src/uix/eidos/lib/recipes/base.ts index 95a937698..10d5a691c 100644 --- a/src/uix/eidos/lib/recipes/base.ts +++ b/src/uix/eidos/lib/recipes/base.ts @@ -1951,7 +1951,6 @@ export const THEME_BASE_RECIPE_TOKENS = { 'size-xl': '256px', radius: 'var(--radius-md)', 'overlay-bg': '#ffffff', - 'overlay-padding': 'var(--space-1)', 'overlay-radius': 'var(--radius-sm)' }, // RadioCards — card-style skin over RadioGroup. Reuses RadioGroup's diff --git a/web/routes/uix/components/qr-code/+page.svelte b/web/routes/uix/components/qr-code/+page.svelte index 1a8d1674b..7093781e6 100644 --- a/web/routes/uix/components/qr-code/+page.svelte +++ b/web/routes/uix/components/qr-code/+page.svelte @@ -11,13 +11,27 @@ type Tab = 'live' | 'examples' | 'api' | 'morfo' | 'recipe' | 'a11y'; let tab = $state('live'); - let value = $state('https://uix.dev/qr-code'); + // 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('lg'); let cellShape = $state('square'); let errorCorrection = $state('M'); let margin = $state(4); let color = $state(undefined); - let withLogo = $state(false); + let withLogo = $state(true); const sizes: QrCodeSize[] = ['xs', 'sm', 'md', 'lg', 'xl']; const shapes: QrCodeCellShape[] = ['square', 'rounded', 'dots']; @@ -60,8 +74,16 @@ {#snippet brandLogo()} -

Pure eidos display — no soma. value → matrix → SVG.

+