From 0353170982fbd86312d66d903a75f37accc851ad Mon Sep 17 00:00:00 2001 From: dev Date: Fri, 22 May 2026 19:29:32 +0200 Subject: [PATCH] =?UTF-8?q?feat(typography):=20Text=20=E2=80=94=20POC=20co?= =?UTF-8?q?nsuming=20new=20--style-*=20foundation=20tokens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First typography primitive consuming the named-style tokens added in `015bd761`. Sets the canon for the remaining typography ports (Heading, Display, Code, CodeBlock, Kbd, Mark, Highlight, Link). API: - `style: 'body' | 'prose' | 'label' | 'caption'` (default `body`). Selects which `--style-{name}-*` token set the recipe consumes. - Per-axis overrides via `size`, `family`, `weight`, `color`, `align`. Each writes a local `--_text-{axis}` CSS variable that wins over the named style's default via the cascade. - `italic`, `underline` shorthand booleans. - `truncate` (single-line ellipsis) + `clamp={N}` (multi-line via CSS `-webkit-line-clamp`). - `as` prop switches the tag (default `'span'` — inline composable). - `tagStyle` escape hatch for one-off inline CSS that the prop surface doesn't cover (separate from the `style` named-style prop). Architectural choices documented in README: - Sizes map to eidos `xxs..xxxl` (not air's `1..12`). - Families use the eidos slot names (`primary/secondary/mono/display`) not air's domain aliases (`ui/prose/code`) — coherence with the foundation contract. - Colors are restricted to the content-role slots (`--color-content-*`) to preserve contrast guarantees. Free colors via `tagStyle`. - Canvas line-count measurement (air's `text` + bindable `lineCount`) deferred; CSS line-clamp covers the visual-truncate use case. Sidebar nav gains a `Typography` group with Text as its first entry. `npm run component:audit`: 82 / 82 PASS, 0 NEEDS-WORK. svelte-check 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/uix/eidos/components/text/README.md | 115 +++++ src/uix/eidos/components/text/index.ts | 29 ++ src/uix/eidos/components/text/text.css | 93 ++++ src/uix/eidos/components/text/text.svelte | 83 ++++ src/uix/eidos/components/text/types.ts | 67 +++ src/uix/eidos/index.css | 1 + src/uix/morfo/components/text.ts | 32 ++ web/routes/uix/+layout@.svelte | 5 + web/routes/uix/components/text/+page.svelte | 505 ++++++++++++++++++++ 9 files changed, 930 insertions(+) create mode 100644 src/uix/eidos/components/text/README.md create mode 100644 src/uix/eidos/components/text/index.ts create mode 100644 src/uix/eidos/components/text/text.css create mode 100644 src/uix/eidos/components/text/text.svelte create mode 100644 src/uix/eidos/components/text/types.ts create mode 100644 src/uix/morfo/components/text.ts create mode 100644 web/routes/uix/components/text/+page.svelte diff --git a/src/uix/eidos/components/text/README.md b/src/uix/eidos/components/text/README.md new file mode 100644 index 000000000..5d02d84ee --- /dev/null +++ b/src/uix/eidos/components/text/README.md @@ -0,0 +1,115 @@ +# Eidos Text + +Universal inline-flow typography primitive. Aplica uno de los named +styles del foundation (`body` / `prose` / `label` / `caption`) y +permite sobrescribir per-axis (size / family / weight / color / align) +sin tocar el resto. Render por defecto `` — composable dentro de +cualquier contenedor. + +## Superficie + +```svelte +Body copy. +Longer reading passage. +Image credit. +Single-line label that may overflow… +Three-line clamped paragraph. +Centered block paragraph. +``` + +## Baseline + +Origen: `air/components/typography/text` (rama `morfo-runtime`). +Adaptaciones para eidos: + +- **Sizes**: air `1..12` → eidos `xxs/xs/sm/md/lg/xl/xxl/xxxl`. Sin escala + separada — usamos el contrato existente `--font-size-{key}` / + `--font-line-height-{key}` / `--font-letter-spacing-{key}` del foundation. +- **Families**: air `'ui'|'prose'|'code'` → eidos `primary/secondary/mono/display`. + El foundation ya emite los aliases `--font-ui` / `--font-prose` / `--font-code` + pero preferimos referencias directas a `--font-family-{key}` para + claridad de origen. +- **Named styles**: nuevo respecto a air. Cuando se pasa `style="body"` + (default), `"prose"`, `"label"` o `"caption"`, el recipe consume los + tokens `--style-{name}-*` que el foundation emite (familia + size + + weight + line-height + letter-spacing + color, con overrides + responsive cuando aplica). Los per-prop overrides ganan vía local + `--_text-{axis}` declaradas inline por el componente. +- **Canvas measurement** del air (clamp con conteo exacto de líneas vía + canvas) se simplifica a CSS `-webkit-line-clamp` en este port — + ver Gaps. + +## Comparativa + +| Capacidad | UIX Text | Radix Themes Text | Chakra Text | Mantine Text | +| --- | --- | --- | --- | --- | +| Named style prop (body/prose/label/caption) | Sí (`style`) | Sí (`as` + `size`) | Sí (`textStyle`) | Sí (`size`) | +| Size scale | xxs..xxxl (8 steps) | 1..9 | xs..6xl | xs..xxl | +| Family override | Sí (`family`) | Sí | Sí (`fontFamily`) | Sí (`ff`) | +| Weight override | Sí (`weight`) | Sí | Sí (`fontWeight`) | Sí (`fw`) | +| Color override | Sí (`color` → content-role slot) | Sí (intent) | Sí (free) | Sí (free) | +| Align | Sí | Sí | Sí (`textAlign`) | Sí (`ta`) | +| Italic shorthand | Sí (`italic`) | No (style) | Sí (`fontStyle`) | Sí (`fs`) | +| Underline shorthand | Sí (`underline`) | Sí (`underline`) | Sí (`textDecoration`) | Sí (`td`) | +| Truncate (single-line ellipsis) | Sí (`truncate`) | Sí (`truncate`) | Sí (`isTruncated`) | Sí (`truncate`) | +| Line-clamp (multi-line) | Sí (`clamp={N}`, CSS only) | Sí (`truncate` + `weight`) | Sí (`noOfLines`) | Sí (`lineClamp`) | +| Canvas line-count measurement | **No** — gap | No | No | No | +| Bindable `lineCount` output | **No** — gap | No | No | No | +| `as` prop polymorphism | Sí | Sí | Sí | Sí | +| Responsive props | Sí (`ResponsiveProp`) | Sí | Sí | Sí | + +## Decisiones + +- **`style` prop apunta a named foundation tokens** — no recreamos los + combos family+size+weight. Cuando cambias el design system, los + Text con `style="body"` actualizan sin tocar componentes. +- **Per-prop overrides vía local CSS vars `--_text-*`** — escritas + inline en el span. El recipe usa `var(--_text-x, var(--style-{name}-x))` + para cascade limpio. +- **Default ``, no `

`** — Text es inline-flow primitive. + Composable dentro de un `

`, ` + + + + + + + + {#if tab === 'live'} +
+

Controls

+

+ Text is eidos-native — no soma split. The + style prop selects one of the four named foundation styles + (body / prose / label / caption); the rest of the props override individual axes via + local --_text-* CSS variables. Unset = inherit from the active style. +

+ +
+ eidos props · named style + axis overrides +
+
+ + + + + + + + + + + +
+ + +
+
+ soma + n/a · text is eidos-native — equivalent markup shown + svelte +
+
{somaSnippet}
+
+ +
+
+ eidos + visual · style + per-axis overrides + svelte +
+
{eidosSnippet}
+
+
+ {/if} + + {#if tab === 'api'} +
+

API reference

+

+ The style prop selects one of four named foundation tokens + (--style-{`{name}`}-{`{axis}`}). Per-axis props (size, + family, weight, color) emit local + --_text-{`{axis}`} CSS variables that override the named-style defaults. + Unset overrides inherit the style's value. +

+ +
Named style
+
+ + + + + +
PropTypeDefaultNotes
style'body' | 'prose' | 'label' | 'caption''body'Selects which --style-* token set the recipe consumes.
+
+ +
Per-axis overrides
+
+ + + + + + + + + +
PropTypeNotes
size'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'xxxl'Resolves to var(--font-size-{`{key}`}) + matching line-height / letter-spacing.
family'primary' | 'secondary' | 'mono' | 'display'Resolves to var(--font-family-{`{key}`}).
weight'regular' | 'medium' | 'semibold' | 'bold'Resolves to var(--font-weight-{`{key}`}).
color'primary' | 'secondary' | 'muted' | 'disabled' | 'on-solid'Resolves to var(--color-content-{`{key}`}). Free colors via tagStyle.
align'start' | 'center' | 'end' | 'justify'Maps to text-align.
+
+ +
Shorthand booleans
+
+ + + + + + +
PropTypeNotes
italicbooleanfont-style: italic.
underlinebooleantext-decoration: underline.
+
+ +
Truncation
+
+ + + + + + +
PropTypeNotes
truncatebooleanSingle-line ellipsis. Requires container width.
clampnumberMulti-line cap via CSS -webkit-line-clamp. Takes precedence over truncate.
+
+ +
Element + escape hatch
+
+ + + + + + +
PropTypeNotes
asstringHTML tag. Default 'span'. Use 'p' / 'em' / 'small' for semantic blocks.
tagStylestringExtra inline CSS. Appended after the recipe's vars.
+
+ +
Reference comparison
+
+ + + + + + + + +
LibraryClosest equivalentDifference
radix-themes<Text>Radix uses numeric size="1..9"; UIX maps to the named scale + per-style defaults.
chakra-ui<Text>Chakra accepts free color; UIX narrows to content-role slots for contract integrity.
mantine<Text>Mantine ships a 2-letter prop syntax (fz, fw, ta); UIX uses full names.
mui Typography<Typography variant="…">MUI's variant maps to a named style — same idea as UIX's style.
+
+
+ {/if} + + {#if tab === 'morfo'} +
+

Morfo contract

+ +
+ + + + + + + + + +
FieldValue
name{textMorfo.name}
kebab{textMorfo.kebab}
scope{textMorfo.scope.join(', ')}
parts{partsList.length}
events0
+
+ +
Parts
+
+ + + + {#each partsList as part} + + + + + + + + {/each} + +
kebabmarkerelementarchetypeoptional
{part.kebab}[{part.marker}]<{part.defaultElement}>{part.archetype}{part.optional ? 'yes' : 'no'}
+
+ +

+ The Provider part emits the data-text marker plus + data-style for the active named style and boolean attrs + (data-italic, data-underline, + data-truncate, data-clamp) for the visual + modifiers. Per-axis overrides write local CSS variables + (--_text-*) inline; the recipe falls through to the + --style-{`{name}`}-* foundation tokens when an override is + absent. +

+
+ {/if} + + {#if tab === 'sema'} +
+

+ sema · events +

+

+ Text declares no semantic events. As a passive typography primitive, it does not + commit, emerge, or react — it just styles its inline content. Components that animate + text (toast appearance, collapsible reveal) compose Text inside an interactive + primitive that owns the relevant sema verbs. +

+
+ {/if} + + {#if tab === 'recipe'} +
+

Eidos recipe

+

+ Recipe lives in src/uix/eidos/components/text/text.css. The selector + [data-text] applies the body-style fallback chain; matching the + [data-style] attribute swaps to the corresponding + --style-{`{name}`}-* tokens. Per-axis overrides come from the local + --_text-{`{axis}`} custom properties the component writes inline. +

+
+ + + + + + + + + + + + + + + + + + + +
SelectorOwnerPurpose
[data-text]morfoProvider marker. Default body-style applied.
[data-text][data-style='prose' | 'label' | 'caption']eidosSwap to the named style's --style-* token set.
[data-text][data-italic|data-underline|data-truncate|data-clamp]eidosVisual modifier flags emitted by boolean props.
+
+
+ {/if} + + {#if tab === 'a11y'} +
+

Accessibility

+
+ + + + + + + + +
ConcernContract
RoleNone implicit. Default tag is <span> — semantically neutral. Use as="p" for paragraphs, as="strong" / as="em" for emphasis with semantic weight.
Heading levelsOut of scope. Use the future <Heading level={`{1..6}`}> primitive for h1–h6 with the matching style.
Truncation + ATCSS-only truncation hides text visually but it remains in the DOM and is announced fully by screen readers. Don't rely on truncation to hide meaning.
Color contrastAll content-role colors meet WCAG AA against the foundation surfaces. Free colors via tagStyle bypass this guarantee — verify manually.
+
+
+ {/if} +