@a4ui/core 0.12.0 → 0.13.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.
- package/README.md +44 -11
- package/dist/elements.css +79 -9
- package/dist/full.css +79 -9
- package/dist/index.d.ts +13 -1
- package/dist/index.js +3392 -2537
- package/dist/lib/flyToCart.d.ts +25 -0
- package/dist/ui/Curtain.d.ts +44 -0
- package/dist/ui/Expandable.d.ts +34 -0
- package/dist/ui/FillText.d.ts +18 -0
- package/dist/ui/HoldToConfirm.d.ts +24 -0
- package/dist/ui/LoadingDots.d.ts +20 -0
- package/dist/ui/MultiStateBadge.d.ts +23 -0
- package/dist/ui/NotificationStack.d.ts +32 -0
- package/dist/ui/NowPlaying.d.ts +21 -0
- package/dist/ui/Parallax.d.ts +21 -0
- package/dist/ui/ScrambleText.d.ts +22 -0
- package/dist/ui/TextReveal.d.ts +23 -0
- package/package.json +1 -1
- package/src/index.ts +21 -1
- package/src/lib/flyToCart.ts +103 -0
- package/src/ui/Curtain.tsx +348 -0
- package/src/ui/Expandable.tsx +229 -0
- package/src/ui/FillText.tsx +63 -0
- package/src/ui/HoldToConfirm.tsx +141 -0
- package/src/ui/LoadingDots.tsx +75 -0
- package/src/ui/MultiStateBadge.tsx +93 -0
- package/src/ui/NotificationStack.tsx +104 -0
- package/src/ui/NowPlaying.tsx +100 -0
- package/src/ui/Parallax.tsx +51 -0
- package/src/ui/ScrambleText.tsx +95 -0
- package/src/ui/SpeedDial.tsx +12 -2
- package/src/ui/TextReveal.tsx +93 -0
package/src/ui/SpeedDial.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import type { JSX } from 'solid-js'
|
|
|
4
4
|
import { createSignal, For, Show } from 'solid-js'
|
|
5
5
|
|
|
6
6
|
import { cn } from '../lib/cn'
|
|
7
|
+
import { animate, motionReduced } from '../lib/motion'
|
|
7
8
|
|
|
8
9
|
/** A single action in a {@link SpeedDial} — an icon button with an accessible label. */
|
|
9
10
|
export interface SpeedDialAction {
|
|
@@ -42,8 +43,17 @@ export function SpeedDial(props: SpeedDialProps): JSX.Element {
|
|
|
42
43
|
<Show when={open()}>
|
|
43
44
|
<div class="flex flex-col items-center gap-3">
|
|
44
45
|
<For each={props.actions}>
|
|
45
|
-
{(action) => (
|
|
46
|
+
{(action, i) => (
|
|
46
47
|
<button
|
|
48
|
+
ref={(el) => {
|
|
49
|
+
// Fan out with a staggered spring as the dial opens.
|
|
50
|
+
if (!motionReduced())
|
|
51
|
+
animate(
|
|
52
|
+
el,
|
|
53
|
+
{ opacity: [0, 1], y: [12, 0], scale: [0.8, 1] },
|
|
54
|
+
{ type: 'spring', stiffness: 500, damping: 24, delay: i() * 0.05 },
|
|
55
|
+
)
|
|
56
|
+
}}
|
|
47
57
|
type="button"
|
|
48
58
|
aria-label={action.label}
|
|
49
59
|
title={action.label}
|
|
@@ -51,7 +61,7 @@ export function SpeedDial(props: SpeedDialProps): JSX.Element {
|
|
|
51
61
|
action.onClick()
|
|
52
62
|
setOpen(false)
|
|
53
63
|
}}
|
|
54
|
-
class="flex h-11 w-11
|
|
64
|
+
class="flex h-11 w-11 items-center justify-center rounded-full border border-border bg-card text-foreground shadow hover:bg-muted focus:outline-none focus:ring-2 focus:ring-ring"
|
|
55
65
|
>
|
|
56
66
|
{action.icon}
|
|
57
67
|
</button>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Reveal text word-by-word or char-by-char with a Motion (motion.dev) stagger:
|
|
2
|
+
// each unit starts translated down and transparent, then fades/slides in with
|
|
3
|
+
// `animate`'s `delay: stagger(...)`, either on mount or the first time the
|
|
4
|
+
// text scrolls into view (`inView`). Whitespace units are rendered as plain
|
|
5
|
+
// text (not wrapped/animated) so word spacing stays exactly what it would be
|
|
6
|
+
// without the effect.
|
|
7
|
+
import { createMemo, onMount, For, type JSX } from 'solid-js'
|
|
8
|
+
|
|
9
|
+
import { cn } from '../lib/cn'
|
|
10
|
+
import { animate, inView, motionReduced, stagger } from '../lib/motion'
|
|
11
|
+
|
|
12
|
+
export interface TextRevealProps {
|
|
13
|
+
text: string
|
|
14
|
+
/** Split unit. @default 'word' */
|
|
15
|
+
by?: 'word' | 'char'
|
|
16
|
+
/** Reveal when scrolled into view instead of on mount. @default false */
|
|
17
|
+
onView?: boolean
|
|
18
|
+
class?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Split `text` into render units: words with their surrounding spaces kept as separate whitespace units, or individual characters. */
|
|
22
|
+
function splitUnits(text: string, by: 'word' | 'char'): string[] {
|
|
23
|
+
if (by === 'char') return text.split('')
|
|
24
|
+
return text.split(/(\s+)/).filter((unit) => unit.length > 0)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const isWhitespace = (unit: string): boolean => /^\s+$/.test(unit)
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Reveals `text`, split into words or characters, with a staggered
|
|
31
|
+
* fade + slide-up (via Motion's `animate` + `stagger`). Runs once on mount by
|
|
32
|
+
* default; pass `onView` to instead reveal the first time the component
|
|
33
|
+
* scrolls into view. Whitespace between words renders as plain text, not an
|
|
34
|
+
* animated unit. Under reduced motion, everything stays visible and no
|
|
35
|
+
* animation runs.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* <TextReveal text="Ship it." by="char" onView />
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function TextReveal(props: TextRevealProps): JSX.Element {
|
|
43
|
+
const units = createMemo(() => splitUnits(props.text, props.by ?? 'word'))
|
|
44
|
+
const spans: (HTMLSpanElement | undefined)[] = []
|
|
45
|
+
let root: HTMLSpanElement | undefined
|
|
46
|
+
|
|
47
|
+
const reveal = (): void => {
|
|
48
|
+
const targets = spans.filter((el): el is HTMLSpanElement => el !== undefined)
|
|
49
|
+
if (targets.length === 0) return
|
|
50
|
+
animate(
|
|
51
|
+
targets,
|
|
52
|
+
{ opacity: [0, 1], y: [12, 0] },
|
|
53
|
+
{ delay: stagger(0.03), duration: 0.4, ease: 'easeOut' },
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
onMount(() => {
|
|
58
|
+
if (motionReduced()) return
|
|
59
|
+
|
|
60
|
+
for (const el of spans) {
|
|
61
|
+
if (el) el.style.opacity = '0'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (props.onView) {
|
|
65
|
+
if (!root) return
|
|
66
|
+
inView(root, () => reveal(), { amount: 0.3 })
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
reveal()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<span class={cn(props.class)} ref={root}>
|
|
75
|
+
<For each={units()}>
|
|
76
|
+
{(unit, index) =>
|
|
77
|
+
isWhitespace(unit) ? (
|
|
78
|
+
unit
|
|
79
|
+
) : (
|
|
80
|
+
<span
|
|
81
|
+
class="inline-block will-change-transform"
|
|
82
|
+
ref={(el) => {
|
|
83
|
+
spans[index()] = el
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
{unit}
|
|
87
|
+
</span>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
</For>
|
|
91
|
+
</span>
|
|
92
|
+
)
|
|
93
|
+
}
|