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

850 lines
31 KiB

feat(eidos): Card, Avatar ext, Image, SText, Badge, service components + dates engine fixes Sprint completo: - Card (eidos + sema): elevation/intent/interactive, eventos select+activate. - Avatar: ring, AvatarBadge, AvatarGroup separados; demo Avatar standalone. - Image: status surface (idle/loading/loaded/error) consumiendo soma ImageProvider. - SText + STextVirtualList: canvas-text engine portado de morfo-driven-soma como componentes separados para optimizar bundle (no se importa canvas si no se usa). - Badge: primitivo eidos puro. - Trans, FormatNumber, FormatDate, RelativeTime: 4 service components greppables alrededor de uix.langs y uix.format. Nav group "Utilities". Bugs fix arts/format/dates (engine-dates.ts): 1. Preset / per-axis conflict: Intl.DateTimeFormat rechaza dateStyle/timeStyle con cualquier per-axis (weekday/era/year/month/day/dayPeriod/hour/minute/ second/fractionalSecondDigits) o con timeZoneName. Engine drop-ea ambos presets por defecto cuando detecta conflict (regla estricta — un solo per-axis hace ilegales los dos presets). 2. Per-call locale override: formatDate/Time/DateTime aceptan tercer arg locale?: string. No muta engine state. Cache (getCachedDateFormat) keyea por (locale x options). resolveCallHourCycle: cuando hay locale override y pref auto, el ciclo horario se resuelve desde el locale OVERRIDE. 3. withHourCycle precedence: caller hourCycle/hour12 ahora gana sobre preferencia del engine. Antes spread mal ordenado sobrescribia 'h12' del caller con 'h23' del engine cuando locale resolvia a 24h. FormatDate component vuelve a rutear siempre por uix.format.dates (cache + hourCycle + locale reactivo), pasando locale como tercer arg. Demos service components autonomos del toolbar global: cada uno owns su demoLocale state con 7 chips (es-ES/en-US/en-GB/de-DE/fr-FR/ja-JP/ar-EG). FormatDate stage filtra dateStyle/timeStyle por mode + chip hourCycle anadido. Tests: 14/14 passing en engine-dates.test.ts (+6 nuevos: conflict handling, locale override, hourCycle precedence). Docs: arts/format/README.md (handoff 2026-05-25 con las 3 reglas), README.md por service component, project_service_components_sprint_2026-05-25.md, feedback_service_demos_own_locale.md (pattern reusable). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
<script lang="ts">
import {
Badge,
type BadgeSize,
type BadgeColor,
type BadgeVariant,
type BadgeRounded
} from '$uix/eidos/components/badge';
import { SHAPE_FAMILIES, type ShapeFamily } from '$uix/eidos/lib/types';
feat(eidos): Card, Avatar ext, Image, SText, Badge, service components + dates engine fixes Sprint completo: - Card (eidos + sema): elevation/intent/interactive, eventos select+activate. - Avatar: ring, AvatarBadge, AvatarGroup separados; demo Avatar standalone. - Image: status surface (idle/loading/loaded/error) consumiendo soma ImageProvider. - SText + STextVirtualList: canvas-text engine portado de morfo-driven-soma como componentes separados para optimizar bundle (no se importa canvas si no se usa). - Badge: primitivo eidos puro. - Trans, FormatNumber, FormatDate, RelativeTime: 4 service components greppables alrededor de uix.langs y uix.format. Nav group "Utilities". Bugs fix arts/format/dates (engine-dates.ts): 1. Preset / per-axis conflict: Intl.DateTimeFormat rechaza dateStyle/timeStyle con cualquier per-axis (weekday/era/year/month/day/dayPeriod/hour/minute/ second/fractionalSecondDigits) o con timeZoneName. Engine drop-ea ambos presets por defecto cuando detecta conflict (regla estricta — un solo per-axis hace ilegales los dos presets). 2. Per-call locale override: formatDate/Time/DateTime aceptan tercer arg locale?: string. No muta engine state. Cache (getCachedDateFormat) keyea por (locale x options). resolveCallHourCycle: cuando hay locale override y pref auto, el ciclo horario se resuelve desde el locale OVERRIDE. 3. withHourCycle precedence: caller hourCycle/hour12 ahora gana sobre preferencia del engine. Antes spread mal ordenado sobrescribia 'h12' del caller con 'h23' del engine cuando locale resolvia a 24h. FormatDate component vuelve a rutear siempre por uix.format.dates (cache + hourCycle + locale reactivo), pasando locale como tercer arg. Demos service components autonomos del toolbar global: cada uno owns su demoLocale state con 7 chips (es-ES/en-US/en-GB/de-DE/fr-FR/ja-JP/ar-EG). FormatDate stage filtra dateStyle/timeStyle por mode + chip hourCycle anadido. Tests: 14/14 passing en engine-dates.test.ts (+6 nuevos: conflict handling, locale override, hourCycle precedence). Docs: arts/format/README.md (handoff 2026-05-25 con las 3 reglas), README.md por service component, project_service_components_sprint_2026-05-25.md, feedback_service_demos_own_locale.md (pattern reusable). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
import { Box } from '$uix/eidos/components/box';
import { Stack } from '$uix/eidos/components/stack';
import { compileMorfo } from '$uix/morfo';
import { badgeMorfo } from '@/uix/morfo/components/badge';
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 ────────────────────────────────────────────────────────
let variant = $state<BadgeVariant>('soft');
let size = $state<BadgeSize>('md');
let color = $state<BadgeColor>('primary');
let rounded = $state<BadgeRounded>('full');
let shape = $state<ShapeFamily>('rounded');
feat(eidos): Card, Avatar ext, Image, SText, Badge, service components + dates engine fixes Sprint completo: - Card (eidos + sema): elevation/intent/interactive, eventos select+activate. - Avatar: ring, AvatarBadge, AvatarGroup separados; demo Avatar standalone. - Image: status surface (idle/loading/loaded/error) consumiendo soma ImageProvider. - SText + STextVirtualList: canvas-text engine portado de morfo-driven-soma como componentes separados para optimizar bundle (no se importa canvas si no se usa). - Badge: primitivo eidos puro. - Trans, FormatNumber, FormatDate, RelativeTime: 4 service components greppables alrededor de uix.langs y uix.format. Nav group "Utilities". Bugs fix arts/format/dates (engine-dates.ts): 1. Preset / per-axis conflict: Intl.DateTimeFormat rechaza dateStyle/timeStyle con cualquier per-axis (weekday/era/year/month/day/dayPeriod/hour/minute/ second/fractionalSecondDigits) o con timeZoneName. Engine drop-ea ambos presets por defecto cuando detecta conflict (regla estricta — un solo per-axis hace ilegales los dos presets). 2. Per-call locale override: formatDate/Time/DateTime aceptan tercer arg locale?: string. No muta engine state. Cache (getCachedDateFormat) keyea por (locale x options). resolveCallHourCycle: cuando hay locale override y pref auto, el ciclo horario se resuelve desde el locale OVERRIDE. 3. withHourCycle precedence: caller hourCycle/hour12 ahora gana sobre preferencia del engine. Antes spread mal ordenado sobrescribia 'h12' del caller con 'h23' del engine cuando locale resolvia a 24h. FormatDate component vuelve a rutear siempre por uix.format.dates (cache + hourCycle + locale reactivo), pasando locale como tercer arg. Demos service components autonomos del toolbar global: cada uno owns su demoLocale state con 7 chips (es-ES/en-US/en-GB/de-DE/fr-FR/ja-JP/ar-EG). FormatDate stage filtra dateStyle/timeStyle por mode + chip hourCycle anadido. Tests: 14/14 passing en engine-dates.test.ts (+6 nuevos: conflict handling, locale override, hourCycle precedence). Docs: arts/format/README.md (handoff 2026-05-25 con las 3 reglas), README.md por service component, project_service_components_sprint_2026-05-25.md, feedback_service_demos_own_locale.md (pattern reusable). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
let labelText = $state('New');
let withDot = $state(false);
let withIcon = $state(false);
let removable = $state(false);
let removeCount = $state(0);
const variants: BadgeVariant[] = ['soft', 'solid', 'outline', 'ghost'];
const sizes: BadgeSize[] = ['xs', 'sm', 'md', 'lg'];
const colors: BadgeColor[] = [
'primary',
'secondary',
'neutral',
'affirm',
'fulfill',
'risk',
'threat',
'loss'
];
const roundeds: BadgeRounded[] = ['sm', 'md', 'lg', 'full'];
// ── Compiled morfo ────────────────────────────────────────────────────
const compiled = compileMorfo(badgeMorfo);
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();
});
// ── Code snippet ──────────────────────────────────────────────────────
feat(eidos): Card, Avatar ext, Image, SText, Badge, service components + dates engine fixes Sprint completo: - Card (eidos + sema): elevation/intent/interactive, eventos select+activate. - Avatar: ring, AvatarBadge, AvatarGroup separados; demo Avatar standalone. - Image: status surface (idle/loading/loaded/error) consumiendo soma ImageProvider. - SText + STextVirtualList: canvas-text engine portado de morfo-driven-soma como componentes separados para optimizar bundle (no se importa canvas si no se usa). - Badge: primitivo eidos puro. - Trans, FormatNumber, FormatDate, RelativeTime: 4 service components greppables alrededor de uix.langs y uix.format. Nav group "Utilities". Bugs fix arts/format/dates (engine-dates.ts): 1. Preset / per-axis conflict: Intl.DateTimeFormat rechaza dateStyle/timeStyle con cualquier per-axis (weekday/era/year/month/day/dayPeriod/hour/minute/ second/fractionalSecondDigits) o con timeZoneName. Engine drop-ea ambos presets por defecto cuando detecta conflict (regla estricta — un solo per-axis hace ilegales los dos presets). 2. Per-call locale override: formatDate/Time/DateTime aceptan tercer arg locale?: string. No muta engine state. Cache (getCachedDateFormat) keyea por (locale x options). resolveCallHourCycle: cuando hay locale override y pref auto, el ciclo horario se resuelve desde el locale OVERRIDE. 3. withHourCycle precedence: caller hourCycle/hour12 ahora gana sobre preferencia del engine. Antes spread mal ordenado sobrescribia 'h12' del caller con 'h23' del engine cuando locale resolvia a 24h. FormatDate component vuelve a rutear siempre por uix.format.dates (cache + hourCycle + locale reactivo), pasando locale como tercer arg. Demos service components autonomos del toolbar global: cada uno owns su demoLocale state con 7 chips (es-ES/en-US/en-GB/de-DE/fr-FR/ja-JP/ar-EG). FormatDate stage filtra dateStyle/timeStyle por mode + chip hourCycle anadido. Tests: 14/14 passing en engine-dates.test.ts (+6 nuevos: conflict handling, locale override, hourCycle precedence). Docs: arts/format/README.md (handoff 2026-05-25 con las 3 reglas), README.md por service component, project_service_components_sprint_2026-05-25.md, feedback_service_demos_own_locale.md (pattern reusable). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
const eidosSnippet = $derived(
[
"<script lang='ts'>",
" import { Badge } from '$uix/eidos/components/badge';",
'</' + 'script>',
'',
'<Badge',
variant !== 'soft' && ` variant="${variant}"`,
size !== 'md' && ` size="${size}"`,
color !== 'primary' && ` color="${color}"`,
rounded !== 'full' && ` rounded="${rounded}"`,
shape !== 'rounded' && ` shape="${shape}"`,
feat(eidos): Card, Avatar ext, Image, SText, Badge, service components + dates engine fixes Sprint completo: - Card (eidos + sema): elevation/intent/interactive, eventos select+activate. - Avatar: ring, AvatarBadge, AvatarGroup separados; demo Avatar standalone. - Image: status surface (idle/loading/loaded/error) consumiendo soma ImageProvider. - SText + STextVirtualList: canvas-text engine portado de morfo-driven-soma como componentes separados para optimizar bundle (no se importa canvas si no se usa). - Badge: primitivo eidos puro. - Trans, FormatNumber, FormatDate, RelativeTime: 4 service components greppables alrededor de uix.langs y uix.format. Nav group "Utilities". Bugs fix arts/format/dates (engine-dates.ts): 1. Preset / per-axis conflict: Intl.DateTimeFormat rechaza dateStyle/timeStyle con cualquier per-axis (weekday/era/year/month/day/dayPeriod/hour/minute/ second/fractionalSecondDigits) o con timeZoneName. Engine drop-ea ambos presets por defecto cuando detecta conflict (regla estricta — un solo per-axis hace ilegales los dos presets). 2. Per-call locale override: formatDate/Time/DateTime aceptan tercer arg locale?: string. No muta engine state. Cache (getCachedDateFormat) keyea por (locale x options). resolveCallHourCycle: cuando hay locale override y pref auto, el ciclo horario se resuelve desde el locale OVERRIDE. 3. withHourCycle precedence: caller hourCycle/hour12 ahora gana sobre preferencia del engine. Antes spread mal ordenado sobrescribia 'h12' del caller con 'h23' del engine cuando locale resolvia a 24h. FormatDate component vuelve a rutear siempre por uix.format.dates (cache + hourCycle + locale reactivo), pasando locale como tercer arg. Demos service components autonomos del toolbar global: cada uno owns su demoLocale state con 7 chips (es-ES/en-US/en-GB/de-DE/fr-FR/ja-JP/ar-EG). FormatDate stage filtra dateStyle/timeStyle por mode + chip hourCycle anadido. Tests: 14/14 passing en engine-dates.test.ts (+6 nuevos: conflict handling, locale override, hourCycle precedence). Docs: arts/format/README.md (handoff 2026-05-25 con las 3 reglas), README.md por service component, project_service_components_sprint_2026-05-25.md, feedback_service_demos_own_locale.md (pattern reusable). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
withDot && ' dot',
removable && ' removable',
removable && ' onRemove={() => removeTag(id)}',
'>',
` ${labelText || ''}`,
'</Badge>'
]
.filter(Boolean)
.join('\n')
);
function handleRemove() {
removeCount += 1;
trace = [
{ event: 'onRemove (consumer callback)', family: 'consumer', at: Date.now() },
...trace
].slice(0, 6);
}
</script>
{#snippet sparkIcon()}
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
style="inline-size: 100%; block-size: 100%;"
aria-hidden="true"
>
<path
d="M12 2l2.39 5.95L20 9.27l-4 4.13.94 6.04L12 16.77l-4.94 2.67L8 13.4 4 9.27l5.61-1.32L12 2z"
/>
</svg>
{/snippet}
<div data-uix-canvas-inner>
<header>
<div data-uix-eyebrow>Status · Badge</div>
<h1 data-uix-page-title>Badge</h1>
<p data-uix-page-lede>
Small inline label / status pill. Four visual variants
(<code>soft</code>, <code>solid</code>, <code>outline</code>, <code>ghost</code>), four
sizes, eight color accents, four rounded scales, and optional
<code>dot</code> · <code>icon</code> · <code>remove</code> slots. Eidos-native: no semantic
events, no soma split — the morfo declares five parts and the recipe paints the chip from
the canonical color palette.
</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>variants</span>{variants.length}
</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>sizes</span>{sizes.length}
</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>colors</span>{colors.length}
</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>scope</span>eidos
</span>
</div>
</header>
<!-- Live preview always rendered (between header and tabs). -->
<div data-uix-stage>
<div data-uix-stage-area bind:this={stageRef}>
<Box
style="padding: var(--uix-space-6); display: flex; align-items: center; justify-content: center; inline-size: 100%; gap: var(--uix-space-3); flex-wrap: wrap;"
>
<Badge
{variant}
{size}
{color}
{rounded}
{shape}
feat(eidos): Card, Avatar ext, Image, SText, Badge, service components + dates engine fixes Sprint completo: - Card (eidos + sema): elevation/intent/interactive, eventos select+activate. - Avatar: ring, AvatarBadge, AvatarGroup separados; demo Avatar standalone. - Image: status surface (idle/loading/loaded/error) consumiendo soma ImageProvider. - SText + STextVirtualList: canvas-text engine portado de morfo-driven-soma como componentes separados para optimizar bundle (no se importa canvas si no se usa). - Badge: primitivo eidos puro. - Trans, FormatNumber, FormatDate, RelativeTime: 4 service components greppables alrededor de uix.langs y uix.format. Nav group "Utilities". Bugs fix arts/format/dates (engine-dates.ts): 1. Preset / per-axis conflict: Intl.DateTimeFormat rechaza dateStyle/timeStyle con cualquier per-axis (weekday/era/year/month/day/dayPeriod/hour/minute/ second/fractionalSecondDigits) o con timeZoneName. Engine drop-ea ambos presets por defecto cuando detecta conflict (regla estricta — un solo per-axis hace ilegales los dos presets). 2. Per-call locale override: formatDate/Time/DateTime aceptan tercer arg locale?: string. No muta engine state. Cache (getCachedDateFormat) keyea por (locale x options). resolveCallHourCycle: cuando hay locale override y pref auto, el ciclo horario se resuelve desde el locale OVERRIDE. 3. withHourCycle precedence: caller hourCycle/hour12 ahora gana sobre preferencia del engine. Antes spread mal ordenado sobrescribia 'h12' del caller con 'h23' del engine cuando locale resolvia a 24h. FormatDate component vuelve a rutear siempre por uix.format.dates (cache + hourCycle + locale reactivo), pasando locale como tercer arg. Demos service components autonomos del toolbar global: cada uno owns su demoLocale state con 7 chips (es-ES/en-US/en-GB/de-DE/fr-FR/ja-JP/ar-EG). FormatDate stage filtra dateStyle/timeStyle por mode + chip hourCycle anadido. Tests: 14/14 passing en engine-dates.test.ts (+6 nuevos: conflict handling, locale override, hourCycle precedence). Docs: arts/format/README.md (handoff 2026-05-25 con las 3 reglas), README.md por service component, project_service_components_sprint_2026-05-25.md, feedback_service_demos_own_locale.md (pattern reusable). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
dot={withDot}
{removable}
onRemove={handleRemove}
icon={withIcon ? sparkIcon : undefined}
>
{labelText || 'Badge'}
</Badge>
</Box>
</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>removes</span>
<span>{removeCount}</span>
<span style="margin-inline-start: auto;">
<span data-uix-stage-trace-key>variant</span> {variant} ·
<span data-uix-stage-trace-key>size</span> {size} ·
<span data-uix-stage-trace-key>color</span> {color}
</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>9</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>
Badge is eidos-native — no <span data-uix-layer-badge="soma">soma</span> split. Variants
stretch over the same chrome (padding, radius, font). The optional
<code>Dot</code> / <code>Icon</code> / <code>Remove</code> slots compose inline next to
the label; toggle them independently.
</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>variant</span>
<span data-uix-chips role="radiogroup">
{#each variants as v (v)}
<button data-uix-chip data-active={variant === v} onclick={() => (variant = v)}
>{v}</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 (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>color</span>
<span data-uix-chips role="radiogroup">
{#each colors as c (c)}
<button data-uix-chip data-active={color === c} onclick={() => (color = c)}
>{c}</button
>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>rounded</span>
<span data-uix-chips role="radiogroup">
{#each roundeds as r (r)}
<button data-uix-chip data-active={rounded === r} onclick={() => (rounded = r)}
>{r}</button
>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>shape</span>
<span data-uix-chips role="radiogroup">
{#each SHAPE_FAMILIES as sh (sh)}
<button data-uix-chip data-active={shape === sh} onclick={() => (shape = sh)}
>{sh}</button
>
{/each}
</span>
</label>
feat(eidos): Card, Avatar ext, Image, SText, Badge, service components + dates engine fixes Sprint completo: - Card (eidos + sema): elevation/intent/interactive, eventos select+activate. - Avatar: ring, AvatarBadge, AvatarGroup separados; demo Avatar standalone. - Image: status surface (idle/loading/loaded/error) consumiendo soma ImageProvider. - SText + STextVirtualList: canvas-text engine portado de morfo-driven-soma como componentes separados para optimizar bundle (no se importa canvas si no se usa). - Badge: primitivo eidos puro. - Trans, FormatNumber, FormatDate, RelativeTime: 4 service components greppables alrededor de uix.langs y uix.format. Nav group "Utilities". Bugs fix arts/format/dates (engine-dates.ts): 1. Preset / per-axis conflict: Intl.DateTimeFormat rechaza dateStyle/timeStyle con cualquier per-axis (weekday/era/year/month/day/dayPeriod/hour/minute/ second/fractionalSecondDigits) o con timeZoneName. Engine drop-ea ambos presets por defecto cuando detecta conflict (regla estricta — un solo per-axis hace ilegales los dos presets). 2. Per-call locale override: formatDate/Time/DateTime aceptan tercer arg locale?: string. No muta engine state. Cache (getCachedDateFormat) keyea por (locale x options). resolveCallHourCycle: cuando hay locale override y pref auto, el ciclo horario se resuelve desde el locale OVERRIDE. 3. withHourCycle precedence: caller hourCycle/hour12 ahora gana sobre preferencia del engine. Antes spread mal ordenado sobrescribia 'h12' del caller con 'h23' del engine cuando locale resolvia a 24h. FormatDate component vuelve a rutear siempre por uix.format.dates (cache + hourCycle + locale reactivo), pasando locale como tercer arg. Demos service components autonomos del toolbar global: cada uno owns su demoLocale state con 7 chips (es-ES/en-US/en-GB/de-DE/fr-FR/ja-JP/ar-EG). FormatDate stage filtra dateStyle/timeStyle por mode + chip hourCycle anadido. Tests: 14/14 passing en engine-dates.test.ts (+6 nuevos: conflict handling, locale override, hourCycle precedence). Docs: arts/format/README.md (handoff 2026-05-25 con las 3 reglas), README.md por service component, project_service_components_sprint_2026-05-25.md, feedback_service_demos_own_locale.md (pattern reusable). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
<label data-uix-control>
<span data-uix-control-label>label</span>
<input
data-uix-text-input
type="text"
bind:value={labelText}
placeholder="Badge"
style="inline-size: 12rem;"
/>
</label>
<label data-uix-control>
<span data-uix-control-label>dot</span>
<span data-uix-switch>
<input type="checkbox" bind:checked={withDot} />
<span data-uix-switch-label>{withDot ? 'on' : 'off'}</span>
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>icon</span>
<span data-uix-switch>
<input type="checkbox" bind:checked={withIcon} />
<span data-uix-switch-label>{withIcon ? 'on' : 'off'}</span>
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>removable</span>
<span data-uix-switch>
<input type="checkbox" bind:checked={removable} />
<span data-uix-switch-label>{removable ? 'on' : 'off'}</span>
</span>
</label>
</div>
<!-- ── Code snippet (eidos-native — no soma layer) ──────────────── -->
feat(eidos): Card, Avatar ext, Image, SText, Badge, service components + dates engine fixes Sprint completo: - Card (eidos + sema): elevation/intent/interactive, eventos select+activate. - Avatar: ring, AvatarBadge, AvatarGroup separados; demo Avatar standalone. - Image: status surface (idle/loading/loaded/error) consumiendo soma ImageProvider. - SText + STextVirtualList: canvas-text engine portado de morfo-driven-soma como componentes separados para optimizar bundle (no se importa canvas si no se usa). - Badge: primitivo eidos puro. - Trans, FormatNumber, FormatDate, RelativeTime: 4 service components greppables alrededor de uix.langs y uix.format. Nav group "Utilities". Bugs fix arts/format/dates (engine-dates.ts): 1. Preset / per-axis conflict: Intl.DateTimeFormat rechaza dateStyle/timeStyle con cualquier per-axis (weekday/era/year/month/day/dayPeriod/hour/minute/ second/fractionalSecondDigits) o con timeZoneName. Engine drop-ea ambos presets por defecto cuando detecta conflict (regla estricta — un solo per-axis hace ilegales los dos presets). 2. Per-call locale override: formatDate/Time/DateTime aceptan tercer arg locale?: string. No muta engine state. Cache (getCachedDateFormat) keyea por (locale x options). resolveCallHourCycle: cuando hay locale override y pref auto, el ciclo horario se resuelve desde el locale OVERRIDE. 3. withHourCycle precedence: caller hourCycle/hour12 ahora gana sobre preferencia del engine. Antes spread mal ordenado sobrescribia 'h12' del caller con 'h23' del engine cuando locale resolvia a 24h. FormatDate component vuelve a rutear siempre por uix.format.dates (cache + hourCycle + locale reactivo), pasando locale como tercer arg. Demos service components autonomos del toolbar global: cada uno owns su demoLocale state con 7 chips (es-ES/en-US/en-GB/de-DE/fr-FR/ja-JP/ar-EG). FormatDate stage filtra dateStyle/timeStyle por mode + chip hourCycle anadido. Tests: 14/14 passing en engine-dates.test.ts (+6 nuevos: conflict handling, locale override, hourCycle precedence). Docs: arts/format/README.md (handoff 2026-05-25 con las 3 reglas), README.md por service component, project_service_components_sprint_2026-05-25.md, feedback_service_demos_own_locale.md (pattern reusable). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
<div data-uix-code>
<div data-uix-code-head>
<span data-uix-layer-badge="eidos">eidos</span>
<span>visual · slots compose inline</span>
<span data-uix-code-lang>svelte</span>
</div>
<pre><code>{eidosSnippet}</code></pre>
</div>
<div data-uix-subsection-head>Patterns · common shapes</div>
<Stack gap="var(--uix-space-4)">
<Box
style="padding: var(--uix-space-4); border: 1px solid var(--uix-border); border-radius: var(--uix-radius-md); display: flex; flex-wrap: wrap; gap: var(--uix-space-2); align-items: center;"
>
<code style="min-inline-size: 8rem; color: var(--uix-text-faint);">status pill</code>
<Badge variant="soft" color="affirm" dot>Active</Badge>
<Badge variant="soft" color="risk" dot>Pending</Badge>
<Badge variant="soft" color="threat" dot>Failed</Badge>
<Badge variant="soft" color="loss" dot>Archived</Badge>
<Badge variant="soft" color="neutral" dot>Draft</Badge>
</Box>
<Box
style="padding: var(--uix-space-4); border: 1px solid var(--uix-border); border-radius: var(--uix-radius-md); display: flex; flex-wrap: wrap; gap: var(--uix-space-2); align-items: center;"
>
<code style="min-inline-size: 8rem; color: var(--uix-text-faint);">shape families</code>
<Badge variant="soft" color="primary" rounded="md" shape="rounded">rounded</Badge>
<Badge variant="soft" color="primary" rounded="md" shape="continuous">continuous</Badge>
<Badge variant="solid" color="primary" rounded="md" shape="cut">cut</Badge>
<Badge variant="solid" color="primary" rounded="md" shape="scoop">scoop</Badge>
</Box>
feat(eidos): Card, Avatar ext, Image, SText, Badge, service components + dates engine fixes Sprint completo: - Card (eidos + sema): elevation/intent/interactive, eventos select+activate. - Avatar: ring, AvatarBadge, AvatarGroup separados; demo Avatar standalone. - Image: status surface (idle/loading/loaded/error) consumiendo soma ImageProvider. - SText + STextVirtualList: canvas-text engine portado de morfo-driven-soma como componentes separados para optimizar bundle (no se importa canvas si no se usa). - Badge: primitivo eidos puro. - Trans, FormatNumber, FormatDate, RelativeTime: 4 service components greppables alrededor de uix.langs y uix.format. Nav group "Utilities". Bugs fix arts/format/dates (engine-dates.ts): 1. Preset / per-axis conflict: Intl.DateTimeFormat rechaza dateStyle/timeStyle con cualquier per-axis (weekday/era/year/month/day/dayPeriod/hour/minute/ second/fractionalSecondDigits) o con timeZoneName. Engine drop-ea ambos presets por defecto cuando detecta conflict (regla estricta — un solo per-axis hace ilegales los dos presets). 2. Per-call locale override: formatDate/Time/DateTime aceptan tercer arg locale?: string. No muta engine state. Cache (getCachedDateFormat) keyea por (locale x options). resolveCallHourCycle: cuando hay locale override y pref auto, el ciclo horario se resuelve desde el locale OVERRIDE. 3. withHourCycle precedence: caller hourCycle/hour12 ahora gana sobre preferencia del engine. Antes spread mal ordenado sobrescribia 'h12' del caller con 'h23' del engine cuando locale resolvia a 24h. FormatDate component vuelve a rutear siempre por uix.format.dates (cache + hourCycle + locale reactivo), pasando locale como tercer arg. Demos service components autonomos del toolbar global: cada uno owns su demoLocale state con 7 chips (es-ES/en-US/en-GB/de-DE/fr-FR/ja-JP/ar-EG). FormatDate stage filtra dateStyle/timeStyle por mode + chip hourCycle anadido. Tests: 14/14 passing en engine-dates.test.ts (+6 nuevos: conflict handling, locale override, hourCycle precedence). Docs: arts/format/README.md (handoff 2026-05-25 con las 3 reglas), README.md por service component, project_service_components_sprint_2026-05-25.md, feedback_service_demos_own_locale.md (pattern reusable). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
<Box
style="padding: var(--uix-space-4); border: 1px solid var(--uix-border); border-radius: var(--uix-radius-md); display: flex; flex-wrap: wrap; gap: var(--uix-space-2); align-items: center;"
>
<code style="min-inline-size: 8rem; color: var(--uix-text-faint);">count chip</code>
<Badge variant="solid" color="primary" size="xs">3</Badge>
<Badge variant="solid" color="threat" size="xs">12</Badge>
<Badge variant="solid" color="affirm" size="xs">99+</Badge>
<Badge variant="solid" color="neutral" size="xs">new</Badge>
</Box>
<Box
style="padding: var(--uix-space-4); border: 1px solid var(--uix-border); border-radius: var(--uix-radius-md); display: flex; flex-wrap: wrap; gap: var(--uix-space-2); align-items: center;"
>
<code style="min-inline-size: 8rem; color: var(--uix-text-faint);">filter tags</code>
<Badge variant="outline" color="primary" rounded="md" removable onRemove={handleRemove}>
Engineering
</Badge>
<Badge variant="outline" color="primary" rounded="md" removable onRemove={handleRemove}>
Design
</Badge>
<Badge variant="outline" color="primary" rounded="md" removable onRemove={handleRemove}>
Product
</Badge>
</Box>
<Box
style="padding: var(--uix-space-4); border: 1px solid var(--uix-border); border-radius: var(--uix-radius-md); display: flex; flex-wrap: wrap; gap: var(--uix-space-2); align-items: center;"
>
<code style="min-inline-size: 8rem; color: var(--uix-text-faint);">with icon</code>
<Badge variant="soft" color="affirm" icon={sparkIcon}>Premium</Badge>
<Badge variant="solid" color="fulfill" icon={sparkIcon}>Beta</Badge>
<Badge variant="outline" color="risk" icon={sparkIcon}>Limited</Badge>
</Box>
<Box
style="padding: var(--uix-space-4); border: 1px solid var(--uix-border); border-radius: var(--uix-radius-md); display: flex; flex-wrap: wrap; gap: var(--uix-space-2); align-items: center;"
>
<code style="min-inline-size: 8rem; color: var(--uix-text-faint);">inline label</code>
<span style="display: inline-flex; align-items: center; gap: var(--uix-space-2);">
Subscription <Badge variant="soft" color="affirm" size="sm">Active</Badge>
</span>
<span style="display: inline-flex; align-items: center; gap: var(--uix-space-2);">
Build #4218 <Badge variant="solid" color="primary" size="sm">latest</Badge>
</span>
<span style="display: inline-flex; align-items: center; gap: var(--uix-space-2);">
API v2 <Badge variant="outline" color="loss" size="sm">deprecated</Badge>
</span>
</Box>
</Stack>
</section>
{/if}
{#if tab === 'api'}
<section data-uix-section>
<h2 data-uix-section-title>API reference</h2>
<p data-uix-section-desc>
Badge composes through <code>&lt;Box&gt;</code> so consumers can apply layout primitives
(gridColumn, margin, …) without wrapping. The five Badge-specific props are independent —
you can render a label-only badge, a dot-only chip, an icon + label, or a removable tag
without touching the others.
</p>
<div data-uix-subsection-head>Badge-specific 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">variant</td>
<td class="type">'soft' | 'solid' | 'outline' | 'ghost'</td>
<td>
<code>soft</code> (default) — tinted bg + text. <code>solid</code> — filled bg
+ contrast text. <code>outline</code> — border-only. <code>ghost</code> — text
only.
</td>
</tr>
<tr>
<td class="name">size</td>
<td class="type">'xs' | 'sm' | 'md' | 'lg'</td>
<td>
Drives padding · gap · font-size. Default <code>md</code>. No <code>xl</code> —
badges are inline labels, a larger size competes with body type.
</td>
</tr>
<tr>
<td class="name">color</td>
<td class="type">ColorRole</td>
<td>
Full 8-step palette. Map status semantics directly:
<code>affirm</code> → success, <code>risk</code> → warning,
<code>threat</code> → error, <code>loss</code> → archived / muted.
</td>
</tr>
<tr>
<td class="name">rounded</td>
<td class="type">'sm' | 'md' | 'lg' | 'full'</td>
<td>
Corner radius. Default <code>full</code> (pill). <code>md</code>/<code>lg</code>
for a soft-rect look at any size.
</td>
</tr>
<tr>
<td class="name">dot</td>
<td class="type">boolean</td>
<td>
Render a small coloured circle before the label — status-pill style. Uses the
solid palette step so it reads even on soft / outline / ghost variants.
</td>
</tr>
<tr>
<td class="name">icon</td>
<td class="type">Snippet?</td>
<td>
Optional snippet rendered before the label. Sized via
<code>--badge-icon-size</code> (1em — scales with the badge font-size).
</td>
</tr>
<tr>
<td class="name">removable</td>
<td class="type">boolean</td>
<td>
Render an X button at the trailing edge. Use for filter chips / dismissable
tags.
</td>
</tr>
<tr>
<td class="name">onRemove</td>
<td class="type">() =&gt; void</td>
<td>Callback fired when the user activates the remove button.</td>
</tr>
<tr>
<td class="name">removeLabel</td>
<td class="type">string?</td>
<td>
Override the remove button's accessible label. Defaults to the translated
<code>'Remove'</code>.
</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">shadcn/ui</td>
<td><code>&lt;Badge&gt;</code></td>
<td>
shadcn ships 4 variants (default/secondary/destructive/outline). UIX adds the
8-color matrix, <code>ghost</code> variant, and built-in
<code>dot</code> / <code>icon</code> / <code>removable</code> slots.
</td>
</tr>
<tr>
<td class="name">radix-themes</td>
<td><code>&lt;Badge&gt;</code></td>
<td>
Radix has variant + radius + color + high-contrast. UIX drops high-contrast
(handled by <code>solid</code>) and adds <code>dot</code> + <code>removable</code>
as first-class props instead of consumer-composed children.
</td>
</tr>
<tr>
<td class="name">chakra-ui</td>
<td><code>&lt;Tag&gt;</code> / <code>&lt;Badge&gt;</code></td>
<td>
Chakra splits Tag (with CloseButton) and Badge (label only). UIX collapses both
into one primitive — <code>removable</code> turns a badge into a tag.
</td>
</tr>
<tr>
<td class="name">mantine</td>
<td><code>&lt;Badge&gt;</code></td>
<td>
Mantine has <code>leftSection</code> / <code>rightSection</code> for icons. UIX
uses a typed <code>icon</code> snippet for the leading slot; the trailing slot
is reserved for <code>removable</code>.
</td>
</tr>
<tr>
<td class="name">ark-ui</td>
<td>—</td>
<td>
Ark doesn't ship a Badge — it's pure visual without a behavior surface (no
open / close / select to coordinate). UIX places it in eidos for the same
reason.
</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>{badgeMorfo.name}</td></tr>
<tr><td class="name">kebab</td><td><code>{badgeMorfo.kebab}</code></td></tr>
<tr><td class="name">scope</td><td>{badgeMorfo.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>role</th>
<th>optional</th>
</tr>
</thead>
<tbody>
{#each partsList as part (part.kebab)}
<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="type">{part.role ?? '—'}</td>
<td class="default">{part.optional ? 'yes' : 'no'}</td>
</tr>
{/each}
</tbody>
</table>
</div>
<div data-uix-subsection-head>Dot · aria</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>attr</th><th>value</th><th>severity</th></tr></thead>
<tbody>
<tr>
<td class="name"><code>aria-hidden</code></td>
<td>"true"</td>
<td class="type">literal</td>
</tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>Icon · aria</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>attr</th><th>value</th><th>severity</th></tr></thead>
<tbody>
<tr>
<td class="name"><code>aria-hidden</code></td>
<td>"true"</td>
<td class="type">literal</td>
</tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>Remove · aria</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>attr</th><th>value</th><th>severity</th></tr></thead>
<tbody>
<tr>
<td class="name"><code>role</code></td>
<td>button</td>
<td class="type">part default</td>
</tr>
<tr>
<td class="name"><code>type</code></td>
<td>"button"</td>
<td class="type">literal</td>
</tr>
<tr>
<td class="name"><code>aria-label</code></td>
<td>translationRef('components.badge.remove')</td>
<td class="type">recommended</td>
</tr>
</tbody>
</table>
</div>
</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>
Badge declares <strong>no semantic events</strong>. The chip is perceptual chrome — a
label, status pill, or tag — not a behavior surface. The optional
<code>Remove</code> button emits a click but the canonical shape for "user clicked the X"
is the consumer's <code>onRemove</code> handler, not a sema event the engine should
route.
</p>
<p data-uix-section-desc>
If you need a perceptual cue when a badge appears or removes (e.g. a tag added to a
filter list), attach it to the parent action — the <code>TagsInput</code>'s
<code>commit-add</code> / <code>commit-remove</code>, the search field's filter update,
etc.
</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/badge/badge.css</code>. All variants share
private tokens (<code>--_badge-bg</code>, <code>--_badge-fg</code>,
<code>--_badge-border-color</code>) set per variant + color cascade so the rules below
stay flat. The recipe surface (<code>--badge-*</code>) feeds the canonical accent slots
(track / border / solid / text / contrast).
</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-badge]</code></td>
<td><span data-uix-tag data-kind="morfo">morfo</span></td>
<td>Provider marker. Inline-flex chrome (padding, radius, font, border).</td>
</tr>
<tr>
<td class="name"><code>[data-badge][data-size='xs'..'lg']</code></td>
<td><span data-uix-tag data-kind="eidos">eidos</span></td>
<td>
Sets <code>--_badge-px</code> · <code>--_badge-py</code> ·
<code>--_badge-gap</code> · <code>--_badge-font-size</code>.
</td>
</tr>
<tr>
<td class="name"><code>[data-badge][data-rounded='sm'..'full']</code></td>
<td><span data-uix-tag data-kind="eidos">eidos</span></td>
<td>Sets <code>--_badge-radius</code>.</td>
</tr>
<tr>
<td class="name"><code>[data-badge][data-color='primary'..'loss']</code></td>
<td><span data-uix-tag data-kind="eidos">eidos</span></td>
<td>
Cascades track / border / solid / text / contrast from the canonical 8-color
palette.
</td>
</tr>
<tr>
<td class="name"><code>[data-badge][data-variant='soft'..'ghost']</code></td>
<td><span data-uix-tag data-kind="eidos">eidos</span></td>
<td>
Picks which palette slot becomes <code>--_badge-bg</code> /
<code>--_badge-fg</code> / <code>--_badge-border-color</code>.
</td>
</tr>
<tr>
<td class="name"><code>[data-badge-dot]</code></td>
<td><span data-uix-tag data-kind="morfo">morfo</span></td>
<td>Status dot — small filled circle, always uses the solid palette step.</td>
</tr>
<tr>
<td class="name"><code>[data-badge-icon]</code></td>
<td><span data-uix-tag data-kind="morfo">morfo</span></td>
<td>Icon slot — sized 1em so it scales with the badge font-size.</td>
</tr>
<tr>
<td class="name"><code>[data-badge-label]</code></td>
<td><span data-uix-tag data-kind="morfo">morfo</span></td>
<td>Label wrapper around the children text.</td>
</tr>
<tr>
<td class="name"><code>[data-badge-remove]</code></td>
<td><span data-uix-tag data-kind="morfo">morfo</span></td>
<td>
Remove button — circular X with hover tint. Focus-visible paints the global
focus ring.
</td>
</tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>All variants × colors (size = md)</div>
<Stack gap="var(--uix-space-3)">
{#each variants as v (v)}
<Box
style="display: flex; align-items: center; gap: var(--uix-space-3); padding: var(--uix-space-3); border: 1px solid var(--uix-border); border-radius: var(--uix-radius-md); flex-wrap: wrap;"
>
<code style="min-inline-size: 5rem; color: var(--uix-text-faint);">{v}</code>
{#each colors as c (c)}
<Badge variant={v} color={c}>{c}</Badge>
{/each}
</Box>
{/each}
</Stack>
<div data-uix-subsection-head>All sizes × rounded (variant = soft, color = primary)</div>
<Box
style="display: flex; flex-wrap: wrap; gap: var(--uix-space-4);"
>
{#each sizes as s (s)}
<Box
style="display: flex; align-items: center; gap: var(--uix-space-2); padding: var(--uix-space-3); border: 1px solid var(--uix-border); border-radius: var(--uix-radius-md);"
>
<code style="min-inline-size: 2rem; color: var(--uix-text-faint);">{s}</code>
{#each roundeds as r (r)}
<Badge size={s} rounded={r} color="primary">{r}</Badge>
{/each}
</Box>
{/each}
</Box>
<div data-uix-subsection-head>Slot matrix · dot · icon · removable</div>
<Box
style="display: flex; flex-wrap: wrap; gap: var(--uix-space-3); padding: var(--uix-space-3); border: 1px solid var(--uix-border); border-radius: var(--uix-radius-md);"
>
<Badge color="affirm">label only</Badge>
<Badge color="affirm" dot>dot</Badge>
<Badge color="affirm" icon={sparkIcon}>icon</Badge>
<Badge color="affirm" removable onRemove={handleRemove}>removable</Badge>
<Badge color="affirm" dot icon={sparkIcon}>dot + icon</Badge>
<Badge color="affirm" icon={sparkIcon} removable onRemove={handleRemove}>
icon + remove
</Badge>
<Badge color="affirm" dot removable onRemove={handleRemove}>dot + remove</Badge>
</Box>
</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">Provider role</td>
<td>
Default <code>&lt;span&gt;</code>, no implicit role. Screen readers read the
inline text alongside surrounding context — the badge is read as part of the
sentence it sits in, which is the right default for status / count chips.
</td>
</tr>
<tr>
<td class="name">Decorative slots</td>
<td>
<code>data-badge-dot</code> and <code>data-badge-icon</code> are
<code>aria-hidden="true"</code>. The label text alone carries semantic
meaning. Don't communicate state through the dot color only — pair it with a
word ("Active", "Failed").
</td>
</tr>
<tr>
<td class="name">Remove button</td>
<td>
<code>&lt;button type="button"&gt;</code> with translated
<code>aria-label="Remove"</code>. Activates via Enter / Space.
<code>stopPropagation</code> prevents the click from bubbling to a parent
interactive element (e.g. the row a filter tag sits in).
</td>
</tr>
<tr>
<td class="name">Focus visible</td>
<td>
Only the remove button is focusable. Focus ring uses the global
<code>--focus-ring-*</code> tokens.
</td>
</tr>
<tr>
<td class="name">Color contrast</td>
<td>
Each color × variant pair is auto-balanced by the canonical palette
(<code>track</code> + <code>text</code> for soft / outline / ghost,
<code>solid</code> + <code>contrast</code> for solid). Tested against WCAG AA
for normal text at all sizes ≥ <code>sm</code>.
</td>
</tr>
<tr>
<td class="name">High-contrast mode</td>
<td>
<code>outline</code> variant survives forced-colors (border + text use
<code>currentColor</code>). <code>ghost</code> degrades to text-only —
acceptable for label use, not for status pills (use <code>soft</code> or
<code>solid</code> there).
</td>
</tr>
<tr>
<td class="name">When to use</td>
<td>
Status pills, count chips, tags, version markers. For interactive selection use
<a href="/uix/components/tag-group">&lt;TagGroup&gt;</a>; for user-typed tags use
<a href="/uix/components/tags-input">&lt;TagsInput&gt;</a>; for filter chips use
<a href="/uix/components/toggle-group">&lt;ToggleGroup&gt;</a> with
<code>variant="chip"</code>.
</td>
</tr>
</tbody>
</table>
</div>
</section>
{/if}
</div>

Powered by TurnKey Linux.