feat(typography): 8 primitives — Heading, Display, Code, CodeBlock, Kbd, Mark, Highlight, Link
Full canon for the rest of the typography family, following Text
(commit `03531709`) as template. Each primitive ships morfo (passive,
scope eidos, 0 events) + full eidos set (svelte/css/types/index +
README with required sections) + canon 6-tab demo (≥400 lines each).
**Heading** — semantic h1..h6 via `level` prop, visual style decoupled
via `style` prop (defaults to match level but `<Heading level={3}
style="h1">` allowed). Demo 594 lines.
**Display** — always-h1 page-title primitive. Always uses
`style="hero"`. Tracking via literal em values until foundation emits
hero-scale tracking tokens. Demo 546 lines.
**Code** — inline code via `<code>`. `variant: 'plain' | 'soft' |
'outline'` for subtle background chrome. Demo 423 lines.
**CodeBlock** — multi-line `<pre><code>` with optional title +
copy button (button utility, NOT a sema verb — kept out of morfo per
2-of-3 rule). Demo 402 lines.
**Kbd** — keyboard shortcut chrome over `style="code"`. Demo 418 lines.
**Mark** — text highlight via `<mark>`. Palette uses canonical
`ColorRole` intents (default `fulfill` ≈ legacy yellow). Demo 402 lines.
**Highlight** — substring highlighter inside Text. Scans children for
`query` and wraps matches. Same intent palette as Mark. Demo 407 lines.
**Link** — text link via `<a>`. `variant: 'default' | 'subtle' |
'plain'` for underline style, `intent: ColorRole` for color. Passive
on the morfo — hover/active is CSS-only, navigation belongs to the
consumer (router). Demo 448 lines.
**Architectural deviations from air documented per README's
Decisiones section:**
- Heading: semantic level vs visual style decoupling
- Code split into inline `<Code>` + block `<CodeBlock>` (was one
component with `variant='block'` in air)
- Mark/Highlight palette swapped from literal hue names to canonical
ColorRole intents (theme contract)
- Link palette opened to full ColorRole; `quiet`/`emphasized` air
variants dropped in favour of composable patterns
`npm run component:audit`: **90 / 90 PASS**, 0 NEEDS-WORK.
svelte-check: 0 errors, 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
<script lang="ts">
|
|
|
|
|
import './kbd.css';
|
feat(typography): 8 primitives — Heading, Display, Code, CodeBlock, Kbd, Mark, Highlight, Link
Full canon for the rest of the typography family, following Text
(commit `03531709`) as template. Each primitive ships morfo (passive,
scope eidos, 0 events) + full eidos set (svelte/css/types/index +
README with required sections) + canon 6-tab demo (≥400 lines each).
**Heading** — semantic h1..h6 via `level` prop, visual style decoupled
via `style` prop (defaults to match level but `<Heading level={3}
style="h1">` allowed). Demo 594 lines.
**Display** — always-h1 page-title primitive. Always uses
`style="hero"`. Tracking via literal em values until foundation emits
hero-scale tracking tokens. Demo 546 lines.
**Code** — inline code via `<code>`. `variant: 'plain' | 'soft' |
'outline'` for subtle background chrome. Demo 423 lines.
**CodeBlock** — multi-line `<pre><code>` with optional title +
copy button (button utility, NOT a sema verb — kept out of morfo per
2-of-3 rule). Demo 402 lines.
**Kbd** — keyboard shortcut chrome over `style="code"`. Demo 418 lines.
**Mark** — text highlight via `<mark>`. Palette uses canonical
`ColorRole` intents (default `fulfill` ≈ legacy yellow). Demo 402 lines.
**Highlight** — substring highlighter inside Text. Scans children for
`query` and wraps matches. Same intent palette as Mark. Demo 407 lines.
**Link** — text link via `<a>`. `variant: 'default' | 'subtle' |
'plain'` for underline style, `intent: ColorRole` for color. Passive
on the morfo — hover/active is CSS-only, navigation belongs to the
consumer (router). Demo 448 lines.
**Architectural deviations from air documented per README's
Decisiones section:**
- Heading: semantic level vs visual style decoupling
- Code split into inline `<Code>` + block `<CodeBlock>` (was one
component with `variant='block'` in air)
- Mark/Highlight palette swapped from literal hue names to canonical
ColorRole intents (theme contract)
- Link palette opened to full ColorRole; `quiet`/`emphasized` air
variants dropped in favour of composable patterns
`npm run component:audit`: **90 / 90 PASS**, 0 NEEDS-WORK.
svelte-check: 0 errors, 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
/**
|
|
|
|
|
* Eidos `<Kbd>` — keyboard shortcut visual.
|
|
|
|
|
*
|
|
|
|
|
* <Kbd>Ctrl</Kbd> + <Kbd>K</Kbd>
|
|
|
|
|
* <Kbd variant="outline" size="sm">Esc</Kbd>
|
|
|
|
|
*
|
|
|
|
|
* Renders `<kbd>` with a small chrome (border, background, inner
|
|
|
|
|
* padding) so the key looks like a physical cap.
|
|
|
|
|
*/
|
|
|
|
|
import { ActiveEidos } from '$uix/eidos';
|
|
|
|
|
import type { KbdProps } from './types';
|
|
|
|
|
|
|
|
|
|
let {
|
|
|
|
|
variant,
|
|
|
|
|
size,
|
|
|
|
|
as = 'kbd',
|
|
|
|
|
tagStyle,
|
|
|
|
|
class: className,
|
|
|
|
|
children,
|
|
|
|
|
...restProps
|
|
|
|
|
}: KbdProps = $props();
|
|
|
|
|
|
|
|
|
|
const eidos = ActiveEidos.require();
|
|
|
|
|
|
|
|
|
|
const resolvedVariant = $derived(eidos.resolve(variant) ?? 'surface');
|
|
|
|
|
|
|
|
|
|
function pushVar(decls: string[], name: string, value: string | undefined): void {
|
|
|
|
|
if (!value) return;
|
|
|
|
|
decls.push(`${name}: ${value};`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const inlineStyle = $derived.by(() => {
|
|
|
|
|
const decls: string[] = [];
|
|
|
|
|
const sizeKey = eidos.resolve(size);
|
|
|
|
|
if (sizeKey) pushVar(decls, '--_kbd-font-size', `var(--font-size-${sizeKey})`);
|
|
|
|
|
const inline = decls.join(' ');
|
|
|
|
|
return [inline, tagStyle].filter(Boolean).join(' ');
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<svelte:element
|
|
|
|
|
this={as}
|
|
|
|
|
{...restProps}
|
|
|
|
|
class={className}
|
|
|
|
|
data-kbd=""
|
|
|
|
|
data-variant={resolvedVariant}
|
|
|
|
|
style={inlineStyle}
|
|
|
|
|
>
|
|
|
|
|
{@render children?.()}
|
|
|
|
|
</svelte:element>
|