From 0a6d112cdeeec2ae3239fc70723d2fe9091de145 Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 20 Jun 2026 23:56:11 +0200 Subject: [PATCH] =?UTF-8?q?feat(motion):=20pilot=20the=20universal=20`moti?= =?UTF-8?q?on`=20prop=20on=20a=20real=20component=20(Badge)=20=E2=80=94=20?= =?UTF-8?q?(b)=20step=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First real consumer of `motionAttrs`: `` gains a `motion` prop so a designer can apply any registered preset as the badge's entrance — `` plays the preset's enter on mount, zero per-component machinery (the component just spreads `{...motionAttrs(motion)}`). Badge is the clean content-domain pilot: pure eidos primitive, no morfo events, no own `data-state` (so the helper's presentation `data-state="open"` doesn't clash — unlike Card, which has selected/idle + a built-in emerge). Additive + non-breaking: `motion` defaults to undefined (no animation). - badge/types.ts: `motion?: MotionPresetName`. - badge/badge.svelte: spread `motionAttrs(motion)` on the span. - badge demo: a "Motion" showcase (fade / scale-fade / slide-fade + Replay) — the prop demonstrated per the testbed doctrine. Verified at runtime: the three motion badges render `data-animation-style` + `data-state="open"` with the right enter `animation-name` (fade-in / scale-in, fade-in / slide-from-bottom, scale-in, fade-in). `check` clean for badge. (A fresh badge screenshot was blocked by preview-infra flakiness this round; the enter keyframes themselves were confirmed visually earlier via the cascade.) Establishes the pattern: any component becomes animation-selectable by accepting `motion` + spreading the helper. RFC §D.12 (b). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/uix/eidos/components/badge/badge.svelte | 4 ++- src/uix/eidos/components/badge/types.ts | 8 ++++++ web/routes/uix/components/badge/+page.svelte | 27 ++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/uix/eidos/components/badge/badge.svelte b/src/uix/eidos/components/badge/badge.svelte index 8df4518ae..f9279c3ed 100644 --- a/src/uix/eidos/components/badge/badge.svelte +++ b/src/uix/eidos/components/badge/badge.svelte @@ -12,7 +12,7 @@ * Optional Dot / Icon / Remove sub-parts compose inside. */ import './badge.css'; - import { ActiveEidos } from '$uix/eidos'; + import { ActiveEidos, motionAttrs } from '$uix/eidos'; import { BADGE_LANGS } from './langs'; import type { BadgeProps } from './types'; @@ -27,6 +27,7 @@ onRemove, removeLabel, icon, + motion, children, ...rest }: BadgeProps = $props(); @@ -49,6 +50,7 @@ data-shape={shape} data-dot={dot ? '' : undefined} data-removable={removable ? '' : undefined} + {...motionAttrs(motion)} {...rest} > {#if dot} diff --git a/src/uix/eidos/components/badge/types.ts b/src/uix/eidos/components/badge/types.ts index 25dfd9a85..ba55e9956 100644 --- a/src/uix/eidos/components/badge/types.ts +++ b/src/uix/eidos/components/badge/types.ts @@ -1,5 +1,6 @@ import type { Snippet } from 'svelte'; import type { ChipVariant, ColorRole, ResponsiveProp, ShapeFamily, Size } from '$uix/eidos/lib/types'; +import type { MotionPresetName } from '$uix/eidos/lib/motion/registry'; import type { BoxProps } from '../box/types'; /** @@ -78,4 +79,11 @@ export type BadgeProps = Omit & { icon?: Snippet; /** Badge label. */ children?: Snippet; + /** + * Entrance animation — any registered motion preset (`fade` / `scale-fade` / + * `slide-fade` / …). The badge plays the preset's enter on mount (content + * domain — RFC §D.12; routed via `motionAttrs`). `undefined` (default) = no + * animation. A common use: a notification badge that pops in. + */ + motion?: MotionPresetName; }; diff --git a/web/routes/uix/components/badge/+page.svelte b/web/routes/uix/components/badge/+page.svelte index df39b81d9..6ba6101f8 100644 --- a/web/routes/uix/components/badge/+page.svelte +++ b/web/routes/uix/components/badge/+page.svelte @@ -30,6 +30,8 @@ let withIcon = $state(false); let removable = $state(false); let removeCount = $state(0); + // Remount key so the on-mount entrance animation can be replayed on demand. + let motionReplay = $state(0); const variants: BadgeVariant[] = ['soft', 'solid', 'outline', 'ghost']; const sizes: BadgeSize[] = ['xs', 'sm', 'md', 'lg']; @@ -770,6 +772,31 @@ dot + remove + +
Motion · entrance animation (content domain · RFC §D.12)
+ +

+ Any component can opt into a registered motion preset via the motion prop — + the badge plays its enter on mount (routed through motionAttrs, reusing the + existing preset, no per-component machinery). undefined = no animation. +

+ + {#key motionReplay} + + motion="fade" + motion="scale-fade" + motion="slide-fade" + + {/key} +
{/if}