@a4ui/core 0.29.1 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ import { JSX } from 'solid-js';
2
+ /** Fan shape applied when a {@link CardSpread} opens. Defaults to `'arc'`. */
3
+ export type CardSpreadLayout = 'arc' | 'long-arc' | 'linear' | 'corner' | 'cascade' | 'scatter' | 'wheel';
4
+ export interface CardSpreadProps {
5
+ /** Card contents; each entry becomes one card in the stack. */
6
+ items: JSX.Element[];
7
+ /** Fan shape. @default 'arc' */
8
+ layout?: CardSpreadLayout;
9
+ /** Controlled open state. Omit to spread on hover instead. */
10
+ open?: boolean;
11
+ /** Strength knob: degrees for arced/wheel layouts, px for linear/corner/cascade/scatter. Per-layout default applies when omitted. */
12
+ spread?: number;
13
+ class?: string;
14
+ 'aria-label'?: string;
15
+ }
16
+ /**
17
+ * A stack of cards that fans out into an arc, line, corner, cascade, scatter,
18
+ * or wheel shape. At rest the cards sit in a near-centered pile; opening
19
+ * (controlled via `open`, or on hover when uncontrolled) spreads them.
20
+ * Animates only `transform`/`opacity` (Motion spring) so the fan stays
21
+ * compositor-only, and falls back to a static fanned layout under reduced
22
+ * motion.
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * <CardSpread
27
+ * layout="arc"
28
+ * aria-label="Trip itinerary cards"
29
+ * items={[
30
+ * <div class="p-4">Day 1 — Arrival</div>,
31
+ * <div class="p-4">Day 2 — Old Town</div>,
32
+ * <div class="p-4">Day 3 — Coast drive</div>,
33
+ * ]}
34
+ * />
35
+ * ```
36
+ */
37
+ export declare function CardSpread(props: CardSpreadProps): JSX.Element;
@@ -0,0 +1,38 @@
1
+ import { JSX } from 'solid-js';
2
+ /** Layout curve for a {@link Carousel3D}. Defaults to `'coverflow'`. */
3
+ export type Carousel3DVariant = 'coverflow' | 'arc';
4
+ export interface Carousel3DProps {
5
+ /** Slides shown around the active one, arranged by distance from it. */
6
+ slides: JSX.Element[];
7
+ /** Layout curve. @default 'coverflow' */
8
+ variant?: Carousel3DVariant;
9
+ /** Controlled active index. */
10
+ active?: number;
11
+ /** Initial active index when uncontrolled. @default 0 */
12
+ defaultActive?: number;
13
+ /** Fires with the new index whenever the active slide changes. */
14
+ onChange?: (index: number) => void;
15
+ class?: string;
16
+ }
17
+ /**
18
+ * A 3D carousel that arranges slides around the active one on a coverflow or
19
+ * shallow arc curve — `perspective` + `preserve-3d`, each slide positioned
20
+ * purely by `transform`/`opacity` (Motion spring) so it stays
21
+ * compositor-only. Supports controlled/uncontrolled `active`, prev/next
22
+ * buttons, dot indicators, arrow-key navigation, and pointer-drag swiping.
23
+ * Falls back to a flat opacity crossfade (no 3D transforms) under reduced
24
+ * motion.
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * <Carousel3D
29
+ * variant="coverflow"
30
+ * slides={[
31
+ * <div class="grid h-full place-items-center rounded-xl border border-border bg-card p-6 text-center">Trail A — Ridge Loop</div>,
32
+ * <div class="grid h-full place-items-center rounded-xl border border-border bg-card p-6 text-center">Trail B — Falls Overlook</div>,
33
+ * <div class="grid h-full place-items-center rounded-xl border border-border bg-card p-6 text-center">Trail C — Summit Pass</div>,
34
+ * ]}
35
+ * />
36
+ * ```
37
+ */
38
+ export declare function Carousel3D(props: Carousel3DProps): JSX.Element;
@@ -0,0 +1,32 @@
1
+ import { JSX } from 'solid-js';
2
+ export interface FocusBlurGroupProps<T> {
3
+ /** The full data set to render. */
4
+ items: T[];
5
+ /** Row renderer. */
6
+ children: (item: T, index: number) => JSX.Element;
7
+ /** Blur (px) applied to non-focused items. @default 2 */
8
+ blur?: number;
9
+ /** Opacity (0..1) applied to non-focused items. @default 0.5 */
10
+ dim?: number;
11
+ /** Applied to the container — use it for layout (e.g. `flex gap-6`). */
12
+ class?: string;
13
+ }
14
+ /**
15
+ * Container that spotlights whichever item is hovered or keyboard-focused:
16
+ * that item stays sharp and fully opaque while its siblings blur and dim.
17
+ * Only `opacity`/`filter` transition — never a layout property — and under
18
+ * `motionReduced()` the blur is skipped (dim-only, no transition) since blur
19
+ * motion reads as motion-sickness-adjacent.
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * <FocusBlurGroup items={navLinks} class="flex gap-6">
24
+ * {(link) => (
25
+ * <a href={link.href} class="text-sm font-medium text-foreground">
26
+ * {link.label}
27
+ * </a>
28
+ * )}
29
+ * </FocusBlurGroup>
30
+ * ```
31
+ */
32
+ export declare function FocusBlurGroup<T>(props: FocusBlurGroupProps<T>): JSX.Element;
@@ -0,0 +1,50 @@
1
+ import { JSX } from 'solid-js';
2
+ /** Visual style of an {@link IconMorphButton}. Defaults to `'ghost'`. */
3
+ export type IconMorphButtonVariant = 'solid' | 'ghost' | 'outline';
4
+ export interface IconMorphButtonProps {
5
+ /** Icon shown while `pressed` is `false`, e.g. `<Copy size={18} />`. */
6
+ inactive: JSX.Element;
7
+ /** Icon shown while `pressed` is `true`, e.g. `<Check size={18} />`. */
8
+ active: JSX.Element;
9
+ /** Controlled pressed state. Omit to let the button manage its own state. */
10
+ pressed?: boolean;
11
+ /** Initial pressed state when uncontrolled. Defaults to `false`. */
12
+ defaultPressed?: boolean;
13
+ /** Fired with the next pressed state, controlled or uncontrolled. */
14
+ onChange?: (pressed: boolean) => void;
15
+ /**
16
+ * When greater than `0`, auto-reverts to `inactive` after this many ms once
17
+ * pressed (e.g. copy → check → copy). The timer is cleared on cleanup and
18
+ * restarted on every re-press. @default 0
19
+ */
20
+ revertAfter?: number;
21
+ /** Optional label shown next to the icon while `pressed` is `false`. */
22
+ label?: JSX.Element;
23
+ /** Optional label shown next to the icon while `pressed` is `true`. */
24
+ activeLabel?: JSX.Element;
25
+ /** Visual style. Defaults to `'ghost'`. */
26
+ variant?: IconMorphButtonVariant;
27
+ class?: string;
28
+ 'aria-label'?: string;
29
+ }
30
+ /**
31
+ * Two-state icon toggle button whose icon morphs (spring scale + rotate +
32
+ * fade) between `inactive` and `active` glyphs, stacked in a fixed-size box.
33
+ * Supports controlled (`pressed`/`onChange`) or uncontrolled
34
+ * (`defaultPressed`) use, an optional auto-revert timer, and an optional
35
+ * crossfading label. Falls back to an instant swap under reduced motion.
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * <IconMorphButton
40
+ * inactive={<Copy size={18} />}
41
+ * active={<Check size={18} />}
42
+ * aria-label="Copy to clipboard"
43
+ * revertAfter={1500}
44
+ * onChange={(pressed) => {
45
+ * if (pressed) void navigator.clipboard?.writeText('npm install @a4ui/core')
46
+ * }}
47
+ * />
48
+ * ```
49
+ */
50
+ export declare function IconMorphButton(props: IconMorphButtonProps): JSX.Element;
@@ -0,0 +1,35 @@
1
+ import { JSX } from 'solid-js';
2
+ /** Which icon a {@link LikeButton} renders — picks the semantic use (like/favorite/save). */
3
+ export type LikeButtonIcon = 'heart' | 'star' | 'bookmark';
4
+ export interface LikeButtonProps {
5
+ /** Controlled pressed state. When set, wins over internal state — pair with `onChange`. */
6
+ pressed?: boolean;
7
+ /** Initial pressed state for uncontrolled use. @default false */
8
+ defaultPressed?: boolean;
9
+ onChange?: (pressed: boolean) => void;
10
+ /** Which icon to render. @default 'heart' */
11
+ icon?: LikeButtonIcon;
12
+ /** Count shown next to the icon (e.g. total likes). */
13
+ count?: number;
14
+ /** Show `count`. @default false */
15
+ showCount?: boolean;
16
+ /** Class applied to the icon/count when pressed. @default 'text-primary' */
17
+ activeClass?: string;
18
+ class?: string;
19
+ 'aria-label'?: string;
20
+ }
21
+ /**
22
+ * Like/favorite/save-later toggle button. One component covers all three via
23
+ * `icon` ('heart' | 'star' | 'bookmark') rather than three near-identical
24
+ * toggles. On becoming pressed the icon fills, morphs to `activeClass`,
25
+ * springs a scale pop, and bursts a handful of tiny icon copies outward —
26
+ * skipped under reduced motion, which just flips the filled/unfilled state
27
+ * instantly. Works controlled (`pressed` + `onChange`) or uncontrolled
28
+ * (`defaultPressed`).
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * <LikeButton defaultPressed count={128} showCount aria-label="Like this post" />
33
+ * ```
34
+ */
35
+ export declare function LikeButton(props: LikeButtonProps): JSX.Element;
@@ -0,0 +1,32 @@
1
+ import { JSX } from 'solid-js';
2
+ /** Feedback micro-animation a {@link MicroButton} plays. Defaults to `'ring'`. */
3
+ export type MicroButtonEffect = 'spin' | 'shake' | 'pulse' | 'ring' | 'sparkle' | 'glare';
4
+ /** Visual style of a {@link MicroButton}. Defaults to `'solid'`. */
5
+ export type MicroButtonVariant = 'solid' | 'ghost' | 'outline';
6
+ export interface MicroButtonProps {
7
+ children?: JSX.Element;
8
+ /** Feedback micro-animation. `'glare'` plays on hover; the rest play on click. @default 'ring' */
9
+ effect?: MicroButtonEffect;
10
+ onClick?: (e: MouseEvent) => void;
11
+ /** Visual style. @default 'solid' */
12
+ variant?: MicroButtonVariant;
13
+ disabled?: boolean;
14
+ class?: string;
15
+ 'aria-label'?: string;
16
+ }
17
+ /**
18
+ * Small icon/label button that plays a short feedback micro-animation on
19
+ * interaction — spin, shake, pulse, ring, or sparkle on click, or a diagonal
20
+ * glare sweep on hover. Every effect animates only `transform`/`opacity`, so
21
+ * it stays cheap even on low-end devices; decorative particles are spawned
22
+ * and removed on completion, never left in the DOM. Renders a plain static
23
+ * `<button>` with no effect under reduced motion.
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * <MicroButton effect="sparkle" aria-label="Like">
28
+ * <Heart size={16} />
29
+ * </MicroButton>
30
+ * ```
31
+ */
32
+ export declare function MicroButton(props: MicroButtonProps): JSX.Element;
@@ -0,0 +1,126 @@
1
+ import { JSX } from 'solid-js';
2
+ import { IconMorphButtonVariant } from './IconMorphButton';
3
+ export interface CopyButtonProps {
4
+ /** Text copied to the clipboard when the button is pressed. */
5
+ value: string;
6
+ /** Optional label shown next to the icon before copying. */
7
+ label?: JSX.Element;
8
+ /** Optional label shown next to the icon once copied. */
9
+ copiedLabel?: JSX.Element;
10
+ /** Visual style. Defaults to `'ghost'`. */
11
+ variant?: IconMorphButtonVariant;
12
+ class?: string;
13
+ 'aria-label'?: string;
14
+ }
15
+ /**
16
+ * Copy-to-clipboard button that morphs Copy → Check and auto-reverts after
17
+ * 1.5s.
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * <CopyButton value="npm install @a4ui/core" aria-label="Copy install command" />
22
+ * ```
23
+ */
24
+ export declare function CopyButton(props: CopyButtonProps): JSX.Element;
25
+ export interface PlayPauseButtonProps {
26
+ /** Controlled playing state. Omit to let the button manage its own state. */
27
+ pressed?: boolean;
28
+ /** Initial playing state when uncontrolled. Defaults to `false`. */
29
+ defaultPressed?: boolean;
30
+ /** Fired with the next playing state, controlled or uncontrolled. */
31
+ onChange?: (pressed: boolean) => void;
32
+ /** Optional label shown next to the icon while paused. */
33
+ label?: JSX.Element;
34
+ /** Optional label shown next to the icon while playing. */
35
+ activeLabel?: JSX.Element;
36
+ /** Visual style. Defaults to `'ghost'`. */
37
+ variant?: IconMorphButtonVariant;
38
+ class?: string;
39
+ 'aria-label'?: string;
40
+ }
41
+ /**
42
+ * Play/pause toggle button whose icon morphs Play → Pause.
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * <PlayPauseButton defaultPressed={false} aria-label="Play" />
47
+ * ```
48
+ */
49
+ export declare function PlayPauseButton(props: PlayPauseButtonProps): JSX.Element;
50
+ export interface MuteButtonProps {
51
+ /** Controlled muted state. Omit to let the button manage its own state. */
52
+ pressed?: boolean;
53
+ /** Initial muted state when uncontrolled. Defaults to `false`. */
54
+ defaultPressed?: boolean;
55
+ /** Fired with the next muted state, controlled or uncontrolled. */
56
+ onChange?: (pressed: boolean) => void;
57
+ /** Optional label shown next to the icon while unmuted. */
58
+ label?: JSX.Element;
59
+ /** Optional label shown next to the icon while muted. */
60
+ activeLabel?: JSX.Element;
61
+ /** Visual style. Defaults to `'ghost'`. */
62
+ variant?: IconMorphButtonVariant;
63
+ class?: string;
64
+ 'aria-label'?: string;
65
+ }
66
+ /**
67
+ * Mute toggle button whose icon morphs Volume2 → VolumeX.
68
+ *
69
+ * @example
70
+ * ```tsx
71
+ * <MuteButton defaultPressed={false} aria-label="Mute" />
72
+ * ```
73
+ */
74
+ export declare function MuteButton(props: MuteButtonProps): JSX.Element;
75
+ export interface LockButtonProps {
76
+ /** Controlled locked state. Omit to let the button manage its own state. */
77
+ pressed?: boolean;
78
+ /** Initial locked state when uncontrolled. Defaults to `false`. */
79
+ defaultPressed?: boolean;
80
+ /** Fired with the next locked state, controlled or uncontrolled. */
81
+ onChange?: (pressed: boolean) => void;
82
+ /** Optional label shown next to the icon while unlocked. */
83
+ label?: JSX.Element;
84
+ /** Optional label shown next to the icon while locked. */
85
+ activeLabel?: JSX.Element;
86
+ /** Visual style. Defaults to `'ghost'`. */
87
+ variant?: IconMorphButtonVariant;
88
+ class?: string;
89
+ 'aria-label'?: string;
90
+ }
91
+ /**
92
+ * Lock toggle button whose icon morphs Unlock → Lock.
93
+ *
94
+ * @example
95
+ * ```tsx
96
+ * <LockButton defaultPressed={false} aria-label="Lock" />
97
+ * ```
98
+ */
99
+ export declare function LockButton(props: LockButtonProps): JSX.Element;
100
+ export interface ThemeMorphButtonProps {
101
+ /** Controlled dark-mode state. Omit to let the button manage its own state. */
102
+ pressed?: boolean;
103
+ /** Initial dark-mode state when uncontrolled. Defaults to `false`. */
104
+ defaultPressed?: boolean;
105
+ /** Fired with the next dark-mode state, controlled or uncontrolled. */
106
+ onChange?: (pressed: boolean) => void;
107
+ /** Optional label shown next to the icon in light mode. */
108
+ label?: JSX.Element;
109
+ /** Optional label shown next to the icon in dark mode. */
110
+ activeLabel?: JSX.Element;
111
+ /** Visual style. Defaults to `'ghost'`. */
112
+ variant?: IconMorphButtonVariant;
113
+ class?: string;
114
+ 'aria-label'?: string;
115
+ }
116
+ /**
117
+ * Purely presentational theme toggle whose icon morphs Sun → Moon. Does not
118
+ * touch the theme system itself — wire `pressed`/`onChange` to your theme
119
+ * state.
120
+ *
121
+ * @example
122
+ * ```tsx
123
+ * <ThemeMorphButton pressed={isDark()} onChange={setIsDark} aria-label="Toggle theme" />
124
+ * ```
125
+ */
126
+ export declare function ThemeMorphButton(props: ThemeMorphButtonProps): JSX.Element;
@@ -0,0 +1,30 @@
1
+ import { JSX, ParentProps } from 'solid-js';
2
+ /** Direction the label/arrow slide toward on hover. Defaults to `'right'`. */
3
+ export type SlideArrowDirection = 'right' | 'left';
4
+ /** Visual style of a {@link SlideArrowButton}. Defaults to `'solid'`. */
5
+ export type SlideArrowButtonVariant = 'solid' | 'outline';
6
+ export interface SlideArrowButtonProps extends ParentProps {
7
+ onClick?: (e: MouseEvent) => void;
8
+ /** Direction the label/arrow slide toward on hover. Defaults to `'right'`. */
9
+ direction?: SlideArrowDirection;
10
+ /** Custom icon; defaults to `ArrowRight`/`ArrowLeft` per `direction`. */
11
+ icon?: JSX.Element;
12
+ /** Visual style. Defaults to `'solid'`. */
13
+ variant?: SlideArrowButtonVariant;
14
+ disabled?: boolean;
15
+ class?: string;
16
+ /** Accessible label, forwarded to the underlying `<button>`. */
17
+ 'aria-label'?: string;
18
+ }
19
+ /**
20
+ * CTA button: at rest the label sits centered; on hover it slides toward
21
+ * `direction` while an arrow slides in from the opposite edge. Pure CSS
22
+ * transform/opacity transitions — no JS animation engine.
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * <SlideArrowButton onClick={() => go()}>Get started</SlideArrowButton>
27
+ * <SlideArrowButton direction="left" variant="outline">Back</SlideArrowButton>
28
+ * ```
29
+ */
30
+ export declare function SlideArrowButton(props: SlideArrowButtonProps): JSX.Element;
@@ -0,0 +1,34 @@
1
+ import { JSX } from 'solid-js';
2
+ export interface TimeMachineStackProps {
3
+ /** Slide contents, one per stack position. */
4
+ slides: JSX.Element[];
5
+ /** Controlled active index. Omit to manage it internally. */
6
+ active?: number;
7
+ /** Initial active index when uncontrolled. @default 0 */
8
+ defaultActive?: number;
9
+ /** Fired whenever the active index changes, via click or keyboard. */
10
+ onChange?: (index: number) => void;
11
+ class?: string;
12
+ }
13
+ /**
14
+ * Apple-style "time machine" depth stack: the active slide is front and
15
+ * full-size, and the rest recede into the screen behind it — each further
16
+ * slide translated up, pushed back on Z, scaled down, and dimmed. A vertical
17
+ * scrubber of real buttons (one per slide), pinned to the right edge, jumps
18
+ * to any index; the stage handles ArrowUp/ArrowDown (and Left/Right) when
19
+ * focused. Animates only `transform`/`opacity` (Motion spring); reduced
20
+ * motion collapses to a flat view of just the active slide.
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * <TimeMachineStack
25
+ * defaultActive={0}
26
+ * slides={[
27
+ * <div class="flex h-full items-center justify-center p-6">Sunset over the bay</div>,
28
+ * <div class="flex h-full items-center justify-center p-6">Empty beach at dawn</div>,
29
+ * <div class="flex h-full items-center justify-center p-6">Fog through the pines</div>,
30
+ * ]}
31
+ * />
32
+ * ```
33
+ */
34
+ export declare function TimeMachineStack(props: TimeMachineStackProps): JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a4ui/core",
3
- "version": "0.29.1",
3
+ "version": "0.31.0",
4
4
  "description": "A4ui — Spatial Glass design system & component library for SolidJS (Kobalte behavior + Tailwind glass tokens + motion).",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -101,6 +101,7 @@
101
101
  "test:visual": "playwright test --config playwright.visual.config.ts",
102
102
  "test:report": "playwright show-report",
103
103
  "test:package": "node scripts/test-package.mjs",
104
+ "test:smoke": "node scripts/smoke-console.mjs",
104
105
  "validate": "npm run typecheck && npm run lint && npm run format:check && npm run test:unit && npm run build && npm run test:package",
105
106
  "prepublishOnly": "npm run validate",
106
107
  "prepare": "husky"
package/src/index.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  // import '@a4ui/core/styles.css'
9
9
  // import { Button, Card, Modal } from '@a4ui/core'
10
10
 
11
- export const A4UI_VERSION = '0.29.1'
11
+ export const A4UI_VERSION = '0.31.0'
12
12
 
13
13
  // Helpers (src/lib) — generic, framework-level utilities.
14
14
  export { cn } from './lib/cn'
@@ -160,6 +160,38 @@ export { FloatingToolbar, type FloatingToolbarProps } from './ui/FloatingToolbar
160
160
  export { PageTransition, type PageTransitionProps } from './ui/PageTransition'
161
161
  export { InlineSelect, type InlineSelectProps, type InlineSelectOption } from './ui/InlineSelect'
162
162
 
163
+ // Micro-interactions — animated buttons + card/3D transitions (Motion-driven).
164
+ export {
165
+ MicroButton,
166
+ type MicroButtonProps,
167
+ type MicroButtonEffect,
168
+ type MicroButtonVariant,
169
+ } from './ui/MicroButton'
170
+ export { IconMorphButton, type IconMorphButtonProps, type IconMorphButtonVariant } from './ui/IconMorphButton'
171
+ export { LikeButton, type LikeButtonProps, type LikeButtonIcon } from './ui/LikeButton'
172
+ export {
173
+ SlideArrowButton,
174
+ type SlideArrowButtonProps,
175
+ type SlideArrowButtonVariant,
176
+ type SlideArrowDirection,
177
+ } from './ui/SlideArrowButton'
178
+ export { FocusBlurGroup, type FocusBlurGroupProps } from './ui/FocusBlurGroup'
179
+ export { CardSpread, type CardSpreadProps, type CardSpreadLayout } from './ui/CardSpread'
180
+ export { Carousel3D, type Carousel3DProps, type Carousel3DVariant } from './ui/Carousel3D'
181
+ export { TimeMachineStack, type TimeMachineStackProps } from './ui/TimeMachineStack'
182
+ export {
183
+ CopyButton,
184
+ type CopyButtonProps,
185
+ PlayPauseButton,
186
+ type PlayPauseButtonProps,
187
+ MuteButton,
188
+ type MuteButtonProps,
189
+ LockButton,
190
+ type LockButtonProps,
191
+ ThemeMorphButton,
192
+ type ThemeMorphButtonProps,
193
+ } from './ui/MorphPresets'
194
+
163
195
  // Phase 2 — code, search, navigation, master-detail.
164
196
  export { CodeTabs, type CodeTabsProps, type CodeTab } from './ui/CodeTabs'
165
197
  export { PillSearch, type PillSearchProps, type PillField } from './ui/PillSearch'