feat(eidos): shape `shape` prop on Badge / Card / Button (per-component family)

Makes the shape families usable per-instance, not just in the demo. New canonical
SHAPE_FAMILIES + ShapeFamily in lib/types (canon, like the variant archetypes).
Badge / Card / Button gain a typed `shape` prop (rounded - continuous - cut - scoop)
that emits `data-shape` -> the foundation [data-shape] corner-shape rule. Orthogonal
to `rounded` (radius magnitude); default = arc (no attr); degrades to arc where
corner-shape is unsupported.

A differentiator for chips/badges (<Badge shape="cut">) and a premium touch for
buttons (<Button shape="continuous"> = squircle). For cut/scoop prefer filled
variants (concave/angular corners seam with borders) — documented per prop + pendiente.md.

Verified: check 0 errors; eidos 190/193 (3 pre-existing `words` failures, unrelated).
Browser: real Badge data-shape=cut -> bevel, real Button data-shape=continuous ->
squircle, radius magnitude preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
active-uix
dev 4 months ago
parent 05a775c072
commit 6d56188fde

@ -2,18 +2,21 @@
## Forma (shape) — asignar la familia de borde por componente ## Forma (shape) — asignar la familia de borde por componente
Permitir que cada componente reciba la **familia de forma** (`rounded` · `continuous` · `cut` · Que cada componente reciba la **familia de forma** (`rounded` · `continuous` · `cut` · `scoop`)
`scoop`) como prop / `data-attr`, además del default del tema. Diferenciador fuerte sobre todo como prop, además del default del tema. Diferenciador fuerte en **chips y badges**.
en **chips y badges** — p. ej. un badge `cut` o `scoop` para una categoría frente al chip
`continuous` por defecto, sin inventar variantes nuevas. - ✅ **Hecho (2026-06-05)**: canon `SHAPE_FAMILIES` + tipo `ShapeFamily` en `lib/types`; prop
`shape` en **Badge · Card · Button** → emite `data-shape="{family}"` (la regla
- **API propuesta**: prop `shape` en el wrapper eidos (`<Badge shape="cut">`, `[data-shape]` del motor hace el resto). Ortogonal a `rounded` (magnitud); default = arco.
`<Chip shape="scoop">`) → emite `data-shape="{family}"` en el elemento. Hereda del tema si no - ⏳ **Pendiente — extender la prop** a más superficies: inputs (text-field/number-field/
se especifica. (Mecánica ya existente: la regla `[data-shape='{family}']` está en el motor; textarea/search-field), `tags-input` (los tags = chips, caso ideal de `cut`/`scoop`),
solo falta exponer la prop en las recipes.) contenidos flotantes (popover/dropdown/select content). Mismo patrón de 3 ediciones
(import `ShapeFamily` + prop + `data-shape`).
- **No** aplicar familias a círculos / píldoras (avatares): las deformaría (un avatar - **No** aplicar familias a círculos / píldoras (avatares): las deformaría (un avatar
`continuous` se vuelve squircle, un `cut` octágono). El default de esos componentes debe `continuous` se vuelve squircle, un `cut` octágono). El default de esos componentes debe
quedarse en `rounded`. quedarse en `rounded`.
- **Demo en componentes reales**: falta un control `shape` en las demos de badge/button/card
(hoy la prop existe y está verificada por probe, pero no se enseña en su demo).
- **scoop / cóncavo**: en Chromium actual, `corner-shape: scoop` con `border` + `box-shadow` - **scoop / cóncavo**: en Chromium actual, `corner-shape: scoop` con `border` + `box-shadow`
proyecta la sombra DENTRO de los entrantes y el borde de 1px hace costura en las esquinas proyecta la sombra DENTRO de los entrantes y el borde de 1px hace costura en las esquinas
cóncavas → se ve sucio. Para chips/badges `scoop` (y `cut`), preferir **relleno plano** sin cóncavas → se ve sucio. Para chips/badges `scoop` (y `cut`), preferir **relleno plano** sin

@ -21,6 +21,7 @@
size = 'md', size = 'md',
color = 'primary', color = 'primary',
rounded = 'full', rounded = 'full',
shape,
dot = false, dot = false,
removable = false, removable = false,
onRemove, onRemove,
@ -45,6 +46,7 @@
data-size={resolvedSize} data-size={resolvedSize}
data-color={color} data-color={color}
data-rounded={resolvedRounded} data-rounded={resolvedRounded}
data-shape={shape}
data-dot={dot ? '' : undefined} data-dot={dot ? '' : undefined}
data-removable={removable ? '' : undefined} data-removable={removable ? '' : undefined}
{...rest} {...rest}

@ -1,5 +1,5 @@
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import type { ChipVariant, ColorRole, ResponsiveProp, Size } from '$uix/eidos/lib/types'; import type { ChipVariant, ColorRole, ResponsiveProp, ShapeFamily, Size } from '$uix/eidos/lib/types';
import type { BoxProps } from '../box/types'; import type { BoxProps } from '../box/types';
/** /**
@ -43,8 +43,17 @@ export type BadgeProps = Omit<BoxProps, 'display'> & {
size?: ResponsiveProp<BadgeSize>; size?: ResponsiveProp<BadgeSize>;
/** Accent palette. @default 'primary' */ /** Accent palette. @default 'primary' */
color?: BadgeColor; color?: BadgeColor;
/** Corner radius. @default 'full' (pill) */ /** Corner radius (magnitude). @default 'full' (pill) */
rounded?: ResponsiveProp<BadgeRounded>; rounded?: ResponsiveProp<BadgeRounded>;
/**
* Corner shape family — `rounded` (arc, default) · `continuous` (squircle) · `cut` (bevel) ·
* `scoop` (concave). Orthogonal to `rounded` (which sets the radius magnitude); a strong
* differentiator for badges/chips. Degrades to the arc where `corner-shape` is unsupported.
* For `cut`/`scoop`, prefer a filled variant (`soft`/`solid`) — concave corners + a border or
* drop-shadow seam at the corner.
* @default 'rounded'
*/
shape?: ShapeFamily;
/** /**
* Show a small coloured dot before the label — status-pill style. * Show a small coloured dot before the label — status-pill style.
* Independent from `icon` (you can render both, though that's * Independent from `icon` (you can render both, though that's

@ -18,6 +18,7 @@
variant = 'solid', variant = 'solid',
size = 'md', size = 'md',
rounded, rounded,
shape,
block = false, block = false,
iconOnly = false, iconOnly = false,
loading = false, loading = false,
@ -43,6 +44,7 @@
data-variant={variant} data-variant={variant}
data-size={resolvedSize} data-size={resolvedSize}
data-rounded={resolvedRounded ?? undefined} data-rounded={resolvedRounded ?? undefined}
data-shape={shape}
data-block={block ? '' : undefined} data-block={block ? '' : undefined}
data-icon-only={iconOnly ? '' : undefined} data-icon-only={iconOnly ? '' : undefined}
data-has-icon={icon || (loading && spinnerPlacement === 'start') ? '' : undefined} data-has-icon={icon || (loading && spinnerPlacement === 'start') ? '' : undefined}

@ -1,6 +1,6 @@
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import type { ProviderProps, SpinnerProps } from '$soma/components/button'; import type { ProviderProps, SpinnerProps } from '$soma/components/button';
import type { ResponsiveProp, Size } from '$uix/eidos/lib/types'; import type { ResponsiveProp, ShapeFamily, Size } from '$uix/eidos/lib/types';
/** /**
* Button visual variant. The 6-variant set ported from air covers the * Button visual variant. The 6-variant set ported from air covers the
@ -40,8 +40,14 @@ export type ButtonProps = ProviderProps & {
variant?: ButtonVariant; variant?: ButtonVariant;
/** Sizing scale. @default 'md' */ /** Sizing scale. @default 'md' */
size?: ResponsiveProp<ButtonSize>; size?: ResponsiveProp<ButtonSize>;
/** Corner radius. @default inherits from size */ /** Corner radius (magnitude). @default inherits from size */
rounded?: ResponsiveProp<ButtonRounded>; rounded?: ResponsiveProp<ButtonRounded>;
/**
* Corner shape family — `rounded` (arc) · `continuous` (squircle) · `cut` (bevel) · `scoop`
* (concave). Orthogonal to `rounded` (radius magnitude). A squircle button reads notably more
* premium. Degrades to the arc where `corner-shape` is unsupported. @default 'rounded'
*/
shape?: ShapeFamily;
/** Stretch to fill the container's inline-size. @default false */ /** Stretch to fill the container's inline-size. @default false */
block?: boolean; block?: boolean;
/** /**

@ -25,6 +25,7 @@
size = 'md', size = 'md',
color = 'neutral', color = 'neutral',
rounded = 'md', rounded = 'md',
shape,
interactive = false, interactive = false,
selected = false, selected = false,
disabled = false, disabled = false,
@ -74,6 +75,7 @@
data-color={resolvedColor} data-color={resolvedColor}
data-color-custom={customColor ? '' : undefined} data-color-custom={customColor ? '' : undefined}
data-rounded={resolvedRounded} data-rounded={resolvedRounded}
data-shape={shape}
data-interactive={interactive ? '' : undefined} data-interactive={interactive ? '' : undefined}
data-selected={selected ? '' : undefined} data-selected={selected ? '' : undefined}
data-disabled={disabled ? '' : undefined} data-disabled={disabled ? '' : undefined}

@ -4,6 +4,7 @@ import type {
ChipVariant, ChipVariant,
ColorRole, ColorRole,
ResponsiveProp, ResponsiveProp,
ShapeFamily,
Size Size
} from '$uix/eidos/lib/types'; } from '$uix/eidos/lib/types';
import type { BoxProps } from '../box/types'; import type { BoxProps } from '../box/types';
@ -70,8 +71,16 @@ export type CardProps = Omit<BoxProps, 'display'> & {
* string (hex / rgb / oklch / named). @default 'neutral' * string (hex / rgb / oklch / named). @default 'neutral'
*/ */
color?: CardColor; color?: CardColor;
/** Corner radius. @default 'md' */ /** Corner radius (magnitude). @default 'md' */
rounded?: ResponsiveProp<CardRounded>; rounded?: ResponsiveProp<CardRounded>;
/**
* Corner shape family — `rounded` (arc) · `continuous` (squircle) · `cut` (bevel) · `scoop`
* (concave). Orthogonal to `rounded` (radius magnitude). Degrades to the arc where
* `corner-shape` is unsupported. For `cut`/`scoop` prefer the `soft`/`solid` variant — an
* `outline` border seams at concave/angular corners in current engines.
* @default 'rounded'
*/
shape?: ShapeFamily;
/** /**
* Make the card clickable. Promotes the Provider to `<button>` and * Make the card clickable. Promotes the Provider to `<button>` and
* enables hover / press / focus visual feedback. Fires * enables hover / press / focus visual feedback. Fires

@ -107,6 +107,17 @@ export type ChipVariant = (typeof EIDOS_VARIANTS.chip)[number];
export type MarkerVariant = (typeof EIDOS_VARIANTS.marker)[number]; export type MarkerVariant = (typeof EIDOS_VARIANTS.marker)[number];
export type TabsVariant = (typeof EIDOS_VARIANTS.tabs)[number]; export type TabsVariant = (typeof EIDOS_VARIANTS.tabs)[number];
/**
* Canonical Eidos shape families — the `corner-shape` archetypes a component opts into via the
* `shape` prop (→ `data-shape='{family}'`). Single source of truth; the foundation emits one
* `[data-shape='{family}']` rule per entry (SHAPE_ENGINE_RFC). `rounded` = the circular arc
* (default); `continuous` = the squircle (superellipse); `cut` = bevel; `scoop` = concave.
* Orthogonal to the radius MAGNITUDE (`--radius-*` / a component's `rounded` prop). Canon of the
* eidos, like the variant archetypes — a theme retunes the values, not the set.
*/
export const SHAPE_FAMILIES = ['rounded', 'continuous', 'cut', 'scoop'] as const;
export type ShapeFamily = (typeof SHAPE_FAMILIES)[number];
/** /**
* Shared Eidos color role vocabulary. `primary` and `secondary` are * Shared Eidos color role vocabulary. `primary` and `secondary` are
* hierarchy roles; the remaining values come from the UIX intent canon. * hierarchy roles; the remaining values come from the UIX intent canon.

Loading…
Cancel
Save

Powered by TurnKey Linux.