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

555 lines
25 KiB

<script lang="ts">
/**
* Button — canary for the v2 demo layout. Exercises the WHOLE system
* surface the old template hid: the full 31-scale palette on the real
* `color` prop (gated by the intent↔color doctrine), the border/shape/
* radius axes, the system foundation axes (density · scaling · mode · dir),
* the motion catalog, and the sema firma with audible sound.
*/
import {
Button,
type ButtonVariant,
type ButtonSize,
type ButtonRounded,
type ButtonChildProps
} from '$uix/eidos/components/button';
import { SHAPE_FAMILIES, type ShapeFamily, type ComponentColor } from '$uix/eidos/lib/types';
import type { ButtonIntent } from '$soma/components/button';
import { compileMorfo } from '$uix/morfo';
import { buttonMorfo } from '@/uix/morfo/components/button';
import { getActiveUix } from '$active-uix';
import * as Icon from '$uix/eidos/components/icon';
import PalettePicker from '../../lib/PalettePicker.svelte';
import SystemAxes from '../../lib/SystemAxes.svelte';
import MotionPanel from '../../lib/MotionPanel.svelte';
import SemaPanel from '../../lib/SemaPanel.svelte';
import { DemoTrace, PALETTE_SCALES } from '../../lib/harness.svelte';
import TokensPanel from '../../lib/TokensPanel.svelte';
const uix = getActiveUix();
type IconKey = 'none' | 'save' | 'send' | 'trash' | 'settings' | 'heart' | 'download' | 'check';
const iconKeys: IconKey[] = ['none', 'save', 'send', 'trash', 'settings', 'heart', 'download', 'check'];
const iconMap = {
none: null,
save: Icon.Save,
send: Icon.Send,
trash: Icon.Trash,
settings: Icon.Settings,
heart: Icon.Heart,
download: Icon.Download,
check: Icon.Check
} as const;
type Tab = 'live' | 'system' | 'motion' | 'sema' | 'services' | 'api' | 'morfo' | 'recipe' | 'tokens'
| 'a11y';
let tab = $state<Tab>('live');
const variants: ButtonVariant[] = ['solid', 'soft', 'surface', 'outline', 'ghost', 'plain'];
const sizes: ButtonSize[] = ['xs', 'sm', 'md', 'lg', 'xl'];
const roundeds: ButtonRounded[] = ['sm', 'md', 'lg', 'xl', 'full'];
// ── Live (component API) state ──────────────────────────────────────────
let intent = $state<string>('neutral');
let color = $state<string | undefined>(undefined);
let variant = $state<ButtonVariant>('solid');
let size = $state<ButtonSize>('md');
let rounded = $state<ButtonRounded | undefined>(undefined);
let shape = $state<ShapeFamily>('rounded');
let block = $state(false);
let iconOnly = $state(false);
let disabled = $state(false);
let loading = $state(false);
let type = $state<'button' | 'submit' | 'reset'>('button');
// asChild: the same Button, painting an <a>. Note `type` disappears from the
// stage element — it belongs to a <button>, and the element is now yours.
let asChild = $state(false);
let iconKey = $state<IconKey>('none');
let label = $state('Save changes');
// ── System (foundation) axes — applied to the stage ─────────────────────
let density = $state('comfortable');
let scaling = $state(1);
let mode = $state<'inherit' | 'light' | 'dark'>('inherit');
let dirMode = $state<'auto' | 'ltr' | 'rtl'>('auto');
// `auto` passes no `dir` prop, so the component resolves the documented
// chain `prop -> soma.prefs.getDir() -> 'ltr'` and follows the shell's
// direction switch — what a real consumer gets. ltr/rtl force an override.
const dir = $derived(dirMode === 'auto' ? undefined : dirMode);
let borderWidth = $state(1);
const stageStyle = $derived(`--scaling: ${scaling}; --border-width: ${borderWidth}px;`);
// ── Live trace (data-event) ─────────────────────────────────────────────
const trace = new DemoTrace();
let stageRef = $state<HTMLElement | null>(null);
$effect(() => {
if (stageRef) return trace.observe(stageRef);
});
// ── Compiled morfo ──────────────────────────────────────────────────────
const compiled = compileMorfo(buttonMorfo);
const partsList = $derived([...compiled.parts.byKebab.values()]);
const actions = $derived([...compiled.actions.byName.values()]);
const SelectedIcon = $derived(iconMap[iconKey]);
// ── Snippets (parity with the live preview) ─────────────────────────────
const eidosSnippet = $derived(
[
"<script lang='ts'>",
" import { Button } from '$uix/eidos/components/button';",
" import * as Icon from '$uix/eidos/components/icon';",
'</' + 'script>',
'',
'<Button',
variant !== 'solid' && ` variant="${variant}"`,
size !== 'md' && ` size="${size}"`,
intent !== 'neutral' && ` intent="${intent}"`,
intent === 'neutral' && color && ` color="${color}"`,
rounded && ` rounded="${rounded}"`,
shape !== 'rounded' && ` shape="${shape}"`,
block && ' block',
iconOnly && ' iconOnly',
disabled && ' disabled',
loading && ' loading',
iconKey !== 'none' && ` {#snippet icon()}<Icon.${iconKey[0].toUpperCase() + iconKey.slice(1)} />{/snippet}`,
asChild &&
' {#snippet child({ props, content })}<a href="/pricing" {...props}>{@render content()}</a>{/snippet}',
iconOnly ? ` aria-label="${label}"` : null,
'>',
` ${label}`,
'</Button>'
]
.filter(Boolean)
.join('\n')
);
</script>
<div data-uix-canvas-inner>
<header>
<div data-uix-eyebrow>Controls · Button</div>
<h1 data-uix-page-title>Button</h1>
<p data-uix-page-lede>
Single-press action surface — and the canary for the v2 demo layout. The
<code>color</code> prop takes the full 31-scale palette (gated by the intent↔color
doctrine), and the demo exposes every system axis: border, shape, density, scaling,
mode, direction, the motion catalog, and the sema firma with audible sound.
</p>
<div data-uix-page-meta>
<span data-uix-meta-pill><span data-uix-meta-key>parts</span>{partsList.length}</span>
<span data-uix-meta-pill><span data-uix-meta-key>events</span>{actions.length}</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>shapes</span>{SHAPE_FAMILIES.length}</span>
<span data-uix-meta-pill><span data-uix-meta-key>palette</span>{PALETTE_SCALES.length}</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>apg</span>
<a href={buttonMorfo.apg} target="_blank" rel="noreferrer">button ↗</a>
</span>
</div>
</header>
<!-- Live preview always rendered, with the System axes applied to the stage -->
<div data-uix-stage>
<div
data-uix-stage-area
bind:this={stageRef}
data-density={density}
data-theme={mode === 'inherit' ? undefined : mode}
data-mode={mode === 'inherit' ? undefined : mode}
{...dir ? { dir } : {}}
style={stageStyle}
>
{#snippet glyph()}
{#if SelectedIcon}<SelectedIcon />{/if}
{/snippet}
<!-- Declared at this level (not inside an {#if}) so it can be PASSED
conditionally: a snippet declared in a block is scoped to it. -->
{#snippet anchorChild({ props, content }: ButtonChildProps)}
<a href="#pricing" {...props}>{@render content()}</a>
{/snippet}
<Button
child={asChild ? anchorChild : undefined}
{variant}
{size}
intent={intent as ButtonIntent}
color={color as ComponentColor | undefined}
rounded={rounded ?? undefined}
{shape}
{block}
{iconOnly}
{disabled}
{loading}
{type}
icon={iconKey !== 'none' ? glyph : undefined}
aria-label={iconOnly ? label : undefined}
>
{label}
</Button>
</div>
<div data-uix-stage-trace>
<span data-uix-stage-trace-key>trace</span>
{#if trace.entries.length === 0}
<span>click the button (or ▶ play in Sema) to see events</span>
{:else}
{#each trace.entries.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
{/each}
{/if}
<span style="margin-inline-start: auto;">
<span data-uix-stage-trace-key>color</span> {intent !== 'neutral' ? intent : (color ?? 'primary')}
</span>
</div>
</div>
<!-- Tabs -->
<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 === 'system'} onclick={() => (tab = 'system')}>
<span data-uix-layer-badge="eidos">eidos</span> System
</button>
<button data-uix-tab data-active={tab === 'motion'} onclick={() => (tab = 'motion')}>Motion</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>{actions.length}</span>
</button>
<button data-uix-tab data-active={tab === 'services'} onclick={() => (tab = 'services')}>Services</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 · {actions.length}e</span>
</button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>106</span>
</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>
The component API. Color is a real prop — pick a hierarchy role or any of the 31
palette scales (it yields to an evaluative <code>intent</code>). Foundation axes
(density, scaling, mode, dir, border) live in the <button data-uix-linklike onclick={() => (tab = 'system')}>System</button> tab.
</p>
<div data-uix-subsection-head>
<span data-uix-layer-badge="eidos">eidos</span> color · intent ↔ palette
</div>
<div data-uix-controls>
<PalettePicker bind:intent bind:color />
</div>
<div data-uix-subsection-head>
<span data-uix-layer-badge="eidos">eidos</span> shape · the bordered surface
</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}
<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}
<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>rounded <span data-uix-control-hint>radius magnitude</span></span>
<span data-uix-chips role="radiogroup">
<button data-uix-chip data-active={rounded === undefined} onclick={() => (rounded = undefined)}>auto</button>
{#each roundeds as 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 data-uix-control-hint>corner-shape engine</span></span>
<span data-uix-chips role="radiogroup">
{#each SHAPE_FAMILIES as sh}
<button data-uix-chip data-active={shape === sh} onclick={() => (shape = sh)}>{sh}</button>
{/each}
</span>
</label>
</div>
<div data-uix-subsection-head>
<span data-uix-layer-badge="soma">soma</span> behavior + layout flags
</div>
<div data-uix-controls>
<label data-uix-control>
<span data-uix-control-label>disabled</span>
<span data-uix-switch><input type="checkbox" bind:checked={disabled} /><span data-uix-switch-label>{disabled ? 'on' : 'off'}</span></span>
</label>
<label data-uix-control>
<span data-uix-control-label>loading</span>
<span data-uix-switch><input type="checkbox" bind:checked={loading} /><span data-uix-switch-label>{loading ? 'on' : 'off'}</span></span>
</label>
<label data-uix-control>
<span data-uix-control-label>block</span>
<span data-uix-switch><input type="checkbox" bind:checked={block} /><span data-uix-switch-label>{block ? 'on' : 'off'}</span></span>
</label>
<label data-uix-control>
<span data-uix-control-label>iconOnly</span>
<span data-uix-switch><input type="checkbox" bind:checked={iconOnly} /><span data-uix-switch-label>{iconOnly ? 'on' : 'off'}</span></span>
</label>
<label data-uix-control>
<span data-uix-control-label>type</span>
<span data-uix-chips role="radiogroup">
{#each ['button', 'submit', 'reset'] as t}
<button data-uix-chip data-active={type === t} onclick={() => (type = t as typeof type)}>{t}</button>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>child (asChild → &lt;a&gt;)</span>
<span data-uix-switch><input type="checkbox" bind:checked={asChild} /><span data-uix-switch-label>{asChild ? 'on' : 'off'}</span></span>
</label>
<label data-uix-control>
<span data-uix-control-label>icon</span>
<span data-uix-chips role="radiogroup">
{#each iconKeys as k}
<button data-uix-chip data-active={iconKey === k} onclick={() => (iconKey = k)}>{k}</button>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>label</span>
<input type="text" bind:value={label} />
</label>
</div>
<div data-uix-code>
<div data-uix-code-head>
<span data-uix-layer-badge="eidos">eidos</span>
<span>visual · variant · size · color · shape</span>
<span data-uix-code-lang>svelte</span>
</div>
<pre><code>{eidosSnippet}</code></pre>
</div>
</section>
{/if}
{#if tab === 'system'}
<section data-uix-section>
<h2 data-uix-section-title><span data-uix-layer-badge="eidos">eidos</span> System axes</h2>
<p data-uix-section-desc>
The foundation knobs that are NOT component props, applied live to the stage above.
These retint / rescale every component identically — change one and watch the preview.
Radius / shape (component props) live in the Live tab.
</p>
<SystemAxes bind:density bind:scaling bind:mode bind:dir={dirMode} bind:borderWidth />
<p data-uix-section-desc style="margin-top: var(--uix-space-4);">
<strong>Theme vs prop.</strong> Here you change the <em>system</em> (mode, density, zoom,
border). To change this component's own color you use its <code>color</code> prop (Live).
Both reach the same DOM: the prop writes <code>data-color</code>; the theme retints the
role scales globally.
</p>
</section>
{/if}
{#if tab === 'motion'}
<section data-uix-section>
<h2 data-uix-section-title>Motion catalog</h2>
<p data-uix-section-desc>
Every <code>motion</code> preset, applied to a live sample with retunable duration /
easing tokens and a reduced-motion preview. Button itself animates via the canonical
press squeeze (click it) and the <code>contact-activate</code> firma (see Sema); the
<code>motion</code> prop is for components that mount/loop content.
</p>
<MotionPanel note="Reduced motion is honored for free: every preset declares a reduce policy, and the engine emits both a [data-motion='reduce'] override and a @media (prefers-reduced-motion) block." />
</section>
{/if}
{#if tab === 'sema'}
<section data-uix-section>
<h2 data-uix-section-title><span data-uix-layer-badge="sema">sema</span> firma + sound</h2>
<p data-uix-section-desc>
Button declares one event — <code>contact-activate</code> (book cap. 22 §10): the press
is reception, it carries NO intent (the evaluative weight lives in whatever commit/signal
the consumer fires downstream). Below is the firma the engine resolves for it. Turn on
<strong>Semantics → Sound</strong> in the topbar, then press ▶ play to hear it on the live button.
</p>
<SemaPanel {actions} {uix} boundIntent={intent} getTarget={() => stageRef?.querySelector('[data-button]') ?? stageRef} />
</section>
{/if}
{#if tab === 'services'}
<section data-uix-section>
<h2 data-uix-section-title>Services</h2>
<p data-uix-section-desc>
Button consumes one app service: <strong>langs</strong> (i18n). Its morfo declares
translatable <code>texts</code> that resolve through <code>uix.langs</code> — switch the
language in the topbar to see them follow. It does not consume format / dates / currency.
</p>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Text key</th><th>Default</th><th>Service</th></tr></thead>
<tbody>
<tr><td class="name">label</td><td class="type">{buttonMorfo.texts?.label ?? '—'}</td><td>langs</td></tr>
<tr><td class="name">loading</td><td class="type">{buttonMorfo.texts?.loading ?? '—'}</td><td>langs</td></tr>
</tbody>
</table>
</div>
<p data-uix-section-desc style="margin-top: var(--uix-space-3);">
Components with richer surfaces (Date field, FormatNumber, RelativeTime) expand this tab
with locale / currency / unit / live-region (announce) controls.
</p>
</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">intent</td><td class="type">'neutral' | 'affirm' | 'fulfill' | 'risk' | 'threat' | 'loss'</td><td class="default">'neutral'</td><td>Evaluative tint. Wins over <code>color</code>. Does NOT load the contact event with intent.</td></tr>
<tr><td class="name">color</td><td class="type">HierarchyRole | Intent | PaletteScale</td><td class="default">'primary'</td><td>Hierarchy role OR any of the 31 palette scales (§25.5). Applies under <code>intent='neutral'</code>.</td></tr>
<tr><td class="name">variant <span data-uix-tag data-kind="eidos">eidos</span></td><td class="type">'solid' | 'soft' | 'surface' | 'outline' | 'ghost' | 'plain'</td><td class="default">'solid'</td><td>Visual treatment.</td></tr>
<tr><td class="name">size <span data-uix-tag data-kind="eidos">eidos</span></td><td class="type">ResponsiveProp&lt;'xs'..'xl'&gt;</td><td class="default">'md'</td><td>Sizing scale.</td></tr>
<tr><td class="name">rounded <span data-uix-tag data-kind="eidos">eidos</span></td><td class="type">'sm' | 'md' | 'lg' | 'xl' | 'full'</td><td class="default empty">inherits size</td><td>Corner radius magnitude.</td></tr>
<tr><td class="name">shape <span data-uix-tag data-kind="eidos">eidos</span></td><td class="type">'rounded' | 'continuous' | 'cut' | 'scoop'</td><td class="default">'rounded'</td><td>Corner-shape family (squircle / bevel / scoop).</td></tr>
<tr><td class="name">block / iconOnly <span data-uix-tag data-kind="eidos">eidos</span></td><td class="type">boolean</td><td class="default">false</td><td>Stretch to fill / square aspect.</td></tr>
<tr><td class="name">disabled / loading</td><td class="type">boolean</td><td class="default">false</td><td>Disabled OR-merges with Field; loading gates the click + shows the Spinner.</td></tr>
<tr><td class="name">icon / endIcon / spinner <span data-uix-tag data-kind="eidos">eidos</span></td><td class="type">Snippet</td><td class="default empty">—</td><td>Leading / trailing glyph, loading indicator.</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/button.ts</code>. The `data-color` enum lists the 9 roles; the palette scales reach `data-color` as the per-instance override (§25.5) the eidos recipe materialises.</p>
<div data-uix-subsection-head>Parts</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Part</th><th>Marker</th><th>Element</th><th>Role</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.role ?? '—'}</td>
<td class="default">{part.archetype ?? '—'}</td>
<td class="default">{part.optional ? 'yes' : 'no'}</td>
</tr>
{/each}
</tbody>
</table>
</div>
<div data-uix-subsection-head>Events</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>name</th><th>family</th><th>verb</th><th>sequence</th><th>intent</th><th>target</th></tr></thead>
<tbody>
{#each actions as action}
{@const sem = action.semantic}
<tr>
<td class="name">{action.name}</td>
<td class="type">{sem.family}</td>
<td>{sem.verb ?? '—'}</td>
<td>{sem.sequence ?? 'pre'}</td>
<td class="default">{'intent' in sem && typeof sem.intent === 'string' ? sem.intent : '—'}</td>
<td>{action.target}</td>
</tr>
{/each}
</tbody>
</table>
</div>
</section>
{/if}
{#if tab === 'tokens'}
<TokensPanel component="button" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'}
<section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2>
<p data-uix-section-desc>
Selectors at <code>src/uix/eidos/components/button/button.css</code>; tokens
<code>--button-*</code>. The palette cascade — <code>[data-button][data-color='&lt;scale&gt;']</code>
for every donor scale — is generated from <code>recipes/base.ts</code> with a graceful
fallback to the primary role (so apps without <code>generated/palette.css</code> degrade,
not break).
</p>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Selector</th><th>Source</th></tr></thead>
<tbody>
<tr><td class="name">[data-button]</td><td><span data-uix-tag data-kind="soma">morfo</span></td></tr>
<tr><td class="name">[data-button][data-variant='X']</td><td><span data-uix-tag data-kind="eidos">eidos</span></td></tr>
<tr><td class="name">[data-button][data-size='X']</td><td><span data-uix-tag data-kind="eidos">eidos</span></td></tr>
<tr><td class="name">[data-button][data-color='X'] · 9 roles + 31 scales</td><td><span data-uix-tag data-kind="eidos">eidos</span></td></tr>
<tr><td class="name">[data-button][data-rounded='X'] / [data-shape='X']</td><td><span data-uix-tag data-kind="eidos">eidos</span></td></tr>
<tr><td class="name">[data-button][data-loading] / [data-disabled]</td><td><span data-uix-tag data-kind="soma">morfo</span></td></tr>
</tbody>
</table>
</div>
</section>
{/if}
{#if tab === 'a11y'}
<section data-uix-section>
<h2 data-uix-section-title>Accessibility</h2>
<p data-uix-section-desc>Follows the APG <a href={buttonMorfo.apg} target="_blank" rel="noreferrer">button pattern</a>. Native <code>&lt;button&gt;</code> — Enter / Space activate.</p>
<div data-uix-subsection-head>Keyboard</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Key</th><th>Action</th></tr></thead>
<tbody>
<tr><td><span data-uix-kbd>Enter</span> / <span data-uix-kbd>Space</span></td><td>Activate (fires contact-activate).</td></tr>
<tr><td><span data-uix-kbd>Enter</span></td><td><code>child</code> over an <code>&lt;a href&gt;</code>: a link activates on Enter only — Space scrolls. That is correct, and it is why Button lends paint instead of growing an <code>href</code>.</td></tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>Composition (<code>child</code>)</div>
<p data-uix-section-desc>
The element is the consumer's, so <code>type</code> is not stamped on it (an
<code>&lt;a type="button"&gt;</code> is a bogus MIME hint). Render the
<code>content</code> snippet inside your element and the icon / label /
spinner slots stay alive.
</p>
<div data-uix-subsection-head>ARIA contract</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Attribute</th><th>Value</th></tr></thead>
<tbody>
<tr><td class="name">aria-disabled</td><td class="type">"true" (when disabled)</td></tr>
<tr><td class="name">aria-busy</td><td class="type">"true" (when loading)</td></tr>
<tr><td class="name">aria-label</td><td class="type">required when iconOnly</td></tr>
</tbody>
</table>
</div>
</section>
{/if}
</div>
<style>
[data-uix-linklike] {
border: 0;
background: none;
padding: 0;
color: var(--uix-accent-text);
font: inherit;
text-decoration: underline;
text-underline-offset: 2px;
cursor: pointer;
}
</style>

Powered by TurnKey Linux.