@waveso/ui 0.7.4 → 0.7.5
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/dist/animate.d.ts.map +1 -1
- package/dist/animate.js +37 -13
- package/dist/animate.js.map +1 -1
- package/dist/badge.d.ts +1 -1
- package/dist/button.d.ts +2 -2
- package/dist/count.d.ts.map +1 -1
- package/dist/count.js +11 -2
- package/dist/count.js.map +1 -1
- package/dist/encrypted-text.d.ts.map +1 -1
- package/dist/encrypted-text.js +7 -2
- package/dist/encrypted-text.js.map +1 -1
- package/dist/input-group.d.ts +1 -1
- package/dist/masonry.d.ts.map +1 -1
- package/dist/masonry.js +9 -4
- package/dist/masonry.js.map +1 -1
- package/dist/styles.css +7 -0
- package/dist/toast.d.ts +1 -1
- package/dist/toast.d.ts.map +1 -1
- package/dist/toast.js +2 -1
- package/dist/toast.js.map +1 -1
- package/dist/typewriter.js +19 -10
- package/dist/typewriter.js.map +1 -1
- package/package.json +1 -1
package/dist/animate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"animate.d.ts","names":[],"sources":["../src/animate.tsx"],"mappings":";;;;;;KAkBK,SAAA;AAAA,UAEK,kBAAA;EACR,QAAA,EAAU,YAAA;;EAEV,KAAA;EALY;EAOZ,IAAA,GAAO,SAAA;EAPK;EASZ,QAAA;EAPQ;EASR,KAAA;;EAEA,IAAA;EANO;EAQP,MAAA;EAQuB;EANvB,IAAA;EAdA;EAgBA,MAAA;EAdA;EAgBA,IAAA;EAdO;EAgBP,UAAA,GAAa,UAAA;AAAA;AAAA,UAGL,cAAA;EACR,QAAA,EAAU,YAAA;EAVV;EAYA,KAAA;EARA;EAUA,IAAA,GAAO,SAAA;EARM;EAUb,QAAA;EAVuB;EAYvB,KAAA;EATsB;EAWtB,IAAA;EAVU;EAYV,MAAA;EAMa;EAJb,IAAA;EAIuB;EAFvB,MAAA;EAhBU;EAkBV,UAAA,GAAa,UAAA;AAAA;AAAA,UAGL,YAAA;EACR,QAAA,EAAU,YAAA;EAdV;EAgBA,QAAA;EAZA;EAcA,SAAA;AAAA;AAAA,UAGQ,UAAA;EACR,QAAA,EAAU,YAAA;EAZa;EAcvB,GAAA;EAXQ;EAaR,GAAA;;EAEA,QAAA;EAdA;EAgBA,OAAA;EAdA;EAgBA,MAAA;AAAA;AAAA,UAGQ,UAAA;EACR,QAAA,EAAU,YAAA;EAfQ;EAiBlB,QAAA;EAhBsB;EAkBtB,QAAA;EAlBU;EAoBV,MAAA;EAhBA;EAkBA,MAAA;AAAA;;;;AAZM;;;;;;;;;;;;AAYA;iBA+HC,aAAA,CAAA;EACP,QAAA;EACA,KAAA;EACA,IAAA;EACA,QAAA;EACA,KAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA;EACA;AAAA,GACC,kBAAA,GAAkB,YAAA,mBAAA,OAAA,CAAA,qBAAA;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"animate.d.ts","names":[],"sources":["../src/animate.tsx"],"mappings":";;;;;;KAkBK,SAAA;AAAA,UAEK,kBAAA;EACR,QAAA,EAAU,YAAA;;EAEV,KAAA;EALY;EAOZ,IAAA,GAAO,SAAA;EAPK;EASZ,QAAA;EAPQ;EASR,KAAA;;EAEA,IAAA;EANO;EAQP,MAAA;EAQuB;EANvB,IAAA;EAdA;EAgBA,MAAA;EAdA;EAgBA,IAAA;EAdO;EAgBP,UAAA,GAAa,UAAA;AAAA;AAAA,UAGL,cAAA;EACR,QAAA,EAAU,YAAA;EAVV;EAYA,KAAA;EARA;EAUA,IAAA,GAAO,SAAA;EARM;EAUb,QAAA;EAVuB;EAYvB,KAAA;EATsB;EAWtB,IAAA;EAVU;EAYV,MAAA;EAMa;EAJb,IAAA;EAIuB;EAFvB,MAAA;EAhBU;EAkBV,UAAA,GAAa,UAAA;AAAA;AAAA,UAGL,YAAA;EACR,QAAA,EAAU,YAAA;EAdV;EAgBA,QAAA;EAZA;EAcA,SAAA;AAAA;AAAA,UAGQ,UAAA;EACR,QAAA,EAAU,YAAA;EAZa;EAcvB,GAAA;EAXQ;EAaR,GAAA;;EAEA,QAAA;EAdA;EAgBA,OAAA;EAdA;EAgBA,MAAA;AAAA;AAAA,UAGQ,UAAA;EACR,QAAA,EAAU,YAAA;EAfQ;EAiBlB,QAAA;EAhBsB;EAkBtB,QAAA;EAlBU;EAoBV,MAAA;EAhBA;EAkBA,MAAA;AAAA;;;;AAZM;;;;;;;;;;;;AAYA;iBA+HC,aAAA,CAAA;EACP,QAAA;EACA,KAAA;EACA,IAAA;EACA,QAAA;EACA,KAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA;EACA;AAAA,GACC,kBAAA,GAAkB,YAAA,mBAAA,OAAA,CAAA,qBAAA;;;;;;;;;;;;;;iBAkEZ,SAAA,CAAA;EACP,QAAA;EACA,KAAA;EACA,IAAA;EACA,QAAA;EACA,KAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA;EACA,MAAA;EACA;AAAA,GACC,cAAA,GAAc,YAAA,mBAAA,OAAA,CAAA,qBAAA;;;;;;;;;;;;;iBA4DR,OAAA,CAAA;EACP,QAAA;EACA,QAAA;EACA;AAAA,GACC,YAAA,GAAY,oBAAA,CAAA,GAAA,CAAA,OAAA;;;;;;;;;;;;iBA0BN,KAAA,CAAA;EACP,QAAA;EACA,GAAA;EACA,GAAA;EACA,QAAA;EACA,OAAA;EACA;AAAA,GACC,UAAA,GAAU,oBAAA,CAAA,GAAA,CAAA,OAAA;;;;;;AA9KQ;;;;;;iBA4OZ,KAAA,CAAA;EACP,QAAA;EACA,QAAA;EACA,QAAA;EACA,MAAA;EACA;AAAA,GACC,UAAA,GAAU,oBAAA,CAAA,GAAA,CAAA,OAAA"}
|
package/dist/animate.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Children, cloneElement, isValidElement, useEffect, useId, useRef, useState } from "react";
|
|
3
3
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
-
import { useInView } from "motion/react";
|
|
4
|
+
import { useInView, useReducedMotion } from "motion/react";
|
|
5
5
|
//#region src/animate.tsx
|
|
6
6
|
/**
|
|
7
7
|
* Direction map: `from` means where the element COMES FROM.
|
|
@@ -112,6 +112,7 @@ function AnimateOnView({ children, delay = 0, from = "down", distance = 16, scal
|
|
|
112
112
|
once,
|
|
113
113
|
margin: "-50px"
|
|
114
114
|
});
|
|
115
|
+
const prefersReducedMotion = useReducedMotion();
|
|
115
116
|
useEffect(() => {
|
|
116
117
|
setHydrated(true);
|
|
117
118
|
}, []);
|
|
@@ -128,6 +129,13 @@ function AnimateOnView({ children, delay = 0, from = "down", distance = 16, scal
|
|
|
128
129
|
rotate,
|
|
129
130
|
flip
|
|
130
131
|
});
|
|
132
|
+
if (prefersReducedMotion) return cloneElement(children, {
|
|
133
|
+
ref: mergeRefs(ref, existingRef),
|
|
134
|
+
style: {
|
|
135
|
+
...existingStyle,
|
|
136
|
+
...styles.visible
|
|
137
|
+
}
|
|
138
|
+
});
|
|
131
139
|
const { duration, ease } = getTransitionParams(transition, spring, .4);
|
|
132
140
|
const currentStyle = isInView ? styles.visible : styles.hidden;
|
|
133
141
|
const transitionStr = buildTransitionStr(duration, ease, delay, styles.hasFilter);
|
|
@@ -157,6 +165,7 @@ function AnimateOnView({ children, delay = 0, from = "down", distance = 16, scal
|
|
|
157
165
|
function AnimateIn({ children, delay = 0, from = "down", distance = 4, scale = false, blur = false, rotate = 0, flip = false, spring = false, transition }) {
|
|
158
166
|
const ref = useRef(null);
|
|
159
167
|
const [visible, setVisible] = useState(false);
|
|
168
|
+
const prefersReducedMotion = useReducedMotion();
|
|
160
169
|
useEffect(() => {
|
|
161
170
|
requestAnimationFrame(() => {
|
|
162
171
|
requestAnimationFrame(() => setVisible(true));
|
|
@@ -174,6 +183,13 @@ function AnimateIn({ children, delay = 0, from = "down", distance = 4, scale = f
|
|
|
174
183
|
rotate,
|
|
175
184
|
flip
|
|
176
185
|
});
|
|
186
|
+
if (prefersReducedMotion) return cloneElement(children, {
|
|
187
|
+
ref: mergeRefs(ref, existingRef),
|
|
188
|
+
style: {
|
|
189
|
+
...existingStyle,
|
|
190
|
+
...styles.visible
|
|
191
|
+
}
|
|
192
|
+
});
|
|
177
193
|
const { duration, ease } = getTransitionParams(transition, spring);
|
|
178
194
|
const currentStyle = visible ? styles.visible : styles.hidden;
|
|
179
195
|
const transitionStr = buildTransitionStr(duration, ease, delay, styles.hasFilter);
|
|
@@ -219,20 +235,24 @@ function Stagger({ children, interval = .08, baseDelay = 0 }) {
|
|
|
219
235
|
function Pulse({ children, min = .97, max = 1.03, duration = 2, opacity, paused = false }) {
|
|
220
236
|
const id = useId().replace(/:/g, "");
|
|
221
237
|
const [hydrated, setHydrated] = useState(false);
|
|
238
|
+
const prefersReducedMotion = useReducedMotion();
|
|
222
239
|
useEffect(() => {
|
|
223
240
|
setHydrated(true);
|
|
224
241
|
}, []);
|
|
225
242
|
if (!isValidElement(children)) return children;
|
|
226
243
|
if (!hydrated) return children;
|
|
227
|
-
const name = `pulse-${id}`;
|
|
228
|
-
const keyframes = `@keyframes ${name} {
|
|
229
|
-
0%, 100% { transform: scale(${min}); opacity: ${opacity?.[0] ?? 1}; }
|
|
230
|
-
50% { transform: scale(${max}); opacity: ${opacity?.[1] ?? 1}; }
|
|
231
|
-
}`;
|
|
232
244
|
const childProps = children.props;
|
|
233
245
|
const existingStyle = childProps.style ?? {};
|
|
234
246
|
const existingRef = childProps.ref;
|
|
235
|
-
|
|
247
|
+
if (prefersReducedMotion) return cloneElement(children, {
|
|
248
|
+
ref: existingRef ? mergeRefs(existingRef) : void 0,
|
|
249
|
+
style: existingStyle
|
|
250
|
+
});
|
|
251
|
+
const name = `pulse-${id}`;
|
|
252
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("style", { children: `@keyframes ${name} {
|
|
253
|
+
0%, 100% { transform: scale(${min}); opacity: ${opacity?.[0] ?? 1}; }
|
|
254
|
+
50% { transform: scale(${max}); opacity: ${opacity?.[1] ?? 1}; }
|
|
255
|
+
}` }), cloneElement(children, {
|
|
236
256
|
ref: existingRef ? mergeRefs(existingRef) : void 0,
|
|
237
257
|
style: {
|
|
238
258
|
...existingStyle,
|
|
@@ -255,22 +275,26 @@ function Pulse({ children, min = .97, max = 1.03, duration = 2, opacity, paused
|
|
|
255
275
|
function Float({ children, distance = 6, duration = 3, rotate = 0, paused = false }) {
|
|
256
276
|
const id = useId().replace(/:/g, "");
|
|
257
277
|
const [hydrated, setHydrated] = useState(false);
|
|
278
|
+
const prefersReducedMotion = useReducedMotion();
|
|
258
279
|
useEffect(() => {
|
|
259
280
|
setHydrated(true);
|
|
260
281
|
}, []);
|
|
261
282
|
if (!isValidElement(children)) return children;
|
|
262
283
|
if (!hydrated) return children;
|
|
284
|
+
const childProps = children.props;
|
|
285
|
+
const existingStyle = childProps.style ?? {};
|
|
286
|
+
const existingRef = childProps.ref;
|
|
287
|
+
if (prefersReducedMotion) return cloneElement(children, {
|
|
288
|
+
...existingRef ? { ref: existingRef } : {},
|
|
289
|
+
style: existingStyle
|
|
290
|
+
});
|
|
263
291
|
const name = `float-${id}`;
|
|
264
292
|
const rotA = rotate ? ` rotate(${-rotate}deg)` : "";
|
|
265
293
|
const rotB = rotate ? ` rotate(${rotate}deg)` : "";
|
|
266
|
-
|
|
294
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("style", { children: `@keyframes ${name} {
|
|
267
295
|
0%, 100% { transform: translateY(0px)${rotA}; }
|
|
268
296
|
50% { transform: translateY(${-distance}px)${rotB}; }
|
|
269
|
-
}
|
|
270
|
-
const childProps = children.props;
|
|
271
|
-
const existingStyle = childProps.style ?? {};
|
|
272
|
-
const existingRef = childProps.ref;
|
|
273
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("style", { children: keyframes }), cloneElement(children, {
|
|
297
|
+
}` }), cloneElement(children, {
|
|
274
298
|
...existingRef ? { ref: existingRef } : {},
|
|
275
299
|
style: {
|
|
276
300
|
...existingStyle,
|
package/dist/animate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"animate.js","names":[],"sources":["../src/animate.tsx"],"sourcesContent":["\"use client\"\n\nimport {\n Children,\n type CSSProperties,\n type ReactElement,\n type Ref,\n cloneElement,\n isValidElement,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\"\nimport { useInView, type Transition } from \"motion/react\"\n\n// ── Types ────────────────────────────────────────────────────────────\n\ntype Direction = \"up\" | \"down\" | \"left\" | \"right\"\n\ninterface AnimateOnViewProps {\n children: ReactElement\n /** Delay in seconds before animation starts. Default: 0 */\n delay?: number\n /** Direction to animate from. Default: 'down' */\n from?: Direction\n /** Distance in px. Default: 16 (scroll reveals need more travel than `AnimateIn`) */\n distance?: number\n /** Also scale in (0.85 → 1). Default: false */\n scale?: boolean\n /** Blur in from a given amount in px. Default: false */\n blur?: boolean | number\n /** Rotate in from a given angle in degrees. Default: 0 */\n rotate?: number\n /** 3D flip entrance along the axis matching `from`. Default: false */\n flip?: boolean\n /** Spring easing with overshoot. Default: false */\n spring?: boolean\n /** Trigger once or every time it enters view. Default: true */\n once?: boolean\n /** Custom transition override */\n transition?: Transition\n}\n\ninterface AnimateInProps {\n children: ReactElement\n /** Delay in seconds before animation starts. Default: 0 */\n delay?: number\n /** Direction to animate from. Default: 'down' */\n from?: Direction\n /** Distance in px. Default: 4 */\n distance?: number\n /** Also scale in (0.85 → 1). Default: false */\n scale?: boolean\n /** Blur in from a given amount in px. Default: false */\n blur?: boolean | number\n /** Rotate in from a given angle in degrees. Default: 0 */\n rotate?: number\n /** 3D flip entrance along the axis matching `from`. Default: false */\n flip?: boolean\n /** Spring easing with overshoot. Default: false */\n spring?: boolean\n /** Custom transition override */\n transition?: Transition\n}\n\ninterface StaggerProps {\n children: ReactElement[]\n /** Delay increment between each child in seconds. Default: 0.08 */\n interval?: number\n /** Base delay before the first child animates in seconds. Default: 0 */\n baseDelay?: number\n}\n\ninterface PulseProps {\n children: ReactElement\n /** Min scale factor. Default: 0.97 */\n min?: number\n /** Max scale factor. Default: 1.03 */\n max?: number\n /** Animation duration in seconds. Default: 2 */\n duration?: number\n /** Also pulse opacity between these bounds [min, max]. Off by default. */\n opacity?: [number, number]\n /** Pause animation. Default: false */\n paused?: boolean\n}\n\ninterface FloatProps {\n children: ReactElement\n /** Vertical float distance in px. Default: 6 */\n distance?: number\n /** Animation duration in seconds. Default: 3 */\n duration?: number\n /** Also rotate slightly while floating (degrees). Default: 0 */\n rotate?: number\n /** Pause animation. Default: false */\n paused?: boolean\n}\n\n// ── Internals ────────────────────────────────────────────────────────\n\n/**\n * Direction map: `from` means where the element COMES FROM.\n * `from=\"left\"` = starts to the left, slides right into place.\n */\nconst DIRECTION_MAP = {\n up: { prop: \"translateY\", value: -1 },\n down: { prop: \"translateY\", value: 1 },\n left: { prop: \"translateX\", value: -1 },\n right: { prop: \"translateX\", value: 1 },\n} as const\n\n/** Flip axis: vertical directions flip around X, horizontal around Y */\nconst FLIP_MAP = {\n up: \"rotateX(90deg)\",\n down: \"rotateX(-90deg)\",\n left: \"rotateY(-90deg)\",\n right: \"rotateY(90deg)\",\n} as const\n\n/** Overshoot (\"spring\") easing for the opt-in `spring` prop on\n * `AnimateIn` / `AnimateOnView`. This is `animate.tsx`'s own page-entrance\n * bounce — the CSS recipe system intentionally has no spring, so this is a standalone constant, not a shared token. */\nconst SPRING_EASE = \"cubic-bezier(0.34, 1.45, 0.64, 1)\"\n\ninterface BuildStylesOptions {\n from: Direction\n distance: number\n doScale: boolean\n blur: boolean | number\n rotate: number\n flip: boolean\n}\n\nfunction buildStyles(opts: BuildStylesOptions) {\n const { from, distance, doScale, blur, rotate, flip } = opts\n const dir = DIRECTION_MAP[from]\n\n const hiddenParts: string[] = []\n if (flip) hiddenParts.push(\"perspective(800px)\")\n hiddenParts.push(`${dir.prop}(${dir.value * distance}px)`)\n if (doScale) hiddenParts.push(\"scale(0.85)\")\n if (rotate) hiddenParts.push(`rotate(${rotate}deg)`)\n if (flip) hiddenParts.push(FLIP_MAP[from])\n\n const visibleParts: string[] = []\n if (flip) visibleParts.push(\"perspective(800px)\")\n visibleParts.push(\"translateX(0) translateY(0)\")\n if (doScale) visibleParts.push(\"scale(1)\")\n if (rotate) visibleParts.push(\"rotate(0deg)\")\n if (flip) visibleParts.push(from === \"up\" || from === \"down\" ? \"rotateX(0deg)\" : \"rotateY(0deg)\")\n\n const blurPx = blur === true ? 8 : typeof blur === \"number\" ? blur : 0\n\n const hidden: CSSProperties = {\n opacity: \"0\",\n transform: hiddenParts.join(\" \"),\n }\n\n const visible: CSSProperties = {\n opacity: \"1\",\n transform: visibleParts.join(\" \"),\n }\n\n if (blurPx > 0) {\n hidden.filter = `blur(${blurPx}px)`\n visible.filter = \"blur(0px)\"\n }\n\n return { hidden, visible, hasFilter: blurPx > 0 }\n}\n\nfunction mergeRefs<T>(...refs: (Ref<T> | undefined)[]) {\n return (el: T | null) => {\n refs.forEach((ref) => {\n if (typeof ref === \"function\") ref(el)\n else if (ref && typeof ref === \"object\") {\n ;(ref as { current: T | null }).current = el\n }\n })\n }\n}\n\nfunction getTransitionParams(transition?: Transition, useSpring?: boolean, fallbackDuration = 0.15) {\n const duration =\n (transition as Record<string, number>)?.duration ?? fallbackDuration\n const ease =\n useSpring ? SPRING_EASE : ((transition as Record<string, string>)?.ease ?? \"ease-out\")\n return { duration, ease }\n}\n\nfunction buildTransitionStr(\n duration: number,\n ease: string,\n delay: number,\n hasFilter: boolean,\n) {\n const parts = [\n `opacity ${duration}s ${ease} ${delay}s`,\n `transform ${duration}s ${ease} ${delay}s`,\n ]\n if (hasFilter) parts.push(`filter ${duration}s ${ease} ${delay}s`)\n return parts.join(\", \")\n}\n\n// ── AnimateOnView ────────────────────────────────────────────────────\n\n/**\n * Animates a child element when it scrolls into view.\n *\n * Defaults are tuned for scroll reveals — larger travel and a slower duration\n * than `AnimateIn` — so the motion reads as the content enters the viewport.\n *\n * Zero DOM overhead — applies styles directly to the child via cloneElement.\n * Renders children unchanged on server to avoid hydration mismatch.\n *\n * @example\n * ```tsx\n * <AnimateOnView from=\"up\" blur scale>\n * <Card>Hello</Card>\n * </AnimateOnView>\n * ```\n */\nfunction AnimateOnView({\n children,\n delay = 0,\n from = \"down\",\n distance = 16,\n scale = false,\n blur = false,\n rotate = 0,\n flip = false,\n spring = false,\n once = true,\n transition,\n}: AnimateOnViewProps) {\n const ref = useRef<HTMLElement>(null)\n const [hydrated, setHydrated] = useState(false)\n const isInView = useInView(ref, { once, margin: \"-50px\" })\n\n useEffect(() => {\n setHydrated(true)\n }, [])\n\n if (!isValidElement(children)) return children\n\n const childProps = children.props as Record<string, unknown>\n const existingStyle = (childProps.style ?? {}) as CSSProperties\n const existingRef = (childProps as { ref?: Ref<HTMLElement> }).ref\n\n if (!hydrated) {\n return cloneElement(children, {\n ref: mergeRefs(ref, existingRef),\n } as Record<string, unknown>)\n }\n\n const styles = buildStyles({ from, distance, doScale: scale, blur, rotate, flip })\n const { duration, ease } = getTransitionParams(transition, spring, 0.4)\n const currentStyle = isInView ? styles.visible : styles.hidden\n const transitionStr = buildTransitionStr(duration, ease, delay, styles.hasFilter)\n\n return cloneElement(children, {\n ref: mergeRefs(ref, existingRef),\n style: {\n ...existingStyle,\n ...currentStyle,\n ...(isInView ? { transition: transitionStr } : {}),\n willChange: \"opacity, transform\",\n },\n } as Record<string, unknown>)\n}\n\n// ── AnimateIn ────────────────────────────────────────────────────────\n\n/**\n * Animates a child element immediately on mount.\n *\n * Zero DOM overhead — applies styles directly to the child via cloneElement.\n * Uses a two-phase approach: hidden → visible with requestAnimationFrame.\n *\n * @example\n * ```tsx\n * <AnimateIn from=\"up\" blur spring>\n * <h1>Welcome</h1>\n * </AnimateIn>\n * ```\n */\nfunction AnimateIn({\n children,\n delay = 0,\n from = \"down\",\n distance = 4,\n scale = false,\n blur = false,\n rotate = 0,\n flip = false,\n spring = false,\n transition,\n}: AnimateInProps) {\n const ref = useRef<HTMLElement>(null)\n const [visible, setVisible] = useState(false)\n\n useEffect(() => {\n requestAnimationFrame(() => {\n requestAnimationFrame(() => setVisible(true))\n })\n }, [])\n\n if (!isValidElement(children)) return children\n\n const childProps = children.props as Record<string, unknown>\n const existingStyle = (childProps.style ?? {}) as CSSProperties\n const existingRef = (childProps as { ref?: Ref<HTMLElement> }).ref\n\n const styles = buildStyles({ from, distance, doScale: scale, blur, rotate, flip })\n const { duration, ease } = getTransitionParams(transition, spring)\n const currentStyle = visible ? styles.visible : styles.hidden\n const transitionStr = buildTransitionStr(duration, ease, delay, styles.hasFilter)\n\n return cloneElement(children, {\n ref: mergeRefs(ref, existingRef),\n style: {\n ...existingStyle,\n ...currentStyle,\n ...(visible ? { transition: transitionStr } : {}),\n willChange: \"opacity, transform\",\n },\n } as Record<string, unknown>)\n}\n\n// ── Stagger ──────────────────────────────────────────────────────────\n\n/**\n * Auto-staggers delay on child AnimateIn/AnimateOnView elements.\n * Eliminates manual `delay={i * 0.08}` math.\n *\n * @example\n * ```tsx\n * <Stagger interval={0.1}>\n * <AnimateIn from=\"up\"><Card>One</Card></AnimateIn>\n * <AnimateIn from=\"up\"><Card>Two</Card></AnimateIn>\n * </Stagger>\n * ```\n */\nfunction Stagger({\n children,\n interval = 0.08,\n baseDelay = 0,\n}: StaggerProps) {\n return (\n <>\n {Children.map(children, (child, index) => {\n if (!isValidElement(child)) return child\n return cloneElement(child as ReactElement<{ delay?: number }>, {\n delay: baseDelay + index * interval,\n })\n })}\n </>\n )\n}\n\n// ── Pulse ────────────────────────────────────────────────────────────\n\n/**\n * Continuous, subtle scale pulse. Great for live indicators and CTAs.\n * Zero DOM overhead — injects a scoped keyframe via `<style>`.\n *\n * @example\n * ```tsx\n * <Pulse>\n * <span className=\"size-3 rounded-full bg-green-500\" />\n * </Pulse>\n * ```\n */\nfunction Pulse({\n children,\n min = 0.97,\n max = 1.03,\n duration = 2,\n opacity,\n paused = false,\n}: PulseProps) {\n const id = useId().replace(/:/g, \"\")\n const [hydrated, setHydrated] = useState(false)\n\n useEffect(() => {\n setHydrated(true)\n }, [])\n\n if (!isValidElement(children)) return children\n if (!hydrated) return children\n\n const name = `pulse-${id}`\n const opacityFrom = opacity?.[0] ?? 1\n const opacityTo = opacity?.[1] ?? 1\n\n const keyframes = `@keyframes ${name} {\n 0%, 100% { transform: scale(${min}); opacity: ${opacityFrom}; }\n 50% { transform: scale(${max}); opacity: ${opacityTo}; }\n}`\n\n const childProps = children.props as Record<string, unknown>\n const existingStyle = (childProps.style ?? {}) as CSSProperties\n const existingRef = (childProps as { ref?: Ref<HTMLElement> }).ref\n\n return (\n <>\n <style>{keyframes}</style>\n {cloneElement(children, {\n ref: existingRef ? mergeRefs(existingRef) : undefined,\n style: {\n ...existingStyle,\n animation: `${name} ${duration}s ease-in-out infinite`,\n animationPlayState: paused ? \"paused\" : \"running\",\n },\n } as Record<string, unknown>)}\n </>\n )\n}\n\n// ── Float ────────────────────────────────────────────────────────────\n\n/**\n * Gentle continuous up/down float. Perfect for decorative elements.\n * Zero DOM overhead — injects a scoped keyframe via `<style>`.\n *\n * @example\n * ```tsx\n * <Float distance={10} duration={4}>\n * <Card>Floating</Card>\n * </Float>\n * ```\n */\nfunction Float({\n children,\n distance = 6,\n duration = 3,\n rotate = 0,\n paused = false,\n}: FloatProps) {\n const id = useId().replace(/:/g, \"\")\n const [hydrated, setHydrated] = useState(false)\n\n useEffect(() => {\n setHydrated(true)\n }, [])\n\n if (!isValidElement(children)) return children\n if (!hydrated) return children\n\n const name = `float-${id}`\n const rotA = rotate ? ` rotate(${-rotate}deg)` : \"\"\n const rotB = rotate ? ` rotate(${rotate}deg)` : \"\"\n\n const keyframes = `@keyframes ${name} {\n 0%, 100% { transform: translateY(0px)${rotA}; }\n 50% { transform: translateY(${-distance}px)${rotB}; }\n}`\n\n const childProps = children.props as Record<string, unknown>\n const existingStyle = (childProps.style ?? {}) as CSSProperties\n const existingRef = (childProps as { ref?: Ref<HTMLElement> }).ref\n\n return (\n <>\n <style>{keyframes}</style>\n {cloneElement(children, {\n ...(existingRef ? { ref: existingRef } : {}),\n style: {\n ...existingStyle,\n animation: `${name} ${duration}s ease-in-out infinite`,\n animationPlayState: paused ? \"paused\" : \"running\",\n },\n } as Record<string, unknown>)}\n </>\n )\n}\n\n// ── Exports ──────────────────────────────────────────────────────────\n\nexport { AnimateOnView, AnimateIn, Stagger, Pulse, Float }\nexport type {\n AnimateOnViewProps,\n AnimateInProps,\n StaggerProps,\n PulseProps,\n FloatProps,\n Direction,\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"animate.js","names":[],"sources":["../src/animate.tsx"],"sourcesContent":["\"use client\"\n\nimport {\n Children,\n type CSSProperties,\n type ReactElement,\n type Ref,\n cloneElement,\n isValidElement,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\"\nimport { useInView, useReducedMotion, type Transition } from \"motion/react\"\n\n// ── Types ────────────────────────────────────────────────────────────\n\ntype Direction = \"up\" | \"down\" | \"left\" | \"right\"\n\ninterface AnimateOnViewProps {\n children: ReactElement\n /** Delay in seconds before animation starts. Default: 0 */\n delay?: number\n /** Direction to animate from. Default: 'down' */\n from?: Direction\n /** Distance in px. Default: 16 (scroll reveals need more travel than `AnimateIn`) */\n distance?: number\n /** Also scale in (0.85 → 1). Default: false */\n scale?: boolean\n /** Blur in from a given amount in px. Default: false */\n blur?: boolean | number\n /** Rotate in from a given angle in degrees. Default: 0 */\n rotate?: number\n /** 3D flip entrance along the axis matching `from`. Default: false */\n flip?: boolean\n /** Spring easing with overshoot. Default: false */\n spring?: boolean\n /** Trigger once or every time it enters view. Default: true */\n once?: boolean\n /** Custom transition override */\n transition?: Transition\n}\n\ninterface AnimateInProps {\n children: ReactElement\n /** Delay in seconds before animation starts. Default: 0 */\n delay?: number\n /** Direction to animate from. Default: 'down' */\n from?: Direction\n /** Distance in px. Default: 4 */\n distance?: number\n /** Also scale in (0.85 → 1). Default: false */\n scale?: boolean\n /** Blur in from a given amount in px. Default: false */\n blur?: boolean | number\n /** Rotate in from a given angle in degrees. Default: 0 */\n rotate?: number\n /** 3D flip entrance along the axis matching `from`. Default: false */\n flip?: boolean\n /** Spring easing with overshoot. Default: false */\n spring?: boolean\n /** Custom transition override */\n transition?: Transition\n}\n\ninterface StaggerProps {\n children: ReactElement[]\n /** Delay increment between each child in seconds. Default: 0.08 */\n interval?: number\n /** Base delay before the first child animates in seconds. Default: 0 */\n baseDelay?: number\n}\n\ninterface PulseProps {\n children: ReactElement\n /** Min scale factor. Default: 0.97 */\n min?: number\n /** Max scale factor. Default: 1.03 */\n max?: number\n /** Animation duration in seconds. Default: 2 */\n duration?: number\n /** Also pulse opacity between these bounds [min, max]. Off by default. */\n opacity?: [number, number]\n /** Pause animation. Default: false */\n paused?: boolean\n}\n\ninterface FloatProps {\n children: ReactElement\n /** Vertical float distance in px. Default: 6 */\n distance?: number\n /** Animation duration in seconds. Default: 3 */\n duration?: number\n /** Also rotate slightly while floating (degrees). Default: 0 */\n rotate?: number\n /** Pause animation. Default: false */\n paused?: boolean\n}\n\n// ── Internals ────────────────────────────────────────────────────────\n\n/**\n * Direction map: `from` means where the element COMES FROM.\n * `from=\"left\"` = starts to the left, slides right into place.\n */\nconst DIRECTION_MAP = {\n up: { prop: \"translateY\", value: -1 },\n down: { prop: \"translateY\", value: 1 },\n left: { prop: \"translateX\", value: -1 },\n right: { prop: \"translateX\", value: 1 },\n} as const\n\n/** Flip axis: vertical directions flip around X, horizontal around Y */\nconst FLIP_MAP = {\n up: \"rotateX(90deg)\",\n down: \"rotateX(-90deg)\",\n left: \"rotateY(-90deg)\",\n right: \"rotateY(90deg)\",\n} as const\n\n/** Overshoot (\"spring\") easing for the opt-in `spring` prop on\n * `AnimateIn` / `AnimateOnView`. This is `animate.tsx`'s own page-entrance\n * bounce — the CSS recipe system intentionally has no spring, so this is a standalone constant, not a shared token. */\nconst SPRING_EASE = \"cubic-bezier(0.34, 1.45, 0.64, 1)\"\n\ninterface BuildStylesOptions {\n from: Direction\n distance: number\n doScale: boolean\n blur: boolean | number\n rotate: number\n flip: boolean\n}\n\nfunction buildStyles(opts: BuildStylesOptions) {\n const { from, distance, doScale, blur, rotate, flip } = opts\n const dir = DIRECTION_MAP[from]\n\n const hiddenParts: string[] = []\n if (flip) hiddenParts.push(\"perspective(800px)\")\n hiddenParts.push(`${dir.prop}(${dir.value * distance}px)`)\n if (doScale) hiddenParts.push(\"scale(0.85)\")\n if (rotate) hiddenParts.push(`rotate(${rotate}deg)`)\n if (flip) hiddenParts.push(FLIP_MAP[from])\n\n const visibleParts: string[] = []\n if (flip) visibleParts.push(\"perspective(800px)\")\n visibleParts.push(\"translateX(0) translateY(0)\")\n if (doScale) visibleParts.push(\"scale(1)\")\n if (rotate) visibleParts.push(\"rotate(0deg)\")\n if (flip) visibleParts.push(from === \"up\" || from === \"down\" ? \"rotateX(0deg)\" : \"rotateY(0deg)\")\n\n const blurPx = blur === true ? 8 : typeof blur === \"number\" ? blur : 0\n\n const hidden: CSSProperties = {\n opacity: \"0\",\n transform: hiddenParts.join(\" \"),\n }\n\n const visible: CSSProperties = {\n opacity: \"1\",\n transform: visibleParts.join(\" \"),\n }\n\n if (blurPx > 0) {\n hidden.filter = `blur(${blurPx}px)`\n visible.filter = \"blur(0px)\"\n }\n\n return { hidden, visible, hasFilter: blurPx > 0 }\n}\n\nfunction mergeRefs<T>(...refs: (Ref<T> | undefined)[]) {\n return (el: T | null) => {\n refs.forEach((ref) => {\n if (typeof ref === \"function\") ref(el)\n else if (ref && typeof ref === \"object\") {\n ;(ref as { current: T | null }).current = el\n }\n })\n }\n}\n\nfunction getTransitionParams(transition?: Transition, useSpring?: boolean, fallbackDuration = 0.15) {\n const duration =\n (transition as Record<string, number>)?.duration ?? fallbackDuration\n const ease =\n useSpring ? SPRING_EASE : ((transition as Record<string, string>)?.ease ?? \"ease-out\")\n return { duration, ease }\n}\n\nfunction buildTransitionStr(\n duration: number,\n ease: string,\n delay: number,\n hasFilter: boolean,\n) {\n const parts = [\n `opacity ${duration}s ${ease} ${delay}s`,\n `transform ${duration}s ${ease} ${delay}s`,\n ]\n if (hasFilter) parts.push(`filter ${duration}s ${ease} ${delay}s`)\n return parts.join(\", \")\n}\n\n// ── AnimateOnView ────────────────────────────────────────────────────\n\n/**\n * Animates a child element when it scrolls into view.\n *\n * Defaults are tuned for scroll reveals — larger travel and a slower duration\n * than `AnimateIn` — so the motion reads as the content enters the viewport.\n *\n * Zero DOM overhead — applies styles directly to the child via cloneElement.\n * Renders children unchanged on server to avoid hydration mismatch.\n *\n * @example\n * ```tsx\n * <AnimateOnView from=\"up\" blur scale>\n * <Card>Hello</Card>\n * </AnimateOnView>\n * ```\n */\nfunction AnimateOnView({\n children,\n delay = 0,\n from = \"down\",\n distance = 16,\n scale = false,\n blur = false,\n rotate = 0,\n flip = false,\n spring = false,\n once = true,\n transition,\n}: AnimateOnViewProps) {\n const ref = useRef<HTMLElement>(null)\n const [hydrated, setHydrated] = useState(false)\n const isInView = useInView(ref, { once, margin: \"-50px\" })\n const prefersReducedMotion = useReducedMotion()\n\n useEffect(() => {\n setHydrated(true)\n }, [])\n\n if (!isValidElement(children)) return children\n\n const childProps = children.props as Record<string, unknown>\n const existingStyle = (childProps.style ?? {}) as CSSProperties\n const existingRef = (childProps as { ref?: Ref<HTMLElement> }).ref\n\n if (!hydrated) {\n return cloneElement(children, {\n ref: mergeRefs(ref, existingRef),\n } as Record<string, unknown>)\n }\n\n const styles = buildStyles({ from, distance, doScale: scale, blur, rotate, flip })\n\n // Reduced motion: render the final, resting state immediately — no\n // travel, blur, scale, or transition. Keeps the same DOM/output shape.\n if (prefersReducedMotion) {\n return cloneElement(children, {\n ref: mergeRefs(ref, existingRef),\n style: {\n ...existingStyle,\n ...styles.visible,\n },\n } as Record<string, unknown>)\n }\n\n const { duration, ease } = getTransitionParams(transition, spring, 0.4)\n const currentStyle = isInView ? styles.visible : styles.hidden\n const transitionStr = buildTransitionStr(duration, ease, delay, styles.hasFilter)\n\n return cloneElement(children, {\n ref: mergeRefs(ref, existingRef),\n style: {\n ...existingStyle,\n ...currentStyle,\n ...(isInView ? { transition: transitionStr } : {}),\n willChange: \"opacity, transform\",\n },\n } as Record<string, unknown>)\n}\n\n// ── AnimateIn ────────────────────────────────────────────────────────\n\n/**\n * Animates a child element immediately on mount.\n *\n * Zero DOM overhead — applies styles directly to the child via cloneElement.\n * Uses a two-phase approach: hidden → visible with requestAnimationFrame.\n *\n * @example\n * ```tsx\n * <AnimateIn from=\"up\" blur spring>\n * <h1>Welcome</h1>\n * </AnimateIn>\n * ```\n */\nfunction AnimateIn({\n children,\n delay = 0,\n from = \"down\",\n distance = 4,\n scale = false,\n blur = false,\n rotate = 0,\n flip = false,\n spring = false,\n transition,\n}: AnimateInProps) {\n const ref = useRef<HTMLElement>(null)\n const [visible, setVisible] = useState(false)\n const prefersReducedMotion = useReducedMotion()\n\n useEffect(() => {\n requestAnimationFrame(() => {\n requestAnimationFrame(() => setVisible(true))\n })\n }, [])\n\n if (!isValidElement(children)) return children\n\n const childProps = children.props as Record<string, unknown>\n const existingStyle = (childProps.style ?? {}) as CSSProperties\n const existingRef = (childProps as { ref?: Ref<HTMLElement> }).ref\n\n const styles = buildStyles({ from, distance, doScale: scale, blur, rotate, flip })\n\n // Reduced motion: render the final, resting state immediately — no\n // travel, blur, scale, or transition. Keeps the same DOM/output shape.\n if (prefersReducedMotion) {\n return cloneElement(children, {\n ref: mergeRefs(ref, existingRef),\n style: {\n ...existingStyle,\n ...styles.visible,\n },\n } as Record<string, unknown>)\n }\n\n const { duration, ease } = getTransitionParams(transition, spring)\n const currentStyle = visible ? styles.visible : styles.hidden\n const transitionStr = buildTransitionStr(duration, ease, delay, styles.hasFilter)\n\n return cloneElement(children, {\n ref: mergeRefs(ref, existingRef),\n style: {\n ...existingStyle,\n ...currentStyle,\n ...(visible ? { transition: transitionStr } : {}),\n willChange: \"opacity, transform\",\n },\n } as Record<string, unknown>)\n}\n\n// ── Stagger ──────────────────────────────────────────────────────────\n\n/**\n * Auto-staggers delay on child AnimateIn/AnimateOnView elements.\n * Eliminates manual `delay={i * 0.08}` math.\n *\n * @example\n * ```tsx\n * <Stagger interval={0.1}>\n * <AnimateIn from=\"up\"><Card>One</Card></AnimateIn>\n * <AnimateIn from=\"up\"><Card>Two</Card></AnimateIn>\n * </Stagger>\n * ```\n */\nfunction Stagger({\n children,\n interval = 0.08,\n baseDelay = 0,\n}: StaggerProps) {\n return (\n <>\n {Children.map(children, (child, index) => {\n if (!isValidElement(child)) return child\n return cloneElement(child as ReactElement<{ delay?: number }>, {\n delay: baseDelay + index * interval,\n })\n })}\n </>\n )\n}\n\n// ── Pulse ────────────────────────────────────────────────────────────\n\n/**\n * Continuous, subtle scale pulse. Great for live indicators and CTAs.\n * Zero DOM overhead — injects a scoped keyframe via `<style>`.\n *\n * @example\n * ```tsx\n * <Pulse>\n * <span className=\"size-3 rounded-full bg-green-500\" />\n * </Pulse>\n * ```\n */\nfunction Pulse({\n children,\n min = 0.97,\n max = 1.03,\n duration = 2,\n opacity,\n paused = false,\n}: PulseProps) {\n const id = useId().replace(/:/g, \"\")\n const [hydrated, setHydrated] = useState(false)\n const prefersReducedMotion = useReducedMotion()\n\n useEffect(() => {\n setHydrated(true)\n }, [])\n\n if (!isValidElement(children)) return children\n if (!hydrated) return children\n\n const childProps = children.props as Record<string, unknown>\n const existingStyle = (childProps.style ?? {}) as CSSProperties\n const existingRef = (childProps as { ref?: Ref<HTMLElement> }).ref\n\n // Reduced motion: no continuous pulse — render the child at rest, no\n // keyframe injected. Same ref/style handling as the animated path.\n if (prefersReducedMotion) {\n return cloneElement(children, {\n ref: existingRef ? mergeRefs(existingRef) : undefined,\n style: existingStyle,\n } as Record<string, unknown>)\n }\n\n const name = `pulse-${id}`\n const opacityFrom = opacity?.[0] ?? 1\n const opacityTo = opacity?.[1] ?? 1\n\n const keyframes = `@keyframes ${name} {\n 0%, 100% { transform: scale(${min}); opacity: ${opacityFrom}; }\n 50% { transform: scale(${max}); opacity: ${opacityTo}; }\n}`\n\n return (\n <>\n <style>{keyframes}</style>\n {cloneElement(children, {\n ref: existingRef ? mergeRefs(existingRef) : undefined,\n style: {\n ...existingStyle,\n animation: `${name} ${duration}s ease-in-out infinite`,\n animationPlayState: paused ? \"paused\" : \"running\",\n },\n } as Record<string, unknown>)}\n </>\n )\n}\n\n// ── Float ────────────────────────────────────────────────────────────\n\n/**\n * Gentle continuous up/down float. Perfect for decorative elements.\n * Zero DOM overhead — injects a scoped keyframe via `<style>`.\n *\n * @example\n * ```tsx\n * <Float distance={10} duration={4}>\n * <Card>Floating</Card>\n * </Float>\n * ```\n */\nfunction Float({\n children,\n distance = 6,\n duration = 3,\n rotate = 0,\n paused = false,\n}: FloatProps) {\n const id = useId().replace(/:/g, \"\")\n const [hydrated, setHydrated] = useState(false)\n const prefersReducedMotion = useReducedMotion()\n\n useEffect(() => {\n setHydrated(true)\n }, [])\n\n if (!isValidElement(children)) return children\n if (!hydrated) return children\n\n const childProps = children.props as Record<string, unknown>\n const existingStyle = (childProps.style ?? {}) as CSSProperties\n const existingRef = (childProps as { ref?: Ref<HTMLElement> }).ref\n\n // Reduced motion: no continuous float — render the child at rest, no\n // keyframe injected. Same ref/style handling as the animated path.\n if (prefersReducedMotion) {\n return cloneElement(children, {\n ...(existingRef ? { ref: existingRef } : {}),\n style: existingStyle,\n } as Record<string, unknown>)\n }\n\n const name = `float-${id}`\n const rotA = rotate ? ` rotate(${-rotate}deg)` : \"\"\n const rotB = rotate ? ` rotate(${rotate}deg)` : \"\"\n\n const keyframes = `@keyframes ${name} {\n 0%, 100% { transform: translateY(0px)${rotA}; }\n 50% { transform: translateY(${-distance}px)${rotB}; }\n}`\n\n return (\n <>\n <style>{keyframes}</style>\n {cloneElement(children, {\n ...(existingRef ? { ref: existingRef } : {}),\n style: {\n ...existingStyle,\n animation: `${name} ${duration}s ease-in-out infinite`,\n animationPlayState: paused ? \"paused\" : \"running\",\n },\n } as Record<string, unknown>)}\n </>\n )\n}\n\n// ── Exports ──────────────────────────────────────────────────────────\n\nexport { AnimateOnView, AnimateIn, Stagger, Pulse, Float }\nexport type {\n AnimateOnViewProps,\n AnimateInProps,\n StaggerProps,\n PulseProps,\n FloatProps,\n Direction,\n}\n"],"mappings":"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"}
|
package/dist/badge.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import * as _$class_variance_authority_types0 from "class-variance-authority/typ
|
|
|
5
5
|
|
|
6
6
|
//#region src/badge.d.ts
|
|
7
7
|
declare const badgeVariants: (props?: ({
|
|
8
|
-
variant?: "default" | "
|
|
8
|
+
variant?: "default" | "outline" | "secondary" | "ghost" | "success" | "destructive" | "link" | "warning" | null | undefined;
|
|
9
9
|
} & _$class_variance_authority_types0.ClassProp) | undefined) => string;
|
|
10
10
|
type BadgeProps = useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>;
|
|
11
11
|
declare function Badge({
|
package/dist/button.d.ts
CHANGED
|
@@ -6,8 +6,8 @@ import * as _$class_variance_authority_types0 from "class-variance-authority/typ
|
|
|
6
6
|
|
|
7
7
|
//#region src/button.d.ts
|
|
8
8
|
declare const buttonVariants: (props?: ({
|
|
9
|
-
variant?: "default" | "
|
|
10
|
-
size?: "default" | "
|
|
9
|
+
variant?: "default" | "outline" | "secondary" | "ghost" | "success" | "destructive" | "link" | null | undefined;
|
|
10
|
+
size?: "default" | "xs" | "sm" | "icon-xs" | "icon-sm" | "lg" | "xl" | "icon" | "icon-lg" | null | undefined;
|
|
11
11
|
} & _$class_variance_authority_types0.ClassProp) | undefined) => string;
|
|
12
12
|
type ButtonProps = React.ComponentProps<typeof Button$1> & VariantProps<typeof buttonVariants>;
|
|
13
13
|
declare function Button({
|
package/dist/count.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"count.d.ts","names":[],"sources":["../src/count.tsx"],"mappings":";;;;UAeU,UAAA;;EAER,EAAA,WAAa,IAAA;EAFL;EAIR,IAAA;;EAEA,QAAA;EAJA;EAMA,KAAA;EAJA;;;;;;EAWA,MAAA,IAAU,KAAA;EAMV;EAJA,MAAA;EAMA;EAJA,MAAA;EAMU;EAJV,QAAA,EAAU,YAAA;EAMA;EAJV,IAAA;EAQG;EANH,MAAA,IAAU,CAAA;;EAEV,UAAA;AAAA;AAI4B;AAAA,KAAzB,YAAA,GAAe,UAAA;;iBAKX,OAAA,CAAQ,CAAA;;AAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwEjB,KAAA,CAAA;EACP,EAAA;EACA,IAAA,EAAM,KAAA;EACN,QAAA;EACA,KAAA;EACA,MAAA;EACA,MAAA;EACA,MAAA;EACA,QAAA;EACA,IAAA;EACA,MAAA;EACA;AAAA,GACC,UAAA,GAAU,oBAAA,CAAA,GAAA,CAAA,OAAA;;
|
|
1
|
+
{"version":3,"file":"count.d.ts","names":[],"sources":["../src/count.tsx"],"mappings":";;;;UAeU,UAAA;;EAER,EAAA,WAAa,IAAA;EAFL;EAIR,IAAA;;EAEA,QAAA;EAJA;EAMA,KAAA;EAJA;;;;;;EAWA,MAAA,IAAU,KAAA;EAMV;EAJA,MAAA;EAMA;EAJA,MAAA;EAMU;EAJV,QAAA,EAAU,YAAA;EAMA;EAJV,IAAA;EAQG;EANH,MAAA,IAAU,CAAA;;EAEV,UAAA;AAAA;AAI4B;AAAA,KAAzB,YAAA,GAAe,UAAA;;iBAKX,OAAA,CAAQ,CAAA;;AAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwEjB,KAAA,CAAA;EACP,EAAA;EACA,IAAA,EAAM,KAAA;EACN,QAAA;EACA,KAAA;EACA,MAAA;EACA,MAAA;EACA,MAAA;EACA,QAAA;EACA,IAAA;EACA,MAAA;EACA;AAAA,GACC,UAAA,GAAU,oBAAA,CAAA,GAAA,CAAA,OAAA;;cAiNP,OAAA,SAAO,KAAA"}
|
package/dist/count.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cloneElement, isValidElement, useEffect, useRef, useState } from "react";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
|
-
import { useInView } from "motion/react";
|
|
4
|
+
import { useInView, useReducedMotion } from "motion/react";
|
|
5
5
|
//#region src/count.tsx
|
|
6
6
|
/** Cubic ease-out: fast start, smooth deceleration */
|
|
7
7
|
function easeOut(t) {
|
|
@@ -88,6 +88,7 @@ function NumberCount({ to, from: start, duration, delay, format, prefix, suffix,
|
|
|
88
88
|
once,
|
|
89
89
|
margin: "-50px"
|
|
90
90
|
});
|
|
91
|
+
const prefersReducedMotion = useReducedMotion();
|
|
91
92
|
const [display, setDisplay] = useState(start);
|
|
92
93
|
const easingRef = useRef(easing);
|
|
93
94
|
const onCompleteRef = useRef(onComplete);
|
|
@@ -99,6 +100,13 @@ function NumberCount({ to, from: start, duration, delay, format, prefix, suffix,
|
|
|
99
100
|
useEffect(() => {
|
|
100
101
|
if (!isInView) return;
|
|
101
102
|
const delayMs = delay * 1e3;
|
|
103
|
+
if (prefersReducedMotion) {
|
|
104
|
+
const timer = setTimeout(() => {
|
|
105
|
+
setDisplay(to);
|
|
106
|
+
onCompleteRef.current?.();
|
|
107
|
+
}, delayMs);
|
|
108
|
+
return () => clearTimeout(timer);
|
|
109
|
+
}
|
|
102
110
|
let raf;
|
|
103
111
|
let startTime;
|
|
104
112
|
const timer = setTimeout(() => {
|
|
@@ -121,7 +129,8 @@ function NumberCount({ to, from: start, duration, delay, format, prefix, suffix,
|
|
|
121
129
|
to,
|
|
122
130
|
start,
|
|
123
131
|
duration,
|
|
124
|
-
delay
|
|
132
|
+
delay,
|
|
133
|
+
prefersReducedMotion
|
|
125
134
|
]);
|
|
126
135
|
if (!isValidElement(children)) return children;
|
|
127
136
|
const existingRef = children.props.ref;
|
package/dist/count.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"count.js","names":[],"sources":["../src/count.tsx"],"sourcesContent":["\"use client\"\n\nimport {\n type ReactElement,\n type Ref,\n cloneElement,\n isValidElement,\n useEffect,\n useRef,\n useState,\n} from \"react\"\nimport { useInView } from \"motion/react\"\n\n// ── Types ────────────────────────────────────────────────────────────\n\ninterface CountProps {\n /** Target number or Date to count to */\n to: number | Date\n /** Starting number. Default: 0. Ignored when `to` is a Date. */\n from?: number\n /** Animation duration in milliseconds. Default: 900. Ignored when `to` is a Date. */\n duration?: number\n /** Delay before starting in seconds. Default: 0 */\n delay?: number\n /**\n * Format the value for display.\n * - For numbers: receives the current interpolated number.\n * - For dates: receives remaining milliseconds.\n * Default: toLocaleString() for numbers, dd:hh:mm:ss for dates.\n */\n format?: (value: number) => string\n /** Prefix string (e.g., \"$\"). Default: '' */\n prefix?: string\n /** Suffix string (e.g., \"%\", \"+\"). Default: '' */\n suffix?: string\n /** Element to render into. Receives the formatted value as children. */\n children: ReactElement\n /** Trigger once. Default: true */\n once?: boolean\n /** Easing function. Default: easeOut. Ignored when `to` is a Date. */\n easing?: (t: number) => number\n /** Called when the count finishes (reaches target or date passes). */\n onComplete?: () => void\n}\n\n/** @deprecated Use `Count` instead. `CountUp` is an alias kept for backwards compatibility. */\ntype CountUpProps = CountProps\n\n// ── Easing ───────────────────────────────────────────────────────────\n\n/** Cubic ease-out: fast start, smooth deceleration */\nfunction easeOut(t: number): number {\n return 1 - Math.pow(1 - t, 3)\n}\n\n// ── Date Formatting ──────────────────────────────────────────────────\n\nfunction formatCountdown(ms: number): string {\n if (ms <= 0) return \"00:00:00\"\n\n const totalSeconds = Math.floor(ms / 1000)\n const days = Math.floor(totalSeconds / 86400)\n const hours = Math.floor((totalSeconds % 86400) / 3600)\n const minutes = Math.floor((totalSeconds % 3600) / 60)\n const seconds = totalSeconds % 60\n\n const pad = (n: number) => String(n).padStart(2, \"0\")\n\n if (days > 0) {\n return `${days}d ${pad(hours)}:${pad(minutes)}:${pad(seconds)}`\n }\n return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`\n}\n\n// ── Ref Merge ────────────────────────────────────────────────────────\n\nfunction mergeRef(\n internalRef: React.RefObject<HTMLElement | null>,\n externalRef?: Ref<HTMLElement>,\n) {\n return (el: HTMLElement | null) => {\n ;(internalRef as { current: HTMLElement | null }).current = el\n if (typeof externalRef === \"function\") externalRef(el)\n else if (externalRef && typeof externalRef === \"object\") {\n ;(externalRef as { current: HTMLElement | null }).current = el\n }\n }\n}\n\n// ── Count ────────────────────────────────────────────────────────────\n\n/**\n * Animated number counter. Counts up, counts down, or live-counts to a date.\n *\n * Direction is automatic — if `from < to` it counts up, if `from > to` it\n * counts down. When `to` is a Date, it becomes a live countdown that ticks\n * every second.\n *\n * Zero wrapper — injects the formatted value as children via cloneElement.\n *\n * @example\n * ```tsx\n * // Count up\n * <Count to={1234}>\n * <span className=\"text-4xl font-bold tabular-nums\" />\n * </Count>\n *\n * // Count down\n * <Count from={100} to={0} onComplete={() => alert(\"Done!\")}>\n * <span className=\"text-4xl font-bold tabular-nums\" />\n * </Count>\n *\n * // Live countdown to a date\n * <Count to={new Date(\"2026-04-01T00:00:00\")}>\n * <span className=\"text-2xl font-mono tabular-nums\" />\n * </Count>\n *\n * // Custom date format\n * <Count to={launchDate} format={(ms) => `${Math.ceil(ms / 86400000)} days left`}>\n * <span className=\"text-xl\" />\n * </Count>\n * ```\n */\nfunction Count({\n to,\n from: start = 0,\n duration = 900,\n delay = 0,\n format,\n prefix = \"\",\n suffix = \"\",\n children,\n once = true,\n easing = easeOut,\n onComplete,\n}: CountProps) {\n const isDate = to instanceof Date\n\n if (isDate) {\n return (\n <DateCount\n to={to}\n delay={delay}\n format={format}\n prefix={prefix}\n suffix={suffix}\n once={once}\n onComplete={onComplete}\n >\n {children}\n </DateCount>\n )\n }\n\n return (\n <NumberCount\n to={to}\n from={start}\n duration={duration}\n delay={delay}\n format={format}\n prefix={prefix}\n suffix={suffix}\n once={once}\n easing={easing}\n onComplete={onComplete}\n >\n {children}\n </NumberCount>\n )\n}\n\n// ── Number Count (up or down) ────────────────────────────────────────\n\nfunction NumberCount({\n to,\n from: start,\n duration,\n delay,\n format,\n prefix,\n suffix,\n children,\n once,\n easing,\n onComplete,\n}: {\n to: number\n from: number\n duration: number\n delay: number\n format?: (value: number) => string\n prefix: string\n suffix: string\n children: ReactElement\n once: boolean\n easing: (t: number) => number\n onComplete?: () => void\n}) {\n const ref = useRef<HTMLElement>(null)\n const isInView = useInView(ref, { once, margin: \"-50px\" })\n const [display, setDisplay] = useState(start)\n\n // Keep the callbacks in refs so a parent re-render with inline `easing` /\n // `onComplete` props doesn't re-run the animation effect — which would cancel\n // the in-flight rAF and freeze the counter mid-count. The effect below keys\n // only on the values that should actually (re)start the animation, so a live\n // `to` change or a re-entry (with `once={false}`) restarts cleanly.\n const easingRef = useRef(easing)\n const onCompleteRef = useRef(onComplete)\n useEffect(() => {\n easingRef.current = easing\n onCompleteRef.current = onComplete\n })\n\n const formatFn = format ?? ((n: number) =>\n Number.isInteger(to) ? Math.round(n).toLocaleString() : n.toLocaleString()\n )\n\n useEffect(() => {\n if (!isInView) return\n\n const delayMs = delay * 1000\n let raf: number\n let startTime: number\n\n const timer = setTimeout(() => {\n const animate = (timestamp: number) => {\n if (!startTime) startTime = timestamp\n const elapsed = timestamp - startTime\n const progress = Math.min(elapsed / duration, 1)\n const current = start + (to - start) * easingRef.current(progress)\n\n setDisplay(current)\n\n if (progress < 1) {\n raf = requestAnimationFrame(animate)\n } else {\n onCompleteRef.current?.()\n }\n }\n\n raf = requestAnimationFrame(animate)\n }, delayMs)\n\n return () => {\n clearTimeout(timer)\n cancelAnimationFrame(raf)\n }\n }, [isInView, to, start, duration, delay])\n\n if (!isValidElement(children)) return children\n\n const childProps = children.props as Record<string, unknown>\n const existingRef = (childProps as { ref?: Ref<HTMLElement> }).ref\n\n return cloneElement(children, {\n ref: mergeRef(ref, existingRef),\n children: `${prefix}${formatFn(display)}${suffix}`,\n } as Record<string, unknown>)\n}\n\n// ── Date Count (live countdown) ──────────────────────────────────────\n\nfunction DateCount({\n to,\n delay,\n format,\n prefix,\n suffix,\n children,\n once,\n onComplete,\n}: {\n to: Date\n delay: number\n format?: (value: number) => string\n prefix: string\n suffix: string\n children: ReactElement\n once: boolean\n onComplete?: () => void\n}) {\n const ref = useRef<HTMLElement>(null)\n const isInView = useInView(ref, { once, margin: \"-50px\" })\n const [remaining, setRemaining] = useState(() => Math.max(0, to.getTime() - Date.now()))\n const [started, setStarted] = useState(false)\n const completedRef = useRef(false)\n\n const formatFn = format ?? formatCountdown\n\n useEffect(() => {\n if (!isInView || started) return\n const timer = setTimeout(() => setStarted(true), delay * 1000)\n return () => clearTimeout(timer)\n }, [isInView, delay, started])\n\n useEffect(() => {\n if (!started) return\n\n const tick = () => {\n const ms = Math.max(0, to.getTime() - Date.now())\n setRemaining(ms)\n\n if (ms <= 0 && !completedRef.current) {\n completedRef.current = true\n onComplete?.()\n }\n }\n\n tick()\n const interval = setInterval(tick, 1000)\n return () => clearInterval(interval)\n }, [started, to, onComplete])\n\n if (!isValidElement(children)) return children\n\n const childProps = children.props as Record<string, unknown>\n const existingRef = (childProps as { ref?: Ref<HTMLElement> }).ref\n\n return cloneElement(children, {\n ref: mergeRef(ref, existingRef),\n children: `${prefix}${formatFn(remaining)}${suffix}`,\n } as Record<string, unknown>)\n}\n\n// ── Exports ──────────────────────────────────────────────────────────\n\n/** @deprecated Use `Count` instead */\nconst CountUp = Count\n\nexport { Count, CountUp, easeOut }\nexport type { CountProps, CountUpProps }\n"],"mappings":";;;;;;AAmDA,SAAS,QAAQ,GAAmB;AAClC,QAAO,IAAI,KAAK,IAAI,IAAI,GAAG,EAAE;;AAK/B,SAAS,gBAAgB,IAAoB;AAC3C,KAAI,MAAM,EAAG,QAAO;CAEpB,MAAM,eAAe,KAAK,MAAM,KAAK,IAAK;CAC1C,MAAM,OAAO,KAAK,MAAM,eAAe,MAAM;CAC7C,MAAM,QAAQ,KAAK,MAAO,eAAe,QAAS,KAAK;CACvD,MAAM,UAAU,KAAK,MAAO,eAAe,OAAQ,GAAG;CACtD,MAAM,UAAU,eAAe;CAE/B,MAAM,OAAO,MAAc,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI;AAErD,KAAI,OAAO,EACT,QAAO,GAAG,KAAK,IAAI,IAAI,MAAM,CAAC,GAAG,IAAI,QAAQ,CAAC,GAAG,IAAI,QAAQ;AAE/D,QAAO,GAAG,IAAI,MAAM,CAAC,GAAG,IAAI,QAAQ,CAAC,GAAG,IAAI,QAAQ;;AAKtD,SAAS,SACP,aACA,aACA;AACA,SAAQ,OAA2B;AAC/B,cAAgD,UAAU;AAC5D,MAAI,OAAO,gBAAgB,WAAY,aAAY,GAAG;WAC7C,eAAe,OAAO,gBAAgB,SAC3C,aAAgD,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuClE,SAAS,MAAM,EACb,IACA,MAAM,QAAQ,GACd,WAAW,KACX,QAAQ,GACR,QACA,SAAS,IACT,SAAS,IACT,UACA,OAAO,MACP,SAAS,SACT,cACa;AAGb,KAFe,cAAc,KAG3B,QACE,oBAAC,WAAD;EACM;EACG;EACC;EACA;EACA;EACF;EACM;EAEX;EACS,CAAA;AAIhB,QACE,oBAAC,aAAD;EACM;EACJ,MAAM;EACI;EACH;EACC;EACA;EACA;EACF;EACE;EACI;EAEX;EACW,CAAA;;AAMlB,SAAS,YAAY,EACnB,IACA,MAAM,OACN,UACA,OACA,QACA,QACA,QACA,UACA,MACA,QACA,cAaC;CACD,MAAM,MAAM,OAAoB,KAAK;CACrC,MAAM,WAAW,UAAU,KAAK;EAAE;EAAM,QAAQ;EAAS,CAAC;CAC1D,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAO7C,MAAM,YAAY,OAAO,OAAO;CAChC,MAAM,gBAAgB,OAAO,WAAW;AACxC,iBAAgB;AACd,YAAU,UAAU;AACpB,gBAAc,UAAU;GACxB;CAEF,MAAM,WAAW,YAAY,MAC3B,OAAO,UAAU,GAAG,GAAG,KAAK,MAAM,EAAE,CAAC,gBAAgB,GAAG,EAAE,gBAAgB;AAG5E,iBAAgB;AACd,MAAI,CAAC,SAAU;EAEf,MAAM,UAAU,QAAQ;EACxB,IAAI;EACJ,IAAI;EAEJ,MAAM,QAAQ,iBAAiB;GAC7B,MAAM,WAAW,cAAsB;AACrC,QAAI,CAAC,UAAW,aAAY;IAC5B,MAAM,UAAU,YAAY;IAC5B,MAAM,WAAW,KAAK,IAAI,UAAU,UAAU,EAAE;AAGhD,eAFgB,SAAS,KAAK,SAAS,UAAU,QAAQ,SAAS,CAE/C;AAEnB,QAAI,WAAW,EACb,OAAM,sBAAsB,QAAQ;QAEpC,eAAc,WAAW;;AAI7B,SAAM,sBAAsB,QAAQ;KACnC,QAAQ;AAEX,eAAa;AACX,gBAAa,MAAM;AACnB,wBAAqB,IAAI;;IAE1B;EAAC;EAAU;EAAI;EAAO;EAAU;EAAM,CAAC;AAE1C,KAAI,CAAC,eAAe,SAAS,CAAE,QAAO;CAGtC,MAAM,cADa,SAAS,MACmC;AAE/D,QAAO,aAAa,UAAU;EAC5B,KAAK,SAAS,KAAK,YAAY;EAC/B,UAAU,GAAG,SAAS,SAAS,QAAQ,GAAG;EAC3C,CAA4B;;AAK/B,SAAS,UAAU,EACjB,IACA,OACA,QACA,QACA,QACA,UACA,MACA,cAUC;CACD,MAAM,MAAM,OAAoB,KAAK;CACrC,MAAM,WAAW,UAAU,KAAK;EAAE;EAAM,QAAQ;EAAS,CAAC;CAC1D,MAAM,CAAC,WAAW,gBAAgB,eAAe,KAAK,IAAI,GAAG,GAAG,SAAS,GAAG,KAAK,KAAK,CAAC,CAAC;CACxF,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAC7C,MAAM,eAAe,OAAO,MAAM;CAElC,MAAM,WAAW,UAAU;AAE3B,iBAAgB;AACd,MAAI,CAAC,YAAY,QAAS;EAC1B,MAAM,QAAQ,iBAAiB,WAAW,KAAK,EAAE,QAAQ,IAAK;AAC9D,eAAa,aAAa,MAAM;IAC/B;EAAC;EAAU;EAAO;EAAQ,CAAC;AAE9B,iBAAgB;AACd,MAAI,CAAC,QAAS;EAEd,MAAM,aAAa;GACjB,MAAM,KAAK,KAAK,IAAI,GAAG,GAAG,SAAS,GAAG,KAAK,KAAK,CAAC;AACjD,gBAAa,GAAG;AAEhB,OAAI,MAAM,KAAK,CAAC,aAAa,SAAS;AACpC,iBAAa,UAAU;AACvB,kBAAc;;;AAIlB,QAAM;EACN,MAAM,WAAW,YAAY,MAAM,IAAK;AACxC,eAAa,cAAc,SAAS;IACnC;EAAC;EAAS;EAAI;EAAW,CAAC;AAE7B,KAAI,CAAC,eAAe,SAAS,CAAE,QAAO;CAGtC,MAAM,cADa,SAAS,MACmC;AAE/D,QAAO,aAAa,UAAU;EAC5B,KAAK,SAAS,KAAK,YAAY;EAC/B,UAAU,GAAG,SAAS,SAAS,UAAU,GAAG;EAC7C,CAA4B;;;AAM/B,MAAM,UAAU"}
|
|
1
|
+
{"version":3,"file":"count.js","names":[],"sources":["../src/count.tsx"],"sourcesContent":["\"use client\"\n\nimport {\n type ReactElement,\n type Ref,\n cloneElement,\n isValidElement,\n useEffect,\n useRef,\n useState,\n} from \"react\"\nimport { useInView, useReducedMotion } from \"motion/react\"\n\n// ── Types ────────────────────────────────────────────────────────────\n\ninterface CountProps {\n /** Target number or Date to count to */\n to: number | Date\n /** Starting number. Default: 0. Ignored when `to` is a Date. */\n from?: number\n /** Animation duration in milliseconds. Default: 900. Ignored when `to` is a Date. */\n duration?: number\n /** Delay before starting in seconds. Default: 0 */\n delay?: number\n /**\n * Format the value for display.\n * - For numbers: receives the current interpolated number.\n * - For dates: receives remaining milliseconds.\n * Default: toLocaleString() for numbers, dd:hh:mm:ss for dates.\n */\n format?: (value: number) => string\n /** Prefix string (e.g., \"$\"). Default: '' */\n prefix?: string\n /** Suffix string (e.g., \"%\", \"+\"). Default: '' */\n suffix?: string\n /** Element to render into. Receives the formatted value as children. */\n children: ReactElement\n /** Trigger once. Default: true */\n once?: boolean\n /** Easing function. Default: easeOut. Ignored when `to` is a Date. */\n easing?: (t: number) => number\n /** Called when the count finishes (reaches target or date passes). */\n onComplete?: () => void\n}\n\n/** @deprecated Use `Count` instead. `CountUp` is an alias kept for backwards compatibility. */\ntype CountUpProps = CountProps\n\n// ── Easing ───────────────────────────────────────────────────────────\n\n/** Cubic ease-out: fast start, smooth deceleration */\nfunction easeOut(t: number): number {\n return 1 - Math.pow(1 - t, 3)\n}\n\n// ── Date Formatting ──────────────────────────────────────────────────\n\nfunction formatCountdown(ms: number): string {\n if (ms <= 0) return \"00:00:00\"\n\n const totalSeconds = Math.floor(ms / 1000)\n const days = Math.floor(totalSeconds / 86400)\n const hours = Math.floor((totalSeconds % 86400) / 3600)\n const minutes = Math.floor((totalSeconds % 3600) / 60)\n const seconds = totalSeconds % 60\n\n const pad = (n: number) => String(n).padStart(2, \"0\")\n\n if (days > 0) {\n return `${days}d ${pad(hours)}:${pad(minutes)}:${pad(seconds)}`\n }\n return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`\n}\n\n// ── Ref Merge ────────────────────────────────────────────────────────\n\nfunction mergeRef(\n internalRef: React.RefObject<HTMLElement | null>,\n externalRef?: Ref<HTMLElement>,\n) {\n return (el: HTMLElement | null) => {\n ;(internalRef as { current: HTMLElement | null }).current = el\n if (typeof externalRef === \"function\") externalRef(el)\n else if (externalRef && typeof externalRef === \"object\") {\n ;(externalRef as { current: HTMLElement | null }).current = el\n }\n }\n}\n\n// ── Count ────────────────────────────────────────────────────────────\n\n/**\n * Animated number counter. Counts up, counts down, or live-counts to a date.\n *\n * Direction is automatic — if `from < to` it counts up, if `from > to` it\n * counts down. When `to` is a Date, it becomes a live countdown that ticks\n * every second.\n *\n * Zero wrapper — injects the formatted value as children via cloneElement.\n *\n * @example\n * ```tsx\n * // Count up\n * <Count to={1234}>\n * <span className=\"text-4xl font-bold tabular-nums\" />\n * </Count>\n *\n * // Count down\n * <Count from={100} to={0} onComplete={() => alert(\"Done!\")}>\n * <span className=\"text-4xl font-bold tabular-nums\" />\n * </Count>\n *\n * // Live countdown to a date\n * <Count to={new Date(\"2026-04-01T00:00:00\")}>\n * <span className=\"text-2xl font-mono tabular-nums\" />\n * </Count>\n *\n * // Custom date format\n * <Count to={launchDate} format={(ms) => `${Math.ceil(ms / 86400000)} days left`}>\n * <span className=\"text-xl\" />\n * </Count>\n * ```\n */\nfunction Count({\n to,\n from: start = 0,\n duration = 900,\n delay = 0,\n format,\n prefix = \"\",\n suffix = \"\",\n children,\n once = true,\n easing = easeOut,\n onComplete,\n}: CountProps) {\n const isDate = to instanceof Date\n\n if (isDate) {\n return (\n <DateCount\n to={to}\n delay={delay}\n format={format}\n prefix={prefix}\n suffix={suffix}\n once={once}\n onComplete={onComplete}\n >\n {children}\n </DateCount>\n )\n }\n\n return (\n <NumberCount\n to={to}\n from={start}\n duration={duration}\n delay={delay}\n format={format}\n prefix={prefix}\n suffix={suffix}\n once={once}\n easing={easing}\n onComplete={onComplete}\n >\n {children}\n </NumberCount>\n )\n}\n\n// ── Number Count (up or down) ────────────────────────────────────────\n\nfunction NumberCount({\n to,\n from: start,\n duration,\n delay,\n format,\n prefix,\n suffix,\n children,\n once,\n easing,\n onComplete,\n}: {\n to: number\n from: number\n duration: number\n delay: number\n format?: (value: number) => string\n prefix: string\n suffix: string\n children: ReactElement\n once: boolean\n easing: (t: number) => number\n onComplete?: () => void\n}) {\n const ref = useRef<HTMLElement>(null)\n const isInView = useInView(ref, { once, margin: \"-50px\" })\n const prefersReducedMotion = useReducedMotion()\n const [display, setDisplay] = useState(start)\n\n // Keep the callbacks in refs so a parent re-render with inline `easing` /\n // `onComplete` props doesn't re-run the animation effect — which would cancel\n // the in-flight rAF and freeze the counter mid-count. The effect below keys\n // only on the values that should actually (re)start the animation, so a live\n // `to` change or a re-entry (with `once={false}`) restarts cleanly.\n const easingRef = useRef(easing)\n const onCompleteRef = useRef(onComplete)\n useEffect(() => {\n easingRef.current = easing\n onCompleteRef.current = onComplete\n })\n\n const formatFn = format ?? ((n: number) =>\n Number.isInteger(to) ? Math.round(n).toLocaleString() : n.toLocaleString()\n )\n\n useEffect(() => {\n if (!isInView) return\n\n const delayMs = delay * 1000\n\n // Vestibular safety: skip the count-up/down tween entirely under\n // prefers-reduced-motion and snap to the final value. We still honor the\n // in-view gate and `delay`, then fire `onComplete` once — matching the\n // resting state and lifecycle of the animated path.\n if (prefersReducedMotion) {\n const timer = setTimeout(() => {\n setDisplay(to)\n onCompleteRef.current?.()\n }, delayMs)\n\n return () => clearTimeout(timer)\n }\n\n let raf: number\n let startTime: number\n\n const timer = setTimeout(() => {\n const animate = (timestamp: number) => {\n if (!startTime) startTime = timestamp\n const elapsed = timestamp - startTime\n const progress = Math.min(elapsed / duration, 1)\n const current = start + (to - start) * easingRef.current(progress)\n\n setDisplay(current)\n\n if (progress < 1) {\n raf = requestAnimationFrame(animate)\n } else {\n onCompleteRef.current?.()\n }\n }\n\n raf = requestAnimationFrame(animate)\n }, delayMs)\n\n return () => {\n clearTimeout(timer)\n cancelAnimationFrame(raf)\n }\n }, [isInView, to, start, duration, delay, prefersReducedMotion])\n\n if (!isValidElement(children)) return children\n\n const childProps = children.props as Record<string, unknown>\n const existingRef = (childProps as { ref?: Ref<HTMLElement> }).ref\n\n return cloneElement(children, {\n ref: mergeRef(ref, existingRef),\n children: `${prefix}${formatFn(display)}${suffix}`,\n } as Record<string, unknown>)\n}\n\n// ── Date Count (live countdown) ──────────────────────────────────────\n\nfunction DateCount({\n to,\n delay,\n format,\n prefix,\n suffix,\n children,\n once,\n onComplete,\n}: {\n to: Date\n delay: number\n format?: (value: number) => string\n prefix: string\n suffix: string\n children: ReactElement\n once: boolean\n onComplete?: () => void\n}) {\n const ref = useRef<HTMLElement>(null)\n const isInView = useInView(ref, { once, margin: \"-50px\" })\n const [remaining, setRemaining] = useState(() => Math.max(0, to.getTime() - Date.now()))\n const [started, setStarted] = useState(false)\n const completedRef = useRef(false)\n\n const formatFn = format ?? formatCountdown\n\n useEffect(() => {\n if (!isInView || started) return\n const timer = setTimeout(() => setStarted(true), delay * 1000)\n return () => clearTimeout(timer)\n }, [isInView, delay, started])\n\n useEffect(() => {\n if (!started) return\n\n const tick = () => {\n const ms = Math.max(0, to.getTime() - Date.now())\n setRemaining(ms)\n\n if (ms <= 0 && !completedRef.current) {\n completedRef.current = true\n onComplete?.()\n }\n }\n\n tick()\n const interval = setInterval(tick, 1000)\n return () => clearInterval(interval)\n }, [started, to, onComplete])\n\n if (!isValidElement(children)) return children\n\n const childProps = children.props as Record<string, unknown>\n const existingRef = (childProps as { ref?: Ref<HTMLElement> }).ref\n\n return cloneElement(children, {\n ref: mergeRef(ref, existingRef),\n children: `${prefix}${formatFn(remaining)}${suffix}`,\n } as Record<string, unknown>)\n}\n\n// ── Exports ──────────────────────────────────────────────────────────\n\n/** @deprecated Use `Count` instead */\nconst CountUp = Count\n\nexport { Count, CountUp, easeOut }\nexport type { CountProps, CountUpProps }\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encrypted-text.d.ts","names":[],"sources":["../src/encrypted-text.tsx"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"encrypted-text.d.ts","names":[],"sources":["../src/encrypted-text.tsx"],"mappings":";;;;KAOK,kBAAA,GAAqB,KAAA,CAAM,cAAA;EAC9B,IAAA;EACA,aAAA;EACA,OAAA;EACA,WAAA;EACA,kBAAA;EACA,iBAAA;EACA,YAAA;EACA,eAAA;AAAA;AAAA,iBAmBO,aAAA,CAAA;EACP,IAAA;EACA,SAAA;EACA,aAAA;EACA,OAAA;EACA,WAAA;EACA,kBAAA;EACA,iBAAA;EACA,YAAA;EACA,eAAA;EAAA,GACG;AAAA,GACF,kBAAA,GAAkB,oBAAA,CAAA,GAAA,CAAA,OAAA"}
|
package/dist/encrypted-text.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { cn } from "./lib/utils.js";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { useReducedMotion } from "motion/react";
|
|
5
6
|
//#region src/encrypted-text.tsx
|
|
6
7
|
const DEFAULT_CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-={}[];:,.<>/?";
|
|
7
8
|
function randomChar(charset) {
|
|
@@ -14,6 +15,7 @@ function scramblePreservingSpaces(original, charset) {
|
|
|
14
15
|
return result;
|
|
15
16
|
}
|
|
16
17
|
function EncryptedText({ text, className, revealDelayMs = 50, charset = DEFAULT_CHARSET, flipDelayMs = 50, encryptedClassName, revealedClassName, scrambleOnly = false, scrambleOneChar = false, ...props }) {
|
|
18
|
+
const prefersReducedMotion = useReducedMotion();
|
|
17
19
|
const ref = React.useRef(null);
|
|
18
20
|
const [isInView, setIsInView] = React.useState(false);
|
|
19
21
|
const [revealCount, setRevealCount] = React.useState(0);
|
|
@@ -23,6 +25,7 @@ function EncryptedText({ text, className, revealDelayMs = 50, charset = DEFAULT_
|
|
|
23
25
|
const lastFlipTimeRef = React.useRef(0);
|
|
24
26
|
const scrambleCharsRef = React.useRef(text ? scramblePreservingSpaces(text, charset).split("") : []);
|
|
25
27
|
React.useEffect(() => {
|
|
28
|
+
if (prefersReducedMotion) return;
|
|
26
29
|
const el = ref.current;
|
|
27
30
|
if (!el) return;
|
|
28
31
|
const observer = new IntersectionObserver(([entry]) => {
|
|
@@ -33,8 +36,9 @@ function EncryptedText({ text, className, revealDelayMs = 50, charset = DEFAULT_
|
|
|
33
36
|
}, { threshold: 0 });
|
|
34
37
|
observer.observe(el);
|
|
35
38
|
return () => observer.disconnect();
|
|
36
|
-
}, []);
|
|
39
|
+
}, [prefersReducedMotion]);
|
|
37
40
|
React.useEffect(() => {
|
|
41
|
+
if (prefersReducedMotion) return;
|
|
38
42
|
if (!isInView) return;
|
|
39
43
|
scrambleCharsRef.current = (text ? scramblePreservingSpaces(text, charset) : "").split("");
|
|
40
44
|
startTimeRef.current = performance.now();
|
|
@@ -76,6 +80,7 @@ function EncryptedText({ text, className, revealDelayMs = 50, charset = DEFAULT_
|
|
|
76
80
|
if (animationFrameRef.current !== null) cancelAnimationFrame(animationFrameRef.current);
|
|
77
81
|
};
|
|
78
82
|
}, [
|
|
83
|
+
prefersReducedMotion,
|
|
79
84
|
isInView,
|
|
80
85
|
text,
|
|
81
86
|
revealDelayMs,
|
|
@@ -94,7 +99,7 @@ function EncryptedText({ text, className, revealDelayMs = 50, charset = DEFAULT_
|
|
|
94
99
|
className: "sr-only",
|
|
95
100
|
children: text
|
|
96
101
|
}), text.split("").map((char, index) => {
|
|
97
|
-
const isRevealed = !scrambleOnly && index < revealCount;
|
|
102
|
+
const isRevealed = prefersReducedMotion || !scrambleOnly && index < revealCount;
|
|
98
103
|
const displayChar = isRevealed ? char : char === " " ? " " : scrambleCharsRef.current[index] ?? randomChar(charset);
|
|
99
104
|
return /* @__PURE__ */ jsx("span", {
|
|
100
105
|
"aria-hidden": "true",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encrypted-text.js","names":[],"sources":["../src/encrypted-text.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"./lib/utils\"\n\ntype EncryptedTextProps = React.ComponentProps<\"span\"> & {\n text: string\n revealDelayMs?: number\n charset?: string\n flipDelayMs?: number\n encryptedClassName?: string\n revealedClassName?: string\n scrambleOnly?: boolean\n scrambleOneChar?: boolean\n}\n\nconst DEFAULT_CHARSET =\n \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-={}[];:,.<>/?\"\n\nfunction randomChar(charset: string): string {\n return charset.charAt(Math.floor(Math.random() * charset.length))\n}\n\nfunction scramblePreservingSpaces(original: string, charset: string): string {\n if (!original) return \"\"\n let result = \"\"\n for (let i = 0; i < original.length; i += 1) {\n result += original[i] === \" \" ? \" \" : randomChar(charset)\n }\n return result\n}\n\nfunction EncryptedText({\n text,\n className,\n revealDelayMs = 50,\n charset = DEFAULT_CHARSET,\n flipDelayMs = 50,\n encryptedClassName,\n revealedClassName,\n scrambleOnly = false,\n scrambleOneChar = false,\n ...props\n}: EncryptedTextProps) {\n const ref = React.useRef<HTMLSpanElement>(null)\n const [isInView, setIsInView] = React.useState(false)\n const [revealCount, setRevealCount] = React.useState(0)\n const [, setFlipTick] = React.useState(0)\n\n const animationFrameRef = React.useRef<number | null>(null)\n const startTimeRef = React.useRef(0)\n const lastFlipTimeRef = React.useRef(0)\n const scrambleCharsRef = React.useRef<string[]>(\n text ? scramblePreservingSpaces(text, charset).split(\"\") : []\n )\n\n React.useEffect(() => {\n const el = ref.current\n if (!el) return\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry?.isIntersecting) {\n setIsInView(true)\n observer.disconnect()\n }\n },\n { threshold: 0 }\n )\n\n observer.observe(el)\n return () => observer.disconnect()\n }, [])\n\n React.useEffect(() => {\n if (!isInView) return\n\n const initial = text\n ? scramblePreservingSpaces(text, charset)\n : \"\"\n scrambleCharsRef.current = initial.split(\"\")\n startTimeRef.current = performance.now()\n lastFlipTimeRef.current = startTimeRef.current\n setRevealCount(0)\n\n let isCancelled = false\n\n const update = (now: number) => {\n if (isCancelled) return\n\n const totalLength = text.length\n\n if (scrambleOnly) {\n const timeSinceLastFlip = now - lastFlipTimeRef.current\n if (timeSinceLastFlip >= Math.max(0, flipDelayMs)) {\n if (scrambleOneChar) {\n const indices: number[] = []\n for (let i = 0; i < totalLength; i++) {\n if (text[i] !== \" \") indices.push(i)\n }\n if (indices.length > 0) {\n const idx = indices[Math.floor(Math.random() * indices.length)]!\n scrambleCharsRef.current[idx] = randomChar(charset)\n }\n } else {\n for (let index = 0; index < totalLength; index += 1) {\n scrambleCharsRef.current[index] =\n text[index] === \" \" ? \" \" : randomChar(charset)\n }\n }\n lastFlipTimeRef.current = now\n setFlipTick((t) => (t + 1) & 0xffff)\n }\n animationFrameRef.current = requestAnimationFrame(update)\n return\n }\n\n const elapsedMs = now - startTimeRef.current\n const currentRevealCount = Math.min(\n totalLength,\n Math.floor(elapsedMs / Math.max(1, revealDelayMs))\n )\n\n setRevealCount(currentRevealCount)\n\n if (currentRevealCount >= totalLength) return\n\n const timeSinceLastFlip = now - lastFlipTimeRef.current\n if (timeSinceLastFlip >= Math.max(0, flipDelayMs)) {\n for (let index = currentRevealCount; index < totalLength; index += 1) {\n scrambleCharsRef.current[index] =\n text[index] === \" \" ? \" \" : randomChar(charset)\n }\n lastFlipTimeRef.current = now\n }\n\n animationFrameRef.current = requestAnimationFrame(update)\n }\n\n animationFrameRef.current = requestAnimationFrame(update)\n\n return () => {\n isCancelled = true\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current)\n }\n }\n }, [isInView, text, revealDelayMs, charset, flipDelayMs, scrambleOnly, scrambleOneChar])\n\n if (!text) return null\n\n return (\n <span\n ref={ref}\n data-slot=\"encrypted-text\"\n className={className}\n {...props}\n >\n {/* Real text for assistive tech; the animated glyphs below are decorative\n * (aria-label on a role-less span is unreliable, and the scrambled\n * characters must not be read out). */}\n <span className=\"sr-only\">{text}</span>\n {text.split(\"\").map((char, index) => {\n const isRevealed
|
|
1
|
+
{"version":3,"file":"encrypted-text.js","names":[],"sources":["../src/encrypted-text.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"./lib/utils\"\n\ntype EncryptedTextProps = React.ComponentProps<\"span\"> & {\n text: string\n revealDelayMs?: number\n charset?: string\n flipDelayMs?: number\n encryptedClassName?: string\n revealedClassName?: string\n scrambleOnly?: boolean\n scrambleOneChar?: boolean\n}\n\nconst DEFAULT_CHARSET =\n \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-={}[];:,.<>/?\"\n\nfunction randomChar(charset: string): string {\n return charset.charAt(Math.floor(Math.random() * charset.length))\n}\n\nfunction scramblePreservingSpaces(original: string, charset: string): string {\n if (!original) return \"\"\n let result = \"\"\n for (let i = 0; i < original.length; i += 1) {\n result += original[i] === \" \" ? \" \" : randomChar(charset)\n }\n return result\n}\n\nfunction EncryptedText({\n text,\n className,\n revealDelayMs = 50,\n charset = DEFAULT_CHARSET,\n flipDelayMs = 50,\n encryptedClassName,\n revealedClassName,\n scrambleOnly = false,\n scrambleOneChar = false,\n ...props\n}: EncryptedTextProps) {\n const prefersReducedMotion = useReducedMotion()\n const ref = React.useRef<HTMLSpanElement>(null)\n const [isInView, setIsInView] = React.useState(false)\n const [revealCount, setRevealCount] = React.useState(0)\n const [, setFlipTick] = React.useState(0)\n\n const animationFrameRef = React.useRef<number | null>(null)\n const startTimeRef = React.useRef(0)\n const lastFlipTimeRef = React.useRef(0)\n const scrambleCharsRef = React.useRef<string[]>(\n text ? scramblePreservingSpaces(text, charset).split(\"\") : []\n )\n\n React.useEffect(() => {\n if (prefersReducedMotion) return\n const el = ref.current\n if (!el) return\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry?.isIntersecting) {\n setIsInView(true)\n observer.disconnect()\n }\n },\n { threshold: 0 }\n )\n\n observer.observe(el)\n return () => observer.disconnect()\n }, [prefersReducedMotion])\n\n React.useEffect(() => {\n if (prefersReducedMotion) return\n if (!isInView) return\n\n const initial = text\n ? scramblePreservingSpaces(text, charset)\n : \"\"\n scrambleCharsRef.current = initial.split(\"\")\n startTimeRef.current = performance.now()\n lastFlipTimeRef.current = startTimeRef.current\n setRevealCount(0)\n\n let isCancelled = false\n\n const update = (now: number) => {\n if (isCancelled) return\n\n const totalLength = text.length\n\n if (scrambleOnly) {\n const timeSinceLastFlip = now - lastFlipTimeRef.current\n if (timeSinceLastFlip >= Math.max(0, flipDelayMs)) {\n if (scrambleOneChar) {\n const indices: number[] = []\n for (let i = 0; i < totalLength; i++) {\n if (text[i] !== \" \") indices.push(i)\n }\n if (indices.length > 0) {\n const idx = indices[Math.floor(Math.random() * indices.length)]!\n scrambleCharsRef.current[idx] = randomChar(charset)\n }\n } else {\n for (let index = 0; index < totalLength; index += 1) {\n scrambleCharsRef.current[index] =\n text[index] === \" \" ? \" \" : randomChar(charset)\n }\n }\n lastFlipTimeRef.current = now\n setFlipTick((t) => (t + 1) & 0xffff)\n }\n animationFrameRef.current = requestAnimationFrame(update)\n return\n }\n\n const elapsedMs = now - startTimeRef.current\n const currentRevealCount = Math.min(\n totalLength,\n Math.floor(elapsedMs / Math.max(1, revealDelayMs))\n )\n\n setRevealCount(currentRevealCount)\n\n if (currentRevealCount >= totalLength) return\n\n const timeSinceLastFlip = now - lastFlipTimeRef.current\n if (timeSinceLastFlip >= Math.max(0, flipDelayMs)) {\n for (let index = currentRevealCount; index < totalLength; index += 1) {\n scrambleCharsRef.current[index] =\n text[index] === \" \" ? \" \" : randomChar(charset)\n }\n lastFlipTimeRef.current = now\n }\n\n animationFrameRef.current = requestAnimationFrame(update)\n }\n\n animationFrameRef.current = requestAnimationFrame(update)\n\n return () => {\n isCancelled = true\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current)\n }\n }\n }, [prefersReducedMotion, isInView, text, revealDelayMs, charset, flipDelayMs, scrambleOnly, scrambleOneChar])\n\n if (!text) return null\n\n return (\n <span\n ref={ref}\n data-slot=\"encrypted-text\"\n className={className}\n {...props}\n >\n {/* Real text for assistive tech; the animated glyphs below are decorative\n * (aria-label on a role-less span is unreliable, and the scrambled\n * characters must not be read out). */}\n <span className=\"sr-only\">{text}</span>\n {text.split(\"\").map((char, index) => {\n // Reduced motion: skip the scramble entirely and show every\n // character in its final revealed form from the first frame.\n const isRevealed =\n prefersReducedMotion || (!scrambleOnly && index < revealCount)\n const displayChar = isRevealed\n ? char\n : char === \" \"\n ? \" \"\n : (scrambleCharsRef.current[index] ?? randomChar(charset))\n\n return (\n <span\n key={index}\n aria-hidden=\"true\"\n data-slot=\"encrypted-text-char\"\n data-revealed={isRevealed || undefined}\n className={cn(isRevealed ? revealedClassName : encryptedClassName)}\n >\n {displayChar}\n </span>\n )\n })}\n </span>\n )\n}\n\nexport { EncryptedText }\n"],"mappings":"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"}
|
package/dist/input-group.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ declare function InputGroup({
|
|
|
18
18
|
...props
|
|
19
19
|
}: InputGroupProps): _$react_jsx_runtime0.JSX.Element;
|
|
20
20
|
declare const inputGroupAddonVariants: (props?: ({
|
|
21
|
-
align?: "inline-
|
|
21
|
+
align?: "inline-start" | "inline-end" | "block-start" | "block-end" | null | undefined;
|
|
22
22
|
} & _$class_variance_authority_types0.ClassProp) | undefined) => string;
|
|
23
23
|
declare function InputGroupAddon({
|
|
24
24
|
className,
|
package/dist/masonry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"masonry.d.ts","names":[],"sources":["../src/masonry.tsx"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"masonry.d.ts","names":[],"sources":["../src/masonry.tsx"],"mappings":";;;;;KAkBK,YAAA,GAAe,KAAA,CAAM,cAAA;EACxB,OAAA;EACA,WAAA;EACA,GAAA;AAAA;AAAA,KAGG,gBAAA,GAAmB,IAAA,CAAK,eAAA;EAC3B,IAAA;EACA,QAAA,GAAW,KAAA,CAAM,SAAA;AAAA;AAAA,iBAGV,OAAA,CAAA;EACP,OAAA;EACA,WAAA;EACA,GAAA;EACA,SAAA;EACA,QAAA;EAAA,GACG;AAAA,GACF,YAAA,GAAY,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,iBA0NN,WAAA,CAAA;EACP,SAAA;EACA,IAAA;EACA,QAAA;EAAA,GACG;AAAA,GACF,gBAAA,GAAgB,oBAAA,CAAA,GAAA,CAAA,OAAA"}
|
package/dist/masonry.js
CHANGED
|
@@ -3,7 +3,7 @@ import { cn } from "./lib/utils.js";
|
|
|
3
3
|
import { StarIcon } from "./lib/internal-icons.js";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
-
import { AnimatePresence, motion } from "motion/react";
|
|
6
|
+
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
7
7
|
//#region src/masonry.tsx
|
|
8
8
|
const STAGGER_STEP = .05;
|
|
9
9
|
const MasonryStaggerContext = React.createContext(null);
|
|
@@ -159,12 +159,17 @@ function FeaturedBadge() {
|
|
|
159
159
|
function MasonryItem({ className, span, children, ...props }) {
|
|
160
160
|
const isSpanned = span != null && span > 1;
|
|
161
161
|
const getStaggerIndex = React.useContext(MasonryStaggerContext);
|
|
162
|
+
const prefersReducedMotion = useReducedMotion();
|
|
162
163
|
const [staggerDelay] = React.useState(() => getStaggerIndex ? getStaggerIndex() * STAGGER_STEP : 0);
|
|
163
164
|
return /* @__PURE__ */ jsxs(motion.div, {
|
|
164
165
|
"data-slot": "masonry-item",
|
|
165
166
|
"data-span": isSpanned ? span : void 0,
|
|
166
167
|
className: cn("relative", className),
|
|
167
|
-
initial: {
|
|
168
|
+
initial: prefersReducedMotion ? {
|
|
169
|
+
opacity: 1,
|
|
170
|
+
y: 0,
|
|
171
|
+
filter: "blur(0px)"
|
|
172
|
+
} : {
|
|
168
173
|
opacity: 0,
|
|
169
174
|
y: 10,
|
|
170
175
|
filter: "blur(8px)"
|
|
@@ -174,13 +179,13 @@ function MasonryItem({ className, span, children, ...props }) {
|
|
|
174
179
|
y: 0,
|
|
175
180
|
filter: "blur(0px)"
|
|
176
181
|
},
|
|
177
|
-
transition: {
|
|
182
|
+
transition: prefersReducedMotion ? { duration: 0 } : {
|
|
178
183
|
type: "spring",
|
|
179
184
|
stiffness: 100,
|
|
180
185
|
damping: 10,
|
|
181
186
|
delay: staggerDelay
|
|
182
187
|
},
|
|
183
|
-
exit: {
|
|
188
|
+
exit: prefersReducedMotion ? { opacity: 0 } : {
|
|
184
189
|
opacity: 0,
|
|
185
190
|
scale: 1.2,
|
|
186
191
|
filter: "blur(8px)"
|
package/dist/masonry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"masonry.js","names":[],"sources":["../src/masonry.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\n\nimport { AnimatePresence, type HTMLMotionProps, motion } from \"motion/react\"\n\nimport { StarIcon } from \"./lib/internal-icons\"\nimport { cn } from \"./lib/utils\"\n\nconst STAGGER_STEP = 0.05 // 50ms between each item's enter animation\n\nconst MasonryStaggerContext = React.createContext<(() => number) | null>(null)\n\ntype MasonryProps = React.ComponentProps<\"div\"> & {\n columns?: number\n columnWidth?: number\n gap?: number\n}\n\ntype MasonryItemProps = Omit<HTMLMotionProps<\"div\">, \"children\"> & {\n span?: number\n children?: React.ReactNode\n}\n\nfunction Masonry({\n columns,\n columnWidth,\n gap = 4,\n className,\n children,\n ...props\n}: MasonryProps) {\n const containerRef = React.useRef<HTMLDivElement>(null)\n const rafRef = React.useRef<number>(0)\n const staggerCounterRef = React.useRef(0)\n\n // Reset counter each render so new items in a batch get fresh 0-based indices\n staggerCounterRef.current = 0\n\n const getStaggerIndex = React.useCallback(\n () => staggerCounterRef.current++,\n [],\n )\n\n React.useLayoutEffect(() => {\n const container = containerRef.current\n if (!container) return\n\n const remPx = parseFloat(\n getComputedStyle(document.documentElement).fontSize,\n )\n const gapPx = gap * 0.25 * remPx\n\n function reflow() {\n const allChildren = Array.from(container!.children) as HTMLElement[]\n // Skip exiting items — they keep their position during exit animation\n const items = allChildren.filter((el) => el.dataset.exiting == null)\n\n if (items.length === 0) {\n container!.style.removeProperty(\"height\")\n return\n }\n\n const containerWidth = container!.clientWidth\n if (containerWidth === 0) return\n\n let colCount: number\n if (columns != null) {\n colCount = Math.max(1, columns)\n } else if (columnWidth != null) {\n colCount = Math.max(\n 1,\n Math.floor((containerWidth + gapPx) / (columnWidth + gapPx)),\n )\n } else {\n colCount = Math.max(\n 1,\n Math.floor((containerWidth + gapPx) / (240 + gapPx)),\n )\n }\n\n // Single column: use normal flow with gap\n if (colCount <= 1) {\n container!.style.removeProperty(\"height\")\n container!.style.display = \"flex\"\n container!.style.flexDirection = \"column\"\n container!.style.gap = `${gapPx}px`\n for (const item of items) {\n item.style.removeProperty(\"position\")\n item.style.removeProperty(\"top\")\n item.style.removeProperty(\"left\")\n item.style.removeProperty(\"width\")\n }\n return\n }\n\n // Multi-column: clear single-column styles\n container!.style.removeProperty(\"display\")\n container!.style.removeProperty(\"flex-direction\")\n container!.style.removeProperty(\"gap\")\n\n const colWidth = (containerWidth - (colCount - 1) * gapPx) / colCount\n const columnBottoms = new Array<number>(colCount).fill(0)\n\n // Partition items: spanning (top-pinned) vs regular\n const topItems: { el: HTMLElement; span: number }[] = []\n const regularItems: HTMLElement[] = []\n for (const item of items) {\n const raw = parseInt(item.dataset.span || \"1\", 10)\n if (raw > 1) {\n topItems.push({ el: item, span: Math.min(raw, colCount) })\n } else {\n regularItems.push(item)\n }\n }\n\n // First pass: set width on all items for correct height measurement\n for (const { el, span } of topItems) {\n el.style.position = \"absolute\"\n el.style.width = `${span * colWidth + (span - 1) * gapPx}px`\n }\n for (const item of regularItems) {\n item.style.position = \"absolute\"\n item.style.width = `${colWidth}px`\n }\n\n // Second pass: batch-read heights\n const topHeights: number[] = []\n for (const { el } of topItems) {\n topHeights.push(el.offsetHeight)\n }\n const regularHeights: number[] = []\n for (const item of regularItems) {\n regularHeights.push(item.offsetHeight)\n }\n\n // Third pass: place top items at Y=0, left-to-right\n let nextCol = 0\n for (let i = 0; i < topItems.length; i++) {\n const { el, span } = topItems[i]!\n const s = Math.min(span, colCount - nextCol)\n\n const x = nextCol * (colWidth + gapPx)\n el.style.top = \"0px\"\n el.style.left = `${x}px`\n // Recalculate width if span was clamped\n if (s !== span) {\n el.style.width = `${s * colWidth + (s - 1) * gapPx}px`\n }\n\n const bottom = topHeights[i]! + gapPx\n for (let c = nextCol; c < nextCol + s; c++) {\n columnBottoms[c] = bottom\n }\n\n nextCol += s\n }\n\n // Fourth pass: place regular items in shortest column\n for (let i = 0; i < regularItems.length; i++) {\n let shortestCol = 0\n for (let c = 1; c < colCount; c++) {\n if (columnBottoms[c]! < columnBottoms[shortestCol]!) {\n shortestCol = c\n }\n }\n\n const x = shortestCol * (colWidth + gapPx)\n const y = columnBottoms[shortestCol]!\n\n regularItems[i]!.style.top = `${y}px`\n regularItems[i]!.style.left = `${x}px`\n\n columnBottoms[shortestCol] = y + regularHeights[i]! + gapPx\n }\n\n const maxBottom = Math.max(...columnBottoms) - gapPx\n container!.style.height = `${Math.max(0, maxBottom)}px`\n }\n\n function scheduleReflow() {\n cancelAnimationFrame(rafRef.current)\n rafRef.current = requestAnimationFrame(reflow)\n }\n\n reflow()\n\n const ro = new ResizeObserver(scheduleReflow)\n ro.observe(container)\n\n const mo = new MutationObserver(scheduleReflow)\n mo.observe(container, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"data-exiting\", \"data-span\"],\n })\n\n // Detect image/media loads that change item heights\n container.addEventListener(\"load\", scheduleReflow, true)\n\n return () => {\n cancelAnimationFrame(rafRef.current)\n ro.disconnect()\n mo.disconnect()\n container.removeEventListener(\"load\", scheduleReflow, true)\n container.style.removeProperty(\"height\")\n container.style.removeProperty(\"display\")\n container.style.removeProperty(\"flex-direction\")\n container.style.removeProperty(\"gap\")\n const items = Array.from(container.children) as HTMLElement[]\n for (const item of items) {\n item.style.removeProperty(\"position\")\n item.style.removeProperty(\"top\")\n item.style.removeProperty(\"left\")\n item.style.removeProperty(\"width\")\n }\n }\n }, [columns, columnWidth, gap])\n\n return (\n <MasonryStaggerContext.Provider value={getStaggerIndex}>\n <div\n ref={containerRef}\n data-slot=\"masonry\"\n className={cn(\"relative\", className)}\n {...props}\n >\n <AnimatePresence>\n {children}\n </AnimatePresence>\n </div>\n </MasonryStaggerContext.Provider>\n )\n}\n\nfunction FeaturedBadge() {\n return (\n <span\n data-slot=\"masonry-badge\"\n className=\"absolute top-2 right-2 z-10 flex size-5 items-center justify-center pointer-events-none\"\n role=\"img\"\n aria-label=\"Featured\"\n >\n <StarIcon width={10} height={10} fill=\"currentColor\" aria-hidden />\n </span>\n )\n}\n\nfunction MasonryItem({\n className,\n span,\n children,\n ...props\n}: MasonryItemProps) {\n const isSpanned = span != null && span > 1\n const getStaggerIndex = React.useContext(MasonryStaggerContext)\n\n // Capture stagger index once on mount — useState initializer runs exactly once\n const [staggerDelay] = React.useState(() =>\n getStaggerIndex ? getStaggerIndex() * STAGGER_STEP : 0,\n )\n\n return (\n <motion.div\n data-slot=\"masonry-item\"\n data-span={isSpanned ? span : undefined}\n className={cn(\"relative\", className)}\n initial={{\n opacity: 0,\n y: 10,\n filter: \"blur(8px)\"\n }}\n animate={{\n opacity: 1,\n y: 0,\n filter: \"blur(0px)\"\n }}\n transition={{\n type: \"spring\",\n stiffness: 100,\n damping: 10,\n delay: staggerDelay,\n }}\n exit={{\n opacity: 0,\n scale: 1.2,\n filter: \"blur(8px)\",\n }}\n {...props}\n >\n {children}\n {isSpanned && <FeaturedBadge />}\n </motion.div>\n )\n}\n\nexport { Masonry, MasonryItem }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"masonry.js","names":[],"sources":["../src/masonry.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\n\nimport {\n AnimatePresence,\n type HTMLMotionProps,\n motion,\n useReducedMotion,\n} from \"motion/react\"\n\nimport { StarIcon } from \"./lib/internal-icons\"\nimport { cn } from \"./lib/utils\"\n\nconst STAGGER_STEP = 0.05 // 50ms between each item's enter animation\n\nconst MasonryStaggerContext = React.createContext<(() => number) | null>(null)\n\ntype MasonryProps = React.ComponentProps<\"div\"> & {\n columns?: number\n columnWidth?: number\n gap?: number\n}\n\ntype MasonryItemProps = Omit<HTMLMotionProps<\"div\">, \"children\"> & {\n span?: number\n children?: React.ReactNode\n}\n\nfunction Masonry({\n columns,\n columnWidth,\n gap = 4,\n className,\n children,\n ...props\n}: MasonryProps) {\n const containerRef = React.useRef<HTMLDivElement>(null)\n const rafRef = React.useRef<number>(0)\n const staggerCounterRef = React.useRef(0)\n\n // Reset counter each render so new items in a batch get fresh 0-based indices\n staggerCounterRef.current = 0\n\n const getStaggerIndex = React.useCallback(\n () => staggerCounterRef.current++,\n [],\n )\n\n React.useLayoutEffect(() => {\n const container = containerRef.current\n if (!container) return\n\n const remPx = parseFloat(\n getComputedStyle(document.documentElement).fontSize,\n )\n const gapPx = gap * 0.25 * remPx\n\n function reflow() {\n const allChildren = Array.from(container!.children) as HTMLElement[]\n // Skip exiting items — they keep their position during exit animation\n const items = allChildren.filter((el) => el.dataset.exiting == null)\n\n if (items.length === 0) {\n container!.style.removeProperty(\"height\")\n return\n }\n\n const containerWidth = container!.clientWidth\n if (containerWidth === 0) return\n\n let colCount: number\n if (columns != null) {\n colCount = Math.max(1, columns)\n } else if (columnWidth != null) {\n colCount = Math.max(\n 1,\n Math.floor((containerWidth + gapPx) / (columnWidth + gapPx)),\n )\n } else {\n colCount = Math.max(\n 1,\n Math.floor((containerWidth + gapPx) / (240 + gapPx)),\n )\n }\n\n // Single column: use normal flow with gap\n if (colCount <= 1) {\n container!.style.removeProperty(\"height\")\n container!.style.display = \"flex\"\n container!.style.flexDirection = \"column\"\n container!.style.gap = `${gapPx}px`\n for (const item of items) {\n item.style.removeProperty(\"position\")\n item.style.removeProperty(\"top\")\n item.style.removeProperty(\"left\")\n item.style.removeProperty(\"width\")\n }\n return\n }\n\n // Multi-column: clear single-column styles\n container!.style.removeProperty(\"display\")\n container!.style.removeProperty(\"flex-direction\")\n container!.style.removeProperty(\"gap\")\n\n const colWidth = (containerWidth - (colCount - 1) * gapPx) / colCount\n const columnBottoms = new Array<number>(colCount).fill(0)\n\n // Partition items: spanning (top-pinned) vs regular\n const topItems: { el: HTMLElement; span: number }[] = []\n const regularItems: HTMLElement[] = []\n for (const item of items) {\n const raw = parseInt(item.dataset.span || \"1\", 10)\n if (raw > 1) {\n topItems.push({ el: item, span: Math.min(raw, colCount) })\n } else {\n regularItems.push(item)\n }\n }\n\n // First pass: set width on all items for correct height measurement\n for (const { el, span } of topItems) {\n el.style.position = \"absolute\"\n el.style.width = `${span * colWidth + (span - 1) * gapPx}px`\n }\n for (const item of regularItems) {\n item.style.position = \"absolute\"\n item.style.width = `${colWidth}px`\n }\n\n // Second pass: batch-read heights\n const topHeights: number[] = []\n for (const { el } of topItems) {\n topHeights.push(el.offsetHeight)\n }\n const regularHeights: number[] = []\n for (const item of regularItems) {\n regularHeights.push(item.offsetHeight)\n }\n\n // Third pass: place top items at Y=0, left-to-right\n let nextCol = 0\n for (let i = 0; i < topItems.length; i++) {\n const { el, span } = topItems[i]!\n const s = Math.min(span, colCount - nextCol)\n\n const x = nextCol * (colWidth + gapPx)\n el.style.top = \"0px\"\n el.style.left = `${x}px`\n // Recalculate width if span was clamped\n if (s !== span) {\n el.style.width = `${s * colWidth + (s - 1) * gapPx}px`\n }\n\n const bottom = topHeights[i]! + gapPx\n for (let c = nextCol; c < nextCol + s; c++) {\n columnBottoms[c] = bottom\n }\n\n nextCol += s\n }\n\n // Fourth pass: place regular items in shortest column\n for (let i = 0; i < regularItems.length; i++) {\n let shortestCol = 0\n for (let c = 1; c < colCount; c++) {\n if (columnBottoms[c]! < columnBottoms[shortestCol]!) {\n shortestCol = c\n }\n }\n\n const x = shortestCol * (colWidth + gapPx)\n const y = columnBottoms[shortestCol]!\n\n regularItems[i]!.style.top = `${y}px`\n regularItems[i]!.style.left = `${x}px`\n\n columnBottoms[shortestCol] = y + regularHeights[i]! + gapPx\n }\n\n const maxBottom = Math.max(...columnBottoms) - gapPx\n container!.style.height = `${Math.max(0, maxBottom)}px`\n }\n\n function scheduleReflow() {\n cancelAnimationFrame(rafRef.current)\n rafRef.current = requestAnimationFrame(reflow)\n }\n\n reflow()\n\n const ro = new ResizeObserver(scheduleReflow)\n ro.observe(container)\n\n const mo = new MutationObserver(scheduleReflow)\n mo.observe(container, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"data-exiting\", \"data-span\"],\n })\n\n // Detect image/media loads that change item heights\n container.addEventListener(\"load\", scheduleReflow, true)\n\n return () => {\n cancelAnimationFrame(rafRef.current)\n ro.disconnect()\n mo.disconnect()\n container.removeEventListener(\"load\", scheduleReflow, true)\n container.style.removeProperty(\"height\")\n container.style.removeProperty(\"display\")\n container.style.removeProperty(\"flex-direction\")\n container.style.removeProperty(\"gap\")\n const items = Array.from(container.children) as HTMLElement[]\n for (const item of items) {\n item.style.removeProperty(\"position\")\n item.style.removeProperty(\"top\")\n item.style.removeProperty(\"left\")\n item.style.removeProperty(\"width\")\n }\n }\n }, [columns, columnWidth, gap])\n\n return (\n <MasonryStaggerContext.Provider value={getStaggerIndex}>\n <div\n ref={containerRef}\n data-slot=\"masonry\"\n className={cn(\"relative\", className)}\n {...props}\n >\n <AnimatePresence>\n {children}\n </AnimatePresence>\n </div>\n </MasonryStaggerContext.Provider>\n )\n}\n\nfunction FeaturedBadge() {\n return (\n <span\n data-slot=\"masonry-badge\"\n className=\"absolute top-2 right-2 z-10 flex size-5 items-center justify-center pointer-events-none\"\n role=\"img\"\n aria-label=\"Featured\"\n >\n <StarIcon width={10} height={10} fill=\"currentColor\" aria-hidden />\n </span>\n )\n}\n\nfunction MasonryItem({\n className,\n span,\n children,\n ...props\n}: MasonryItemProps) {\n const isSpanned = span != null && span > 1\n const getStaggerIndex = React.useContext(MasonryStaggerContext)\n const prefersReducedMotion = useReducedMotion()\n\n // Capture stagger index once on mount — useState initializer runs exactly once\n const [staggerDelay] = React.useState(() =>\n getStaggerIndex ? getStaggerIndex() * STAGGER_STEP : 0,\n )\n\n // Reduced motion: render items in their final resting state with no\n // entrance spring, blur, or stagger — they appear immediately and exit\n // with a plain fade. Same DOM/output as the settled animated state.\n return (\n <motion.div\n data-slot=\"masonry-item\"\n data-span={isSpanned ? span : undefined}\n className={cn(\"relative\", className)}\n initial={\n prefersReducedMotion\n ? { opacity: 1, y: 0, filter: \"blur(0px)\" }\n : {\n opacity: 0,\n y: 10,\n filter: \"blur(8px)\",\n }\n }\n animate={{\n opacity: 1,\n y: 0,\n filter: \"blur(0px)\"\n }}\n transition={\n prefersReducedMotion\n ? { duration: 0 }\n : {\n type: \"spring\",\n stiffness: 100,\n damping: 10,\n delay: staggerDelay,\n }\n }\n exit={\n prefersReducedMotion\n ? { opacity: 0 }\n : {\n opacity: 0,\n scale: 1.2,\n filter: \"blur(8px)\",\n }\n }\n {...props}\n >\n {children}\n {isSpanned && <FeaturedBadge />}\n </motion.div>\n )\n}\n\nexport { Masonry, MasonryItem }\n"],"mappings":"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"}
|
package/dist/styles.css
CHANGED
|
@@ -640,6 +640,13 @@
|
|
|
640
640
|
--offset-sm: 0px; --offset-md: 0px; --offset-lg: 0px;
|
|
641
641
|
--stagger-sm: 0ms; --stagger-md: 0ms; --stagger-lg: 0ms;
|
|
642
642
|
}
|
|
643
|
+
|
|
644
|
+
/* Token-driven recipes collapse via the vars above; a standalone keyframe
|
|
645
|
+
* utility that doesn't use those tokens must be stopped explicitly. (The
|
|
646
|
+
* `animate-spin` loader is intentionally left running — it signals activity.) */
|
|
647
|
+
.animate-caret-blink {
|
|
648
|
+
animation: none;
|
|
649
|
+
}
|
|
643
650
|
}
|
|
644
651
|
|
|
645
652
|
/* ---------------------------------------------------------------------------
|
package/dist/toast.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import * as _$_base_ui_react0 from "@base-ui/react";
|
|
|
7
7
|
//#region src/toast.d.ts
|
|
8
8
|
declare const toastRootClass = "bg-elevated text-contrast ring-contrast/10 rounded-md p-4 text-sm shadow-md ring-1 outline-none select-none";
|
|
9
9
|
declare const toastIconVariants: (props?: ({
|
|
10
|
-
type?: "success" | "
|
|
10
|
+
type?: "success" | "loading" | "info" | "warning" | "error" | null | undefined;
|
|
11
11
|
} & _$class_variance_authority_types0.ClassProp) | undefined) => string;
|
|
12
12
|
declare const toastViewportVariants: (props?: ({
|
|
13
13
|
position?: "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right" | null | undefined;
|
package/dist/toast.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast.d.ts","names":[],"sources":["../src/toast.tsx"],"mappings":";;;;;;;cAoBM,cAAA;AAAA,cAGA,iBAAA,GAAiB,KAAA;;IAatB,iCAAA,CAAA,SAAA;AAAA,cAEK,qBAAA,GAAqB,KAAA;;IAiB1B,iCAAA,CAAA,SAAA;AAAA,KAMI,SAAA;AAAA,KAEA,YAAA;EACH,KAAA;EACA,WAAA;EACA,IAAA,GAAO,SAAA;EACP,OAAA;EACA,QAAA;EACA,WAAA,GAAc,KAAA,CAAM,wBAAA;EACpB,OAAA;EACA,QAAA;AAAA;AAAA,KAGG,eAAA;AAAA,KAQA,YAAA;EACH,QAAA,GAAW,eAAA;EACX,KAAA;EACA,OAAA;AAAA;AAAA,KAGG,UAAA,GAAa,KAAA,CAAM,cAAA,QAAsB,OAAA,CAAe,IAAA;AAAA,KACxD,eAAA,GAAkB,KAAA,CAAM,cAAA,QAAsB,OAAA,CAAe,KAAA;AAAA,KAC7D,qBAAA,GAAwB,KAAA,CAAM,cAAA,QAAsB,OAAA,CAAe,WAAA;AAAA,KACnE,gBAAA,GAAmB,KAAA,CAAM,cAAA,QAAsB,OAAA,CAAe,MAAA;AAAA,KAC9D,eAAA,GAAkB,KAAA,CAAM,cAAA,QAAsB,OAAA,CAAe,KAAA;AAAA,cAM5D,YAAA,EAAkD,iBAAA,CAAtC,YAAA;AAAA,cAEZ,KAAA,IAAK,cAAA,WACiB,YAAA;2BAQD,OAAA,GAAY,IAAA,CAAK,YAAA;yBAGnB,OAAA,GAAY,IAAA,CAAK,YAAA;2BAGf,OAAA,GAAY,IAAA,CAAK,YAAA;wBAGpB,OAAA,GAAY,IAAA,CAAK,YAAA;2BAGd,OAAA,GAAY,IAAA,CAAK,YAAA;;uBAUrB,OAAA,EAAW,OAAA,CAAQ,YAAA;;;iBA2BjC,SAAA,CAAA;EAAY;AAAA;EAAU,IAAA,GAAO,SAAA;AAAA,IAAW,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,iBAiBxC,KAAA,CAAA;EAAQ,SAAA;EAAA,GAAc;AAAA,GAAS,UAAA,GAAU,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,iBAoBzC,UAAA,CAAA;EAAa,SAAA;EAAA,GAAc;AAAA,GAAS,eAAA,GAAe,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,iBAUnD,gBAAA,CAAA;EAAmB,SAAA;EAAA,GAAc;AAAA,GAAS,qBAAA,GAAqB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,iBAU/D,WAAA,CAAA;EAAc,SAAA;EAAA,GAAc;AAAA,GAAS,gBAAA,GAAgB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,iBAarD,UAAA,CAAA;EAAa,SAAA;EAAW,QAAA;EAAA,GAAa;AAAA,GAAS,eAAA,GAAe,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"toast.d.ts","names":[],"sources":["../src/toast.tsx"],"mappings":";;;;;;;cAoBM,cAAA;AAAA,cAGA,iBAAA,GAAiB,KAAA;;IAatB,iCAAA,CAAA,SAAA;AAAA,cAEK,qBAAA,GAAqB,KAAA;;IAiB1B,iCAAA,CAAA,SAAA;AAAA,KAMI,SAAA;AAAA,KAEA,YAAA;EACH,KAAA;EACA,WAAA;EACA,IAAA,GAAO,SAAA;EACP,OAAA;EACA,QAAA;EACA,WAAA,GAAc,KAAA,CAAM,wBAAA;EACpB,OAAA;EACA,QAAA;AAAA;AAAA,KAGG,eAAA;AAAA,KAQA,YAAA;EACH,QAAA,GAAW,eAAA;EACX,KAAA;EACA,OAAA;AAAA;AAAA,KAGG,UAAA,GAAa,KAAA,CAAM,cAAA,QAAsB,OAAA,CAAe,IAAA;AAAA,KACxD,eAAA,GAAkB,KAAA,CAAM,cAAA,QAAsB,OAAA,CAAe,KAAA;AAAA,KAC7D,qBAAA,GAAwB,KAAA,CAAM,cAAA,QAAsB,OAAA,CAAe,WAAA;AAAA,KACnE,gBAAA,GAAmB,KAAA,CAAM,cAAA,QAAsB,OAAA,CAAe,MAAA;AAAA,KAC9D,eAAA,GAAkB,KAAA,CAAM,cAAA,QAAsB,OAAA,CAAe,KAAA;AAAA,cAM5D,YAAA,EAAkD,iBAAA,CAAtC,YAAA;AAAA,cAEZ,KAAA,IAAK,cAAA,WACiB,YAAA;2BAQD,OAAA,GAAY,IAAA,CAAK,YAAA;yBAGnB,OAAA,GAAY,IAAA,CAAK,YAAA;2BAGf,OAAA,GAAY,IAAA,CAAK,YAAA;wBAGpB,OAAA,GAAY,IAAA,CAAK,YAAA;2BAGd,OAAA,GAAY,IAAA,CAAK,YAAA;;uBAUrB,OAAA,EAAW,OAAA,CAAQ,YAAA;;;iBA2BjC,SAAA,CAAA;EAAY;AAAA;EAAU,IAAA,GAAO,SAAA;AAAA,IAAW,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,iBAiBxC,KAAA,CAAA;EAAQ,SAAA;EAAA,GAAc;AAAA,GAAS,UAAA,GAAU,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,iBAoBzC,UAAA,CAAA;EAAa,SAAA;EAAA,GAAc;AAAA,GAAS,eAAA,GAAe,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,iBAUnD,gBAAA,CAAA;EAAmB,SAAA;EAAA,GAAc;AAAA,GAAS,qBAAA,GAAqB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,iBAU/D,WAAA,CAAA;EAAc,SAAA;EAAA,GAAc;AAAA,GAAS,gBAAA,GAAgB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,iBAarD,UAAA,CAAA;EAAa,SAAA;EAAW,QAAA;EAAA,GAAa;AAAA,GAAS,eAAA,GAAe,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,iBA6K7D,OAAA,CAAA;EAAU,QAAA;EAAU,KAAA;EAAO;AAAA,GAAW,YAAA,GAAY,oBAAA,CAAA,GAAA,CAAA,OAAA"}
|
package/dist/toast.js
CHANGED
|
@@ -148,7 +148,8 @@ const STACKING_BASE = [
|
|
|
148
148
|
"data-[ending-style]:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))]",
|
|
149
149
|
"data-[expanded]:data-[ending-style]:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))]",
|
|
150
150
|
"data-[limited]:opacity-0",
|
|
151
|
-
"[transition:transform_0.5s_cubic-bezier(0.22,1,0.36,1),opacity_0.5s,filter_0.5s,height_0.15s]"
|
|
151
|
+
"[transition:transform_0.5s_cubic-bezier(0.22,1,0.36,1),opacity_0.5s,filter_0.5s,height_0.15s]",
|
|
152
|
+
"motion-reduce:[transition:none]"
|
|
152
153
|
].join(" ");
|
|
153
154
|
const STACKING_TOP = [
|
|
154
155
|
"[--toast-stack-offset-y:calc(var(--toast-offset-y)+calc(var(--toast-index)*var(--toast-gap))+var(--toast-swipe-movement-y))]",
|
package/dist/toast.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast.js","names":["ToastPrimitive"],"sources":["../src/toast.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { Toast as ToastPrimitive } from \"@base-ui/react/toast\"\nimport { cva } from \"class-variance-authority\"\n\nimport { cn } from \"./lib/utils\"\nimport {\n CloseIcon,\n SuccessCircleIcon,\n InfoCircleIcon,\n AlertTriangleIcon,\n ErrorCircleIcon,\n LoaderIcon,\n} from \"./lib/internal-icons\"\n\n// ---------------------------------------------------------------------------\n// Styles\n// ---------------------------------------------------------------------------\n\nconst toastRootClass =\n \"bg-elevated text-contrast ring-contrast/10 rounded-md p-4 text-sm shadow-md ring-1 outline-none select-none\"\n\nconst toastIconVariants = cva(\n \"mt-0.5 size-4 shrink-0\",\n {\n variants: {\n type: {\n loading: \"text-muted animate-spin\",\n success: \"text-success\",\n info: \"text-info\",\n warning: \"text-warning\",\n error: \"text-destructive\",\n },\n },\n }\n)\n\nconst toastViewportVariants = cva(\n \"fixed z-[100] flex w-full outline-none sm:max-w-sm\",\n {\n variants: {\n position: {\n \"top-left\": \"top-4 left-4\",\n \"top-center\": \"top-4 right-0 left-0 mx-auto\",\n \"top-right\": \"top-4 right-4\",\n \"bottom-left\": \"bottom-4 left-4\",\n \"bottom-center\": \"bottom-4 right-0 left-0 mx-auto\",\n \"bottom-right\": \"bottom-4 right-4\",\n },\n },\n defaultVariants: {\n position: \"bottom-right\",\n },\n },\n)\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype ToastType = \"loading\" | \"success\" | \"info\" | \"warning\" | \"error\"\n\ntype ToastOptions = {\n title?: string\n description?: string\n type?: ToastType\n timeout?: number\n priority?: \"low\" | \"high\"\n actionProps?: React.ComponentPropsWithoutRef<\"button\">\n onClose?: () => void\n onRemove?: () => void\n}\n\ntype ToasterPosition =\n | \"top-left\"\n | \"top-center\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-center\"\n | \"bottom-right\"\n\ntype ToasterProps = {\n position?: ToasterPosition\n limit?: number\n timeout?: number\n}\n\ntype ToastProps = React.ComponentProps<typeof ToastPrimitive.Root>\ntype ToastTitleProps = React.ComponentProps<typeof ToastPrimitive.Title>\ntype ToastDescriptionProps = React.ComponentProps<typeof ToastPrimitive.Description>\ntype ToastActionProps = React.ComponentProps<typeof ToastPrimitive.Action>\ntype ToastCloseProps = React.ComponentProps<typeof ToastPrimitive.Close>\n\n// ---------------------------------------------------------------------------\n// Toast manager API\n// ---------------------------------------------------------------------------\n\nconst toastManager = ToastPrimitive.createToastManager()\n\nconst toast = Object.assign(\n (titleOrOptions: string | ToastOptions) => {\n const options =\n typeof titleOrOptions === \"string\"\n ? { title: titleOrOptions }\n : titleOrOptions\n return toastManager.add(options)\n },\n {\n success: (title: string, options?: Omit<ToastOptions, \"type\" | \"title\">) =>\n toastManager.add({ ...options, type: \"success\", title }),\n\n error: (title: string, options?: Omit<ToastOptions, \"type\" | \"title\">) =>\n toastManager.add({ ...options, type: \"error\", title }),\n\n warning: (title: string, options?: Omit<ToastOptions, \"type\" | \"title\">) =>\n toastManager.add({ ...options, type: \"warning\", title }),\n\n info: (title: string, options?: Omit<ToastOptions, \"type\" | \"title\">) =>\n toastManager.add({ ...options, type: \"info\", title }),\n\n loading: (title: string, options?: Omit<ToastOptions, \"type\" | \"title\">) =>\n toastManager.add({\n ...options,\n type: \"loading\",\n title,\n timeout: options?.timeout ?? 0,\n }),\n\n dismiss: (id: string) => toastManager.close(id),\n\n update: (id: string, updates: Partial<ToastOptions>) =>\n toastManager.update(id, updates),\n\n promise: toastManager.promise.bind(toastManager),\n },\n)\n\n// ---------------------------------------------------------------------------\n// Icons\n// ---------------------------------------------------------------------------\n\nconst iconMap = {\n loading: LoaderIcon,\n success: SuccessCircleIcon,\n info: InfoCircleIcon,\n warning: AlertTriangleIcon,\n error: ErrorCircleIcon,\n} as const\n\nconst toastTypeLabels: Record<ToastType, string> = {\n loading: \"Loading\",\n success: \"Success\",\n info: \"Information\",\n warning: \"Warning\",\n error: \"Error\",\n}\n\nfunction ToastIcon({ type }: { type?: ToastType }) {\n if (!type) return null\n const Icon = iconMap[type]\n return (\n <>\n <Icon className={cn(toastIconVariants({ type }))} />\n {/* The icon is decorative (color/shape); announce the type in words so it\n * isn't conveyed by color alone. */}\n <span className=\"sr-only\">{toastTypeLabels[type]}</span>\n </>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Toast primitives\n// ---------------------------------------------------------------------------\n\nfunction Toast({ className, ...props }: ToastProps) {\n return (\n <ToastPrimitive.Root\n data-slot=\"toast\"\n className={cn(toastRootClass, className)}\n {...props}\n />\n )\n}\n\nfunction ToastBody({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"toast-body\"\n className={cn(\"flex flex-1 flex-col gap-1\", className)}\n {...props}\n />\n )\n}\n\nfunction ToastTitle({ className, ...props }: ToastTitleProps) {\n return (\n <ToastPrimitive.Title\n data-slot=\"toast-title\"\n className={cn(\"text-sm font-medium\", className)}\n {...props}\n />\n )\n}\n\nfunction ToastDescription({ className, ...props }: ToastDescriptionProps) {\n return (\n <ToastPrimitive.Description\n data-slot=\"toast-description\"\n className={cn(\"text-muted text-sm\", className)}\n {...props}\n />\n )\n}\n\nfunction ToastAction({ className, ...props }: ToastActionProps) {\n return (\n <ToastPrimitive.Action\n data-slot=\"toast-action\"\n className={cn(\n \"hover:bg-secondary inline-flex h-7 items-center rounded-md border border-line px-2.5 text-xs font-medium transition-colors outline-none focus-visible:border-focus focus-visible:ring-focus/50 focus-visible:ring-3\",\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction ToastClose({ className, children, ...props }: ToastCloseProps) {\n return (\n <ToastPrimitive.Close\n data-slot=\"toast-close\"\n className={cn(\n \"text-muted hover:text-contrast shrink-0 rounded-md p-0.5 transition-colors outline-none focus-visible:ring-focus/50 focus-visible:ring-3\",\n className,\n )}\n {...props}\n >\n {children ?? (\n <>\n <CloseIcon className=\"size-4\" />\n <span className=\"sr-only\">Close</span>\n </>\n )}\n </ToastPrimitive.Close>\n )\n}\n\n// ---------------------------------------------------------------------------\n// 3D stacking\n// ---------------------------------------------------------------------------\n\n// Shared classes for both top and bottom positions\nconst STACKING_BASE = [\n // CSS custom properties\n \"[--toast-gap:0.5rem]\",\n \"[--toast-peek:0.5rem]\",\n \"[--toast-scale:calc(max(0,1-(var(--toast-index)*0.05)))]\",\n \"[--toast-shrink:calc(1-var(--toast-scale))]\",\n \"[--toast-stack-height:var(--toast-frontmost-height,var(--toast-height))]\",\n // Layout\n \"absolute w-full\",\n \"[z-index:calc(1000-var(--toast-index))]\",\n \"h-[var(--toast-stack-height)] data-[expanded]:h-[var(--toast-height)]\",\n // Depth blur — subtle defocus on behind toasts, cleared on expand\n \"[filter:blur(calc(var(--toast-index)*0.4px))]\",\n \"data-[expanded]:[filter:none]\",\n // Expanded (hover)\n \"data-[expanded]:[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-stack-offset-y)))]\",\n // Exit — shared\n \"data-[ending-style]:opacity-0\",\n \"data-[ending-style]:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))]\",\n \"data-[expanded]:data-[ending-style]:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))]\",\n \"data-[ending-style]:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--toast-stack-offset-y))]\",\n \"data-[expanded]:data-[ending-style]:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--toast-stack-offset-y))]\",\n \"data-[ending-style]:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))]\",\n \"data-[expanded]:data-[ending-style]:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))]\",\n \"data-[limited]:opacity-0\",\n \"[transition:transform_0.5s_cubic-bezier(0.22,1,0.36,1),opacity_0.5s,filter_0.5s,height_0.15s]\",\n].join(\" \")\n\n// Direction-specific classes — sign flips for anchor, offset, transform, enter/exit\nconst STACKING_TOP = [\n \"[--toast-stack-offset-y:calc(var(--toast-offset-y)+calc(var(--toast-index)*var(--toast-gap))+var(--toast-swipe-movement-y))]\",\n \"top-0 origin-top\",\n \"[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)+(var(--toast-index)*var(--toast-peek))+(var(--toast-shrink)*var(--toast-stack-height))))_scale(var(--toast-scale))]\",\n \"after:absolute after:bottom-full after:left-0 after:h-[calc(var(--toast-gap)+1px)] after:w-full after:content-['']\",\n \"data-[starting-style]:[transform:translateY(-150%)]\",\n \"[&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(-150%)]\",\n].join(\" \")\n\nconst STACKING_BOTTOM = [\n \"[--toast-stack-offset-y:calc(var(--toast-offset-y)*-1+calc(var(--toast-index)*var(--toast-gap)*-1)+var(--toast-swipe-movement-y))]\",\n \"bottom-0 origin-bottom\",\n \"[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--toast-peek))-(var(--toast-shrink)*var(--toast-stack-height))))_scale(var(--toast-scale))]\",\n \"after:absolute after:top-full after:left-0 after:h-[calc(var(--toast-gap)+1px)] after:w-full after:content-['']\",\n \"data-[starting-style]:[transform:translateY(150%)]\",\n \"[&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(150%)]\",\n].join(\" \")\n\nconst STACKING_TOP_CLASS = `${STACKING_BASE} ${STACKING_TOP}`\nconst STACKING_BOTTOM_CLASS = `${STACKING_BASE} ${STACKING_BOTTOM}`\n\n// ---------------------------------------------------------------------------\n// Toaster\n// ---------------------------------------------------------------------------\n\nfunction ToasterContent({ position = \"bottom-right\" }: Pick<ToasterProps, \"position\">) {\n const { toasts } = ToastPrimitive.useToastManager()\n const isTop = position.startsWith(\"top\")\n\n return (\n <ToastPrimitive.Portal>\n <ToastPrimitive.Viewport\n data-slot=\"toast-viewport\"\n className={cn(toastViewportVariants({ position }))}\n >\n {toasts.map((t) => (\n <Toast\n key={t.id}\n toast={t}\n swipeDirection={isTop ? [\"up\", \"right\"] : [\"down\", \"right\"]}\n className={isTop ? STACKING_TOP_CLASS : STACKING_BOTTOM_CLASS}\n >\n <ToastPrimitive.Content\n data-slot=\"toast-content\"\n className=\"flex items-start gap-3 overflow-hidden transition-opacity duration-200 data-[behind]:pointer-events-none data-[behind]:opacity-0 data-[expanded]:pointer-events-auto data-[expanded]:opacity-100\"\n >\n <ToastIcon type={t.type as ToastType | undefined} />\n\n <ToastBody>\n {t.title && <ToastTitle>{t.title}</ToastTitle>}\n {t.description && (\n <ToastDescription>{t.description}</ToastDescription>\n )}\n {t.actionProps && (\n <div className=\"mt-1.5\">\n <ToastAction {...t.actionProps} />\n </div>\n )}\n </ToastBody>\n\n <ToastClose />\n </ToastPrimitive.Content>\n </Toast>\n ))}\n </ToastPrimitive.Viewport>\n </ToastPrimitive.Portal>\n )\n}\n\n// The toast manager is a module-level singleton, so every mounted <Toaster/>\n// renders the SAME shared toast list — duplicating every toast. This registry\n// elects a single primary Toaster (the first mounted); the rest render null.\n// Guards against a consumer accidentally rendering two, and against Storybook\n// autodocs rendering several stories — each with a Toaster — on one page.\nconst mountedToasters: symbol[] = []\nconst toasterListeners = new Set<() => void>()\n\nfunction notifyToasters() {\n for (const listener of toasterListeners) {\n listener()\n }\n}\n\nfunction useIsPrimaryToaster() {\n const keyRef = React.useRef<symbol | null>(null)\n if (keyRef.current === null) {\n keyRef.current = Symbol(\"toaster\")\n }\n const key = keyRef.current\n\n const isPrimary = React.useSyncExternalStore(\n React.useCallback((onChange: () => void) => {\n toasterListeners.add(onChange)\n return () => {\n toasterListeners.delete(onChange)\n }\n }, []),\n () => mountedToasters[0] === key,\n () => false,\n )\n\n React.useEffect(() => {\n mountedToasters.push(key)\n notifyToasters()\n return () => {\n const index = mountedToasters.indexOf(key)\n if (index !== -1) {\n mountedToasters.splice(index, 1)\n }\n notifyToasters()\n }\n }, [key])\n\n return isPrimary\n}\n\nfunction Toaster({ position, limit, timeout }: ToasterProps) {\n const isPrimary = useIsPrimaryToaster()\n if (!isPrimary) {\n return null\n }\n return (\n <ToastPrimitive.Provider\n toastManager={toastManager}\n limit={limit}\n timeout={timeout}\n >\n <ToasterContent position={position} />\n </ToastPrimitive.Provider>\n )\n}\n\nexport {\n toastRootClass,\n toastIconVariants,\n toastViewportVariants,\n toastManager,\n toast,\n Toaster,\n Toast,\n ToastIcon,\n ToastTitle,\n ToastDescription,\n ToastAction,\n ToastClose,\n}\n\nexport type {\n ToastType,\n ToastOptions,\n ToasterPosition,\n ToasterProps\n}"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"toast.js","names":["ToastPrimitive"],"sources":["../src/toast.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { Toast as ToastPrimitive } from \"@base-ui/react/toast\"\nimport { cva } from \"class-variance-authority\"\n\nimport { cn } from \"./lib/utils\"\nimport {\n CloseIcon,\n SuccessCircleIcon,\n InfoCircleIcon,\n AlertTriangleIcon,\n ErrorCircleIcon,\n LoaderIcon,\n} from \"./lib/internal-icons\"\n\n// ---------------------------------------------------------------------------\n// Styles\n// ---------------------------------------------------------------------------\n\nconst toastRootClass =\n \"bg-elevated text-contrast ring-contrast/10 rounded-md p-4 text-sm shadow-md ring-1 outline-none select-none\"\n\nconst toastIconVariants = cva(\n \"mt-0.5 size-4 shrink-0\",\n {\n variants: {\n type: {\n loading: \"text-muted animate-spin\",\n success: \"text-success\",\n info: \"text-info\",\n warning: \"text-warning\",\n error: \"text-destructive\",\n },\n },\n }\n)\n\nconst toastViewportVariants = cva(\n \"fixed z-[100] flex w-full outline-none sm:max-w-sm\",\n {\n variants: {\n position: {\n \"top-left\": \"top-4 left-4\",\n \"top-center\": \"top-4 right-0 left-0 mx-auto\",\n \"top-right\": \"top-4 right-4\",\n \"bottom-left\": \"bottom-4 left-4\",\n \"bottom-center\": \"bottom-4 right-0 left-0 mx-auto\",\n \"bottom-right\": \"bottom-4 right-4\",\n },\n },\n defaultVariants: {\n position: \"bottom-right\",\n },\n },\n)\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype ToastType = \"loading\" | \"success\" | \"info\" | \"warning\" | \"error\"\n\ntype ToastOptions = {\n title?: string\n description?: string\n type?: ToastType\n timeout?: number\n priority?: \"low\" | \"high\"\n actionProps?: React.ComponentPropsWithoutRef<\"button\">\n onClose?: () => void\n onRemove?: () => void\n}\n\ntype ToasterPosition =\n | \"top-left\"\n | \"top-center\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-center\"\n | \"bottom-right\"\n\ntype ToasterProps = {\n position?: ToasterPosition\n limit?: number\n timeout?: number\n}\n\ntype ToastProps = React.ComponentProps<typeof ToastPrimitive.Root>\ntype ToastTitleProps = React.ComponentProps<typeof ToastPrimitive.Title>\ntype ToastDescriptionProps = React.ComponentProps<typeof ToastPrimitive.Description>\ntype ToastActionProps = React.ComponentProps<typeof ToastPrimitive.Action>\ntype ToastCloseProps = React.ComponentProps<typeof ToastPrimitive.Close>\n\n// ---------------------------------------------------------------------------\n// Toast manager API\n// ---------------------------------------------------------------------------\n\nconst toastManager = ToastPrimitive.createToastManager()\n\nconst toast = Object.assign(\n (titleOrOptions: string | ToastOptions) => {\n const options =\n typeof titleOrOptions === \"string\"\n ? { title: titleOrOptions }\n : titleOrOptions\n return toastManager.add(options)\n },\n {\n success: (title: string, options?: Omit<ToastOptions, \"type\" | \"title\">) =>\n toastManager.add({ ...options, type: \"success\", title }),\n\n error: (title: string, options?: Omit<ToastOptions, \"type\" | \"title\">) =>\n toastManager.add({ ...options, type: \"error\", title }),\n\n warning: (title: string, options?: Omit<ToastOptions, \"type\" | \"title\">) =>\n toastManager.add({ ...options, type: \"warning\", title }),\n\n info: (title: string, options?: Omit<ToastOptions, \"type\" | \"title\">) =>\n toastManager.add({ ...options, type: \"info\", title }),\n\n loading: (title: string, options?: Omit<ToastOptions, \"type\" | \"title\">) =>\n toastManager.add({\n ...options,\n type: \"loading\",\n title,\n timeout: options?.timeout ?? 0,\n }),\n\n dismiss: (id: string) => toastManager.close(id),\n\n update: (id: string, updates: Partial<ToastOptions>) =>\n toastManager.update(id, updates),\n\n promise: toastManager.promise.bind(toastManager),\n },\n)\n\n// ---------------------------------------------------------------------------\n// Icons\n// ---------------------------------------------------------------------------\n\nconst iconMap = {\n loading: LoaderIcon,\n success: SuccessCircleIcon,\n info: InfoCircleIcon,\n warning: AlertTriangleIcon,\n error: ErrorCircleIcon,\n} as const\n\nconst toastTypeLabels: Record<ToastType, string> = {\n loading: \"Loading\",\n success: \"Success\",\n info: \"Information\",\n warning: \"Warning\",\n error: \"Error\",\n}\n\nfunction ToastIcon({ type }: { type?: ToastType }) {\n if (!type) return null\n const Icon = iconMap[type]\n return (\n <>\n <Icon className={cn(toastIconVariants({ type }))} />\n {/* The icon is decorative (color/shape); announce the type in words so it\n * isn't conveyed by color alone. */}\n <span className=\"sr-only\">{toastTypeLabels[type]}</span>\n </>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Toast primitives\n// ---------------------------------------------------------------------------\n\nfunction Toast({ className, ...props }: ToastProps) {\n return (\n <ToastPrimitive.Root\n data-slot=\"toast\"\n className={cn(toastRootClass, className)}\n {...props}\n />\n )\n}\n\nfunction ToastBody({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"toast-body\"\n className={cn(\"flex flex-1 flex-col gap-1\", className)}\n {...props}\n />\n )\n}\n\nfunction ToastTitle({ className, ...props }: ToastTitleProps) {\n return (\n <ToastPrimitive.Title\n data-slot=\"toast-title\"\n className={cn(\"text-sm font-medium\", className)}\n {...props}\n />\n )\n}\n\nfunction ToastDescription({ className, ...props }: ToastDescriptionProps) {\n return (\n <ToastPrimitive.Description\n data-slot=\"toast-description\"\n className={cn(\"text-muted text-sm\", className)}\n {...props}\n />\n )\n}\n\nfunction ToastAction({ className, ...props }: ToastActionProps) {\n return (\n <ToastPrimitive.Action\n data-slot=\"toast-action\"\n className={cn(\n \"hover:bg-secondary inline-flex h-7 items-center rounded-md border border-line px-2.5 text-xs font-medium transition-colors outline-none focus-visible:border-focus focus-visible:ring-focus/50 focus-visible:ring-3\",\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction ToastClose({ className, children, ...props }: ToastCloseProps) {\n return (\n <ToastPrimitive.Close\n data-slot=\"toast-close\"\n className={cn(\n \"text-muted hover:text-contrast shrink-0 rounded-md p-0.5 transition-colors outline-none focus-visible:ring-focus/50 focus-visible:ring-3\",\n className,\n )}\n {...props}\n >\n {children ?? (\n <>\n <CloseIcon className=\"size-4\" />\n <span className=\"sr-only\">Close</span>\n </>\n )}\n </ToastPrimitive.Close>\n )\n}\n\n// ---------------------------------------------------------------------------\n// 3D stacking\n// ---------------------------------------------------------------------------\n\n// Shared classes for both top and bottom positions\nconst STACKING_BASE = [\n // CSS custom properties\n \"[--toast-gap:0.5rem]\",\n \"[--toast-peek:0.5rem]\",\n \"[--toast-scale:calc(max(0,1-(var(--toast-index)*0.05)))]\",\n \"[--toast-shrink:calc(1-var(--toast-scale))]\",\n \"[--toast-stack-height:var(--toast-frontmost-height,var(--toast-height))]\",\n // Layout\n \"absolute w-full\",\n \"[z-index:calc(1000-var(--toast-index))]\",\n \"h-[var(--toast-stack-height)] data-[expanded]:h-[var(--toast-height)]\",\n // Depth blur — subtle defocus on behind toasts, cleared on expand\n \"[filter:blur(calc(var(--toast-index)*0.4px))]\",\n \"data-[expanded]:[filter:none]\",\n // Expanded (hover)\n \"data-[expanded]:[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-stack-offset-y)))]\",\n // Exit — shared\n \"data-[ending-style]:opacity-0\",\n \"data-[ending-style]:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))]\",\n \"data-[expanded]:data-[ending-style]:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))]\",\n \"data-[ending-style]:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--toast-stack-offset-y))]\",\n \"data-[expanded]:data-[ending-style]:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--toast-stack-offset-y))]\",\n \"data-[ending-style]:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))]\",\n \"data-[expanded]:data-[ending-style]:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))]\",\n \"data-[limited]:opacity-0\",\n \"[transition:transform_0.5s_cubic-bezier(0.22,1,0.36,1),opacity_0.5s,filter_0.5s,height_0.15s]\",\n // Collapse the stacking motion under reduced motion — toasts appear and\n // restack instantly instead of sliding/scaling.\n \"motion-reduce:[transition:none]\",\n].join(\" \")\n\n// Direction-specific classes — sign flips for anchor, offset, transform, enter/exit\nconst STACKING_TOP = [\n \"[--toast-stack-offset-y:calc(var(--toast-offset-y)+calc(var(--toast-index)*var(--toast-gap))+var(--toast-swipe-movement-y))]\",\n \"top-0 origin-top\",\n \"[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)+(var(--toast-index)*var(--toast-peek))+(var(--toast-shrink)*var(--toast-stack-height))))_scale(var(--toast-scale))]\",\n \"after:absolute after:bottom-full after:left-0 after:h-[calc(var(--toast-gap)+1px)] after:w-full after:content-['']\",\n \"data-[starting-style]:[transform:translateY(-150%)]\",\n \"[&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(-150%)]\",\n].join(\" \")\n\nconst STACKING_BOTTOM = [\n \"[--toast-stack-offset-y:calc(var(--toast-offset-y)*-1+calc(var(--toast-index)*var(--toast-gap)*-1)+var(--toast-swipe-movement-y))]\",\n \"bottom-0 origin-bottom\",\n \"[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--toast-peek))-(var(--toast-shrink)*var(--toast-stack-height))))_scale(var(--toast-scale))]\",\n \"after:absolute after:top-full after:left-0 after:h-[calc(var(--toast-gap)+1px)] after:w-full after:content-['']\",\n \"data-[starting-style]:[transform:translateY(150%)]\",\n \"[&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(150%)]\",\n].join(\" \")\n\nconst STACKING_TOP_CLASS = `${STACKING_BASE} ${STACKING_TOP}`\nconst STACKING_BOTTOM_CLASS = `${STACKING_BASE} ${STACKING_BOTTOM}`\n\n// ---------------------------------------------------------------------------\n// Toaster\n// ---------------------------------------------------------------------------\n\nfunction ToasterContent({ position = \"bottom-right\" }: Pick<ToasterProps, \"position\">) {\n const { toasts } = ToastPrimitive.useToastManager()\n const isTop = position.startsWith(\"top\")\n\n return (\n <ToastPrimitive.Portal>\n <ToastPrimitive.Viewport\n data-slot=\"toast-viewport\"\n className={cn(toastViewportVariants({ position }))}\n >\n {toasts.map((t) => (\n <Toast\n key={t.id}\n toast={t}\n swipeDirection={isTop ? [\"up\", \"right\"] : [\"down\", \"right\"]}\n className={isTop ? STACKING_TOP_CLASS : STACKING_BOTTOM_CLASS}\n >\n <ToastPrimitive.Content\n data-slot=\"toast-content\"\n className=\"flex items-start gap-3 overflow-hidden transition-opacity duration-200 data-[behind]:pointer-events-none data-[behind]:opacity-0 data-[expanded]:pointer-events-auto data-[expanded]:opacity-100\"\n >\n <ToastIcon type={t.type as ToastType | undefined} />\n\n <ToastBody>\n {t.title && <ToastTitle>{t.title}</ToastTitle>}\n {t.description && (\n <ToastDescription>{t.description}</ToastDescription>\n )}\n {t.actionProps && (\n <div className=\"mt-1.5\">\n <ToastAction {...t.actionProps} />\n </div>\n )}\n </ToastBody>\n\n <ToastClose />\n </ToastPrimitive.Content>\n </Toast>\n ))}\n </ToastPrimitive.Viewport>\n </ToastPrimitive.Portal>\n )\n}\n\n// The toast manager is a module-level singleton, so every mounted <Toaster/>\n// renders the SAME shared toast list — duplicating every toast. This registry\n// elects a single primary Toaster (the first mounted); the rest render null.\n// Guards against a consumer accidentally rendering two, and against Storybook\n// autodocs rendering several stories — each with a Toaster — on one page.\nconst mountedToasters: symbol[] = []\nconst toasterListeners = new Set<() => void>()\n\nfunction notifyToasters() {\n for (const listener of toasterListeners) {\n listener()\n }\n}\n\nfunction useIsPrimaryToaster() {\n const keyRef = React.useRef<symbol | null>(null)\n if (keyRef.current === null) {\n keyRef.current = Symbol(\"toaster\")\n }\n const key = keyRef.current\n\n const isPrimary = React.useSyncExternalStore(\n React.useCallback((onChange: () => void) => {\n toasterListeners.add(onChange)\n return () => {\n toasterListeners.delete(onChange)\n }\n }, []),\n () => mountedToasters[0] === key,\n () => false,\n )\n\n React.useEffect(() => {\n mountedToasters.push(key)\n notifyToasters()\n return () => {\n const index = mountedToasters.indexOf(key)\n if (index !== -1) {\n mountedToasters.splice(index, 1)\n }\n notifyToasters()\n }\n }, [key])\n\n return isPrimary\n}\n\nfunction Toaster({ position, limit, timeout }: ToasterProps) {\n const isPrimary = useIsPrimaryToaster()\n if (!isPrimary) {\n return null\n }\n return (\n <ToastPrimitive.Provider\n toastManager={toastManager}\n limit={limit}\n timeout={timeout}\n >\n <ToasterContent position={position} />\n </ToastPrimitive.Provider>\n )\n}\n\nexport {\n toastRootClass,\n toastIconVariants,\n toastViewportVariants,\n toastManager,\n toast,\n Toaster,\n Toast,\n ToastIcon,\n ToastTitle,\n ToastDescription,\n ToastAction,\n ToastClose,\n}\n\nexport type {\n ToastType,\n ToastOptions,\n ToasterPosition,\n ToasterProps\n}"],"mappings":"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"}
|
package/dist/typewriter.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cloneElement, isValidElement, useEffect, useId, useRef, useState } from "react";
|
|
3
3
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
-
import { useInView } from "motion/react";
|
|
4
|
+
import { useInView, useReducedMotion } from "motion/react";
|
|
5
5
|
//#region src/typewriter.tsx
|
|
6
6
|
function mergeRef(internalRef, externalRef) {
|
|
7
7
|
return (el) => {
|
|
@@ -105,22 +105,24 @@ function Typewriter({ text, children, speed = .04, delay = 0, cursor = true, cur
|
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
function TypingReveal({ text, children, elementRef, id, speed, delay, cursor, cursorChar, shouldAnimate }) {
|
|
108
|
+
const prefersReducedMotion = useReducedMotion();
|
|
108
109
|
const flat = flattenText(text);
|
|
109
110
|
const segments = typeof text === "string" ? [{ text }] : text;
|
|
110
111
|
const [charIndex, setCharIndex] = useState(0);
|
|
111
112
|
const [started, setStarted] = useState(false);
|
|
112
113
|
const [done, setDone] = useState(false);
|
|
113
114
|
useEffect(() => {
|
|
114
|
-
if (!shouldAnimate || started) return;
|
|
115
|
+
if (prefersReducedMotion || !shouldAnimate || started) return;
|
|
115
116
|
const timer = setTimeout(() => setStarted(true), delay * 1e3);
|
|
116
117
|
return () => clearTimeout(timer);
|
|
117
118
|
}, [
|
|
119
|
+
prefersReducedMotion,
|
|
118
120
|
shouldAnimate,
|
|
119
121
|
delay,
|
|
120
122
|
started
|
|
121
123
|
]);
|
|
122
124
|
useEffect(() => {
|
|
123
|
-
if (!started || done) return;
|
|
125
|
+
if (prefersReducedMotion || !started || done) return;
|
|
124
126
|
if (charIndex >= flat.length) {
|
|
125
127
|
setDone(true);
|
|
126
128
|
return;
|
|
@@ -128,6 +130,7 @@ function TypingReveal({ text, children, elementRef, id, speed, delay, cursor, cu
|
|
|
128
130
|
const timer = setTimeout(() => setCharIndex((prev) => prev + 1), speed * 1e3);
|
|
129
131
|
return () => clearTimeout(timer);
|
|
130
132
|
}, [
|
|
133
|
+
prefersReducedMotion,
|
|
131
134
|
started,
|
|
132
135
|
charIndex,
|
|
133
136
|
flat.length,
|
|
@@ -136,8 +139,8 @@ function TypingReveal({ text, children, elementRef, id, speed, delay, cursor, cu
|
|
|
136
139
|
]);
|
|
137
140
|
if (!isValidElement(children)) return children;
|
|
138
141
|
const existingRef = children.props.ref;
|
|
139
|
-
const content = started ? buildSegmentContent(segments, charIndex) : null;
|
|
140
|
-
const showCursor = cursor && started && !done;
|
|
142
|
+
const content = prefersReducedMotion ? buildSegmentContent(segments, flat.length) : started ? buildSegmentContent(segments, charIndex) : null;
|
|
143
|
+
const showCursor = !prefersReducedMotion && cursor && started && !done;
|
|
141
144
|
return cloneElement(children, {
|
|
142
145
|
ref: mergeRef(elementRef, existingRef),
|
|
143
146
|
children: /* @__PURE__ */ jsxs(Fragment, { children: [content, showCursor && /* @__PURE__ */ jsx(BlinkingCursor, {
|
|
@@ -147,29 +150,35 @@ function TypingReveal({ text, children, elementRef, id, speed, delay, cursor, cu
|
|
|
147
150
|
});
|
|
148
151
|
}
|
|
149
152
|
function SmoothReveal({ text, children, elementRef, id, delay, duration, cursor, cursorChar, shouldAnimate }) {
|
|
153
|
+
const prefersReducedMotion = useReducedMotion();
|
|
150
154
|
const segments = typeof text === "string" ? [{ text }] : text;
|
|
151
155
|
const [revealed, setRevealed] = useState(false);
|
|
152
156
|
const [done, setDone] = useState(false);
|
|
153
157
|
useEffect(() => {
|
|
154
|
-
if (!shouldAnimate || revealed) return;
|
|
158
|
+
if (prefersReducedMotion || !shouldAnimate || revealed) return;
|
|
155
159
|
const timer = setTimeout(() => setRevealed(true), delay * 1e3);
|
|
156
160
|
return () => clearTimeout(timer);
|
|
157
161
|
}, [
|
|
162
|
+
prefersReducedMotion,
|
|
158
163
|
shouldAnimate,
|
|
159
164
|
delay,
|
|
160
165
|
revealed
|
|
161
166
|
]);
|
|
162
167
|
useEffect(() => {
|
|
163
|
-
if (!revealed) return;
|
|
168
|
+
if (prefersReducedMotion || !revealed) return;
|
|
164
169
|
const timer = setTimeout(() => setDone(true), duration * 1e3);
|
|
165
170
|
return () => clearTimeout(timer);
|
|
166
|
-
}, [
|
|
171
|
+
}, [
|
|
172
|
+
prefersReducedMotion,
|
|
173
|
+
revealed,
|
|
174
|
+
duration
|
|
175
|
+
]);
|
|
167
176
|
if (!isValidElement(children)) return children;
|
|
168
177
|
const childProps = children.props;
|
|
169
178
|
const existingRef = childProps.ref;
|
|
170
179
|
const existingStyle = childProps.style ?? {};
|
|
171
180
|
const animName = `tw-smooth-${id}`;
|
|
172
|
-
const showCursor = cursor && revealed && !done;
|
|
181
|
+
const showCursor = !prefersReducedMotion && cursor && revealed && !done;
|
|
173
182
|
const fullContent = segments.map((seg, i) => seg.className ? /* @__PURE__ */ jsx("span", {
|
|
174
183
|
className: seg.className,
|
|
175
184
|
children: seg.text
|
|
@@ -180,7 +189,7 @@ function SmoothReveal({ text, children, elementRef, id, delay, duration, cursor,
|
|
|
180
189
|
...existingStyle,
|
|
181
190
|
whiteSpace: "nowrap",
|
|
182
191
|
overflow: "hidden",
|
|
183
|
-
...revealed ? { animation: `${animName} ${duration}s linear forwards` } : { maxWidth: 0 }
|
|
192
|
+
...prefersReducedMotion ? { maxWidth: "100%" } : revealed ? { animation: `${animName} ${duration}s linear forwards` } : { maxWidth: 0 }
|
|
184
193
|
},
|
|
185
194
|
children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
186
195
|
/* @__PURE__ */ jsx("style", { children: `@keyframes ${animName} { from { max-width: 0; } to { max-width: 100%; } }` }),
|
package/dist/typewriter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typewriter.js","names":[],"sources":["../src/typewriter.tsx"],"sourcesContent":["\"use client\"\n\nimport {\n type CSSProperties,\n type ReactElement,\n type Ref,\n cloneElement,\n isValidElement,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\"\nimport { useInView } from \"motion/react\"\n\n// ── Types ────────────────────────────────────────────────────────────\n\ninterface TextSegment {\n text: string\n /** Optional className applied to this segment (e.g., bold, colored) */\n className?: string\n}\n\ninterface TypewriterProps {\n /** Simple string or styled segments for per-word/phrase styling */\n text: string | TextSegment[]\n /** Element to render into. Receives the animated text as children. */\n children: ReactElement\n /** Time per character in seconds. Default: 0.04 */\n speed?: number\n /** Delay before typing starts in seconds. Default: 0 */\n delay?: number\n /** Show a blinking cursor during typing. Default: true */\n cursor?: boolean\n /** Cursor character. Default: '|' */\n cursorChar?: string\n /** Trigger when scrolled into view instead of on mount. Default: false */\n onView?: boolean\n /** Trigger once. Default: true */\n once?: boolean\n /**\n * Reveal mode. Default: 'typing'\n * - 'typing': character-by-character like someone typing\n * - 'smooth': sliding mask reveal (cinematic feel)\n */\n variant?: \"typing\" | \"smooth\"\n /** Duration of the smooth reveal in seconds. Default: 2 */\n smoothDuration?: number\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────\n\nfunction mergeRef(\n internalRef: React.RefObject<HTMLElement | null>,\n externalRef?: Ref<HTMLElement>,\n) {\n return (el: HTMLElement | null) => {\n ;(internalRef as { current: HTMLElement | null }).current = el\n if (typeof externalRef === \"function\") externalRef(el)\n else if (externalRef && typeof externalRef === \"object\") {\n ;(externalRef as { current: HTMLElement | null }).current = el\n }\n }\n}\n\n/** Flatten text prop into a single string for character counting */\nfunction flattenText(text: string | TextSegment[]): string {\n if (typeof text === \"string\") return text\n return text.map((s) => s.text).join(\"\")\n}\n\n/** Build rendered content from segments up to a character index */\nfunction buildSegmentContent(\n segments: TextSegment[],\n charIndex: number,\n): React.ReactNode[] {\n const nodes: React.ReactNode[] = []\n let remaining = charIndex\n\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i]!\n if (remaining <= 0) break\n\n const visibleChars = seg.text.slice(0, remaining)\n remaining -= visibleChars.length\n\n if (seg.className) {\n nodes.push(\n <span key={i} className={seg.className}>\n {visibleChars}\n </span>,\n )\n } else {\n nodes.push(visibleChars)\n }\n }\n\n return nodes\n}\n\n// ── Cursor ───────────────────────────────────────────────────────────\n\nfunction BlinkingCursor({ char, id }: { char: string; id: string }) {\n const name = `tw-blink-${id}`\n return (\n <>\n <span\n aria-hidden\n style={{\n animation: `${name} 0.8s step-end infinite`,\n fontWeight: \"normal\",\n }}\n >\n {char}\n </span>\n <style>{`@keyframes ${name} { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }`}</style>\n </>\n )\n}\n\n// ── Typewriter ───────────────────────────────────────────────────────\n\n/**\n * Text reveal animation with two modes:\n *\n * **typing** (default) — character-by-character like someone typing.\n * **smooth** — cinematic sliding mask reveal.\n *\n * Supports plain strings or styled segments for per-word coloring.\n * Zero wrapper divs — renders into the child element via cloneElement.\n *\n * @example\n * ```tsx\n * // Simple string\n * <Typewriter text=\"Welcome to the future.\">\n * <h1 className=\"text-4xl font-bold\" />\n * </Typewriter>\n *\n * // Per-word styling\n * <Typewriter text={[\n * { text: \"Build on \" },\n * { text: \"Intuition\", className: \"text-primary font-bold\" },\n * ]}>\n * <h1 className=\"text-4xl\" />\n * </Typewriter>\n *\n * // Smooth reveal\n * <Typewriter text=\"Cinematic reveal.\" variant=\"smooth\" smoothDuration={1.5}>\n * <h1 className=\"text-4xl font-bold\" />\n * </Typewriter>\n * ```\n */\nfunction Typewriter({\n text,\n children,\n speed = 0.04,\n delay = 0,\n cursor = true,\n cursorChar = \"|\",\n onView = false,\n once = true,\n variant = \"typing\",\n smoothDuration = 2,\n}: TypewriterProps) {\n const ref = useRef<HTMLElement>(null)\n const id = useId().replace(/:/g, \"\")\n const isInView = useInView(ref, { once, margin: \"-50px\" })\n\n const shouldAnimate = onView ? isInView : true\n\n if (variant === \"smooth\") {\n return (\n <SmoothReveal\n text={text}\n elementRef={ref}\n id={id}\n delay={delay}\n duration={smoothDuration}\n cursor={cursor}\n cursorChar={cursorChar}\n shouldAnimate={shouldAnimate}\n >\n {children}\n </SmoothReveal>\n )\n }\n\n return (\n <TypingReveal\n text={text}\n elementRef={ref}\n id={id}\n speed={speed}\n delay={delay}\n cursor={cursor}\n cursorChar={cursorChar}\n shouldAnimate={shouldAnimate}\n >\n {children}\n </TypingReveal>\n )\n}\n\n// ── Typing Reveal ────────────────────────────────────────────────────\n\nfunction TypingReveal({\n text,\n children,\n elementRef,\n id,\n speed,\n delay,\n cursor,\n cursorChar,\n shouldAnimate,\n}: {\n text: string | TextSegment[]\n children: ReactElement\n elementRef: React.RefObject<HTMLElement | null>\n id: string\n speed: number\n delay: number\n cursor: boolean\n cursorChar: string\n shouldAnimate: boolean\n}) {\n const flat = flattenText(text)\n const segments = typeof text === \"string\" ? [{ text }] : text\n const [charIndex, setCharIndex] = useState(0)\n const [started, setStarted] = useState(false)\n const [done, setDone] = useState(false)\n\n useEffect(() => {\n if (!shouldAnimate || started) return\n const timer = setTimeout(() => setStarted(true), delay * 1000)\n return () => clearTimeout(timer)\n }, [shouldAnimate, delay, started])\n\n useEffect(() => {\n if (!started || done) return\n if (charIndex >= flat.length) {\n setDone(true)\n return\n }\n const timer = setTimeout(() => setCharIndex((prev) => prev + 1), speed * 1000)\n return () => clearTimeout(timer)\n }, [started, charIndex, flat.length, speed, done])\n\n if (!isValidElement(children)) return children\n\n const childProps = children.props as Record<string, unknown>\n const existingRef = (childProps as { ref?: Ref<HTMLElement> }).ref\n\n const content = started ? buildSegmentContent(segments, charIndex) : null\n const showCursor = cursor && started && !done\n\n return cloneElement(children, {\n ref: mergeRef(elementRef, existingRef),\n children: (\n <>\n {content}\n {showCursor && <BlinkingCursor char={cursorChar} id={id} />}\n </>\n ),\n } as Record<string, unknown>)\n}\n\n// ── Smooth Reveal ────────────────────────────────────────────────────\n\nfunction SmoothReveal({\n text,\n children,\n elementRef,\n id,\n delay,\n duration,\n cursor,\n cursorChar,\n shouldAnimate,\n}: {\n text: string | TextSegment[]\n children: ReactElement\n elementRef: React.RefObject<HTMLElement | null>\n id: string\n delay: number\n duration: number\n cursor: boolean\n cursorChar: string\n shouldAnimate: boolean\n}) {\n const segments = typeof text === \"string\" ? [{ text }] : text\n const [revealed, setRevealed] = useState(false)\n const [done, setDone] = useState(false)\n\n useEffect(() => {\n if (!shouldAnimate || revealed) return\n const timer = setTimeout(() => setRevealed(true), delay * 1000)\n return () => clearTimeout(timer)\n }, [shouldAnimate, delay, revealed])\n\n useEffect(() => {\n if (!revealed) return\n const timer = setTimeout(() => setDone(true), duration * 1000)\n return () => clearTimeout(timer)\n }, [revealed, duration])\n\n if (!isValidElement(children)) return children\n\n const childProps = children.props as Record<string, unknown>\n const existingRef = (childProps as { ref?: Ref<HTMLElement> }).ref\n const existingStyle = (childProps.style ?? {}) as CSSProperties\n\n const animName = `tw-smooth-${id}`\n const showCursor = cursor && revealed && !done\n\n // Full text is always in the DOM — the mask clip animates to reveal it\n const fullContent = segments.map((seg, i) =>\n seg.className ? (\n <span key={i} className={seg.className}>\n {seg.text}\n </span>\n ) : (\n seg.text\n ),\n )\n\n return cloneElement(children, {\n ref: mergeRef(elementRef, existingRef),\n style: {\n ...existingStyle,\n whiteSpace: \"nowrap\" as const,\n overflow: \"hidden\" as const,\n ...(revealed\n ? {\n animation: `${animName} ${duration}s linear forwards`,\n }\n : {\n maxWidth: 0,\n }),\n },\n children: (\n <>\n <style>{`@keyframes ${animName} { from { max-width: 0; } to { max-width: 100%; } }`}</style>\n {fullContent}\n {showCursor && <BlinkingCursor char={cursorChar} id={id} />}\n </>\n ),\n } as Record<string, unknown>)\n}\n\n// ── Exports ──────────────────────────────────────────────────────────\n\nexport { Typewriter }\nexport type { TypewriterProps, TextSegment }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"typewriter.js","names":[],"sources":["../src/typewriter.tsx"],"sourcesContent":["\"use client\"\n\nimport {\n type CSSProperties,\n type ReactElement,\n type Ref,\n cloneElement,\n isValidElement,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\"\nimport { useInView, useReducedMotion } from \"motion/react\"\n\n// ── Types ────────────────────────────────────────────────────────────\n\ninterface TextSegment {\n text: string\n /** Optional className applied to this segment (e.g., bold, colored) */\n className?: string\n}\n\ninterface TypewriterProps {\n /** Simple string or styled segments for per-word/phrase styling */\n text: string | TextSegment[]\n /** Element to render into. Receives the animated text as children. */\n children: ReactElement\n /** Time per character in seconds. Default: 0.04 */\n speed?: number\n /** Delay before typing starts in seconds. Default: 0 */\n delay?: number\n /** Show a blinking cursor during typing. Default: true */\n cursor?: boolean\n /** Cursor character. Default: '|' */\n cursorChar?: string\n /** Trigger when scrolled into view instead of on mount. Default: false */\n onView?: boolean\n /** Trigger once. Default: true */\n once?: boolean\n /**\n * Reveal mode. Default: 'typing'\n * - 'typing': character-by-character like someone typing\n * - 'smooth': sliding mask reveal (cinematic feel)\n */\n variant?: \"typing\" | \"smooth\"\n /** Duration of the smooth reveal in seconds. Default: 2 */\n smoothDuration?: number\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────\n\nfunction mergeRef(\n internalRef: React.RefObject<HTMLElement | null>,\n externalRef?: Ref<HTMLElement>,\n) {\n return (el: HTMLElement | null) => {\n ;(internalRef as { current: HTMLElement | null }).current = el\n if (typeof externalRef === \"function\") externalRef(el)\n else if (externalRef && typeof externalRef === \"object\") {\n ;(externalRef as { current: HTMLElement | null }).current = el\n }\n }\n}\n\n/** Flatten text prop into a single string for character counting */\nfunction flattenText(text: string | TextSegment[]): string {\n if (typeof text === \"string\") return text\n return text.map((s) => s.text).join(\"\")\n}\n\n/** Build rendered content from segments up to a character index */\nfunction buildSegmentContent(\n segments: TextSegment[],\n charIndex: number,\n): React.ReactNode[] {\n const nodes: React.ReactNode[] = []\n let remaining = charIndex\n\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i]!\n if (remaining <= 0) break\n\n const visibleChars = seg.text.slice(0, remaining)\n remaining -= visibleChars.length\n\n if (seg.className) {\n nodes.push(\n <span key={i} className={seg.className}>\n {visibleChars}\n </span>,\n )\n } else {\n nodes.push(visibleChars)\n }\n }\n\n return nodes\n}\n\n// ── Cursor ───────────────────────────────────────────────────────────\n\nfunction BlinkingCursor({ char, id }: { char: string; id: string }) {\n const name = `tw-blink-${id}`\n return (\n <>\n <span\n aria-hidden\n style={{\n animation: `${name} 0.8s step-end infinite`,\n fontWeight: \"normal\",\n }}\n >\n {char}\n </span>\n <style>{`@keyframes ${name} { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }`}</style>\n </>\n )\n}\n\n// ── Typewriter ───────────────────────────────────────────────────────\n\n/**\n * Text reveal animation with two modes:\n *\n * **typing** (default) — character-by-character like someone typing.\n * **smooth** — cinematic sliding mask reveal.\n *\n * Supports plain strings or styled segments for per-word coloring.\n * Zero wrapper divs — renders into the child element via cloneElement.\n *\n * @example\n * ```tsx\n * // Simple string\n * <Typewriter text=\"Welcome to the future.\">\n * <h1 className=\"text-4xl font-bold\" />\n * </Typewriter>\n *\n * // Per-word styling\n * <Typewriter text={[\n * { text: \"Build on \" },\n * { text: \"Intuition\", className: \"text-primary font-bold\" },\n * ]}>\n * <h1 className=\"text-4xl\" />\n * </Typewriter>\n *\n * // Smooth reveal\n * <Typewriter text=\"Cinematic reveal.\" variant=\"smooth\" smoothDuration={1.5}>\n * <h1 className=\"text-4xl font-bold\" />\n * </Typewriter>\n * ```\n */\nfunction Typewriter({\n text,\n children,\n speed = 0.04,\n delay = 0,\n cursor = true,\n cursorChar = \"|\",\n onView = false,\n once = true,\n variant = \"typing\",\n smoothDuration = 2,\n}: TypewriterProps) {\n const ref = useRef<HTMLElement>(null)\n const id = useId().replace(/:/g, \"\")\n const isInView = useInView(ref, { once, margin: \"-50px\" })\n\n const shouldAnimate = onView ? isInView : true\n\n if (variant === \"smooth\") {\n return (\n <SmoothReveal\n text={text}\n elementRef={ref}\n id={id}\n delay={delay}\n duration={smoothDuration}\n cursor={cursor}\n cursorChar={cursorChar}\n shouldAnimate={shouldAnimate}\n >\n {children}\n </SmoothReveal>\n )\n }\n\n return (\n <TypingReveal\n text={text}\n elementRef={ref}\n id={id}\n speed={speed}\n delay={delay}\n cursor={cursor}\n cursorChar={cursorChar}\n shouldAnimate={shouldAnimate}\n >\n {children}\n </TypingReveal>\n )\n}\n\n// ── Typing Reveal ────────────────────────────────────────────────────\n\nfunction TypingReveal({\n text,\n children,\n elementRef,\n id,\n speed,\n delay,\n cursor,\n cursorChar,\n shouldAnimate,\n}: {\n text: string | TextSegment[]\n children: ReactElement\n elementRef: React.RefObject<HTMLElement | null>\n id: string\n speed: number\n delay: number\n cursor: boolean\n cursorChar: string\n shouldAnimate: boolean\n}) {\n const prefersReducedMotion = useReducedMotion()\n const flat = flattenText(text)\n const segments = typeof text === \"string\" ? [{ text }] : text\n const [charIndex, setCharIndex] = useState(0)\n const [started, setStarted] = useState(false)\n const [done, setDone] = useState(false)\n\n useEffect(() => {\n if (prefersReducedMotion || !shouldAnimate || started) return\n const timer = setTimeout(() => setStarted(true), delay * 1000)\n return () => clearTimeout(timer)\n }, [prefersReducedMotion, shouldAnimate, delay, started])\n\n useEffect(() => {\n if (prefersReducedMotion || !started || done) return\n if (charIndex >= flat.length) {\n setDone(true)\n return\n }\n const timer = setTimeout(() => setCharIndex((prev) => prev + 1), speed * 1000)\n return () => clearTimeout(timer)\n }, [prefersReducedMotion, started, charIndex, flat.length, speed, done])\n\n if (!isValidElement(children)) return children\n\n const childProps = children.props as Record<string, unknown>\n const existingRef = (childProps as { ref?: Ref<HTMLElement> }).ref\n\n // Reduced motion: skip the character-by-character reveal and render the\n // full text immediately in its final resting shape (no cursor).\n const content = prefersReducedMotion\n ? buildSegmentContent(segments, flat.length)\n : started\n ? buildSegmentContent(segments, charIndex)\n : null\n const showCursor = !prefersReducedMotion && cursor && started && !done\n\n return cloneElement(children, {\n ref: mergeRef(elementRef, existingRef),\n children: (\n <>\n {content}\n {showCursor && <BlinkingCursor char={cursorChar} id={id} />}\n </>\n ),\n } as Record<string, unknown>)\n}\n\n// ── Smooth Reveal ────────────────────────────────────────────────────\n\nfunction SmoothReveal({\n text,\n children,\n elementRef,\n id,\n delay,\n duration,\n cursor,\n cursorChar,\n shouldAnimate,\n}: {\n text: string | TextSegment[]\n children: ReactElement\n elementRef: React.RefObject<HTMLElement | null>\n id: string\n delay: number\n duration: number\n cursor: boolean\n cursorChar: string\n shouldAnimate: boolean\n}) {\n const prefersReducedMotion = useReducedMotion()\n const segments = typeof text === \"string\" ? [{ text }] : text\n const [revealed, setRevealed] = useState(false)\n const [done, setDone] = useState(false)\n\n useEffect(() => {\n if (prefersReducedMotion || !shouldAnimate || revealed) return\n const timer = setTimeout(() => setRevealed(true), delay * 1000)\n return () => clearTimeout(timer)\n }, [prefersReducedMotion, shouldAnimate, delay, revealed])\n\n useEffect(() => {\n if (prefersReducedMotion || !revealed) return\n const timer = setTimeout(() => setDone(true), duration * 1000)\n return () => clearTimeout(timer)\n }, [prefersReducedMotion, revealed, duration])\n\n if (!isValidElement(children)) return children\n\n const childProps = children.props as Record<string, unknown>\n const existingRef = (childProps as { ref?: Ref<HTMLElement> }).ref\n const existingStyle = (childProps.style ?? {}) as CSSProperties\n\n const animName = `tw-smooth-${id}`\n const showCursor = !prefersReducedMotion && cursor && revealed && !done\n\n // Full text is always in the DOM — the mask clip animates to reveal it\n const fullContent = segments.map((seg, i) =>\n seg.className ? (\n <span key={i} className={seg.className}>\n {seg.text}\n </span>\n ) : (\n seg.text\n ),\n )\n\n return cloneElement(children, {\n ref: mergeRef(elementRef, existingRef),\n style: {\n ...existingStyle,\n whiteSpace: \"nowrap\" as const,\n overflow: \"hidden\" as const,\n // Reduced motion: skip the mask animation and pin to the reveal's\n // final resting state (fully shown) with no animation or cursor.\n ...(prefersReducedMotion\n ? {\n maxWidth: \"100%\" as const,\n }\n : revealed\n ? {\n animation: `${animName} ${duration}s linear forwards`,\n }\n : {\n maxWidth: 0,\n }),\n },\n children: (\n <>\n <style>{`@keyframes ${animName} { from { max-width: 0; } to { max-width: 100%; } }`}</style>\n {fullContent}\n {showCursor && <BlinkingCursor char={cursorChar} id={id} />}\n </>\n ),\n } as Record<string, unknown>)\n}\n\n// ── Exports ──────────────────────────────────────────────────────────\n\nexport { Typewriter }\nexport type { TypewriterProps, TextSegment }\n"],"mappings":";;;;;AAoDA,SAAS,SACP,aACA,aACA;AACA,SAAQ,OAA2B;AAC/B,cAAgD,UAAU;AAC5D,MAAI,OAAO,gBAAgB,WAAY,aAAY,GAAG;WAC7C,eAAe,OAAO,gBAAgB,SAC3C,aAAgD,UAAU;;;;AAMlE,SAAS,YAAY,MAAsC;AACzD,KAAI,OAAO,SAAS,SAAU,QAAO;AACrC,QAAO,KAAK,KAAK,MAAM,EAAE,KAAK,CAAC,KAAK,GAAG;;;AAIzC,SAAS,oBACP,UACA,WACmB;CACnB,MAAM,QAA2B,EAAE;CACnC,IAAI,YAAY;AAEhB,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,MAAM,SAAS;AACrB,MAAI,aAAa,EAAG;EAEpB,MAAM,eAAe,IAAI,KAAK,MAAM,GAAG,UAAU;AACjD,eAAa,aAAa;AAE1B,MAAI,IAAI,UACN,OAAM,KACJ,oBAAC,QAAD;GAAc,WAAW,IAAI;aAC1B;GACI,EAFI,EAEJ,CACR;MAED,OAAM,KAAK,aAAa;;AAI5B,QAAO;;AAKT,SAAS,eAAe,EAAE,MAAM,MAAoC;CAClE,MAAM,OAAO,YAAY;AACzB,QACE,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,QAAD;EACE,eAAA;EACA,OAAO;GACL,WAAW,GAAG,KAAK;GACnB,YAAY;GACb;YAEA;EACI,CAAA,EACP,oBAAC,SAAD,EAAA,UAAQ,cAAc,KAAK,oDAA2D,CAAA,CACrF,EAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCP,SAAS,WAAW,EAClB,MACA,UACA,QAAQ,KACR,QAAQ,GACR,SAAS,MACT,aAAa,KACb,SAAS,OACT,OAAO,MACP,UAAU,UACV,iBAAiB,KACC;CAClB,MAAM,MAAM,OAAoB,KAAK;CACrC,MAAM,KAAK,OAAO,CAAC,QAAQ,MAAM,GAAG;CACpC,MAAM,WAAW,UAAU,KAAK;EAAE;EAAM,QAAQ;EAAS,CAAC;CAE1D,MAAM,gBAAgB,SAAS,WAAW;AAE1C,KAAI,YAAY,SACd,QACE,oBAAC,cAAD;EACQ;EACN,YAAY;EACR;EACG;EACP,UAAU;EACF;EACI;EACG;EAEd;EACY,CAAA;AAInB,QACE,oBAAC,cAAD;EACQ;EACN,YAAY;EACR;EACG;EACA;EACC;EACI;EACG;EAEd;EACY,CAAA;;AAMnB,SAAS,aAAa,EACpB,MACA,UACA,YACA,IACA,OACA,OACA,QACA,YACA,iBAWC;CACD,MAAM,uBAAuB,kBAAkB;CAC/C,MAAM,OAAO,YAAY,KAAK;CAC9B,MAAM,WAAW,OAAO,SAAS,WAAW,CAAC,EAAE,MAAM,CAAC,GAAG;CACzD,MAAM,CAAC,WAAW,gBAAgB,SAAS,EAAE;CAC7C,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAC7C,MAAM,CAAC,MAAM,WAAW,SAAS,MAAM;AAEvC,iBAAgB;AACd,MAAI,wBAAwB,CAAC,iBAAiB,QAAS;EACvD,MAAM,QAAQ,iBAAiB,WAAW,KAAK,EAAE,QAAQ,IAAK;AAC9D,eAAa,aAAa,MAAM;IAC/B;EAAC;EAAsB;EAAe;EAAO;EAAQ,CAAC;AAEzD,iBAAgB;AACd,MAAI,wBAAwB,CAAC,WAAW,KAAM;AAC9C,MAAI,aAAa,KAAK,QAAQ;AAC5B,WAAQ,KAAK;AACb;;EAEF,MAAM,QAAQ,iBAAiB,cAAc,SAAS,OAAO,EAAE,EAAE,QAAQ,IAAK;AAC9E,eAAa,aAAa,MAAM;IAC/B;EAAC;EAAsB;EAAS;EAAW,KAAK;EAAQ;EAAO;EAAK,CAAC;AAExE,KAAI,CAAC,eAAe,SAAS,CAAE,QAAO;CAGtC,MAAM,cADa,SAAS,MACmC;CAI/D,MAAM,UAAU,uBACZ,oBAAoB,UAAU,KAAK,OAAO,GAC1C,UACE,oBAAoB,UAAU,UAAU,GACxC;CACN,MAAM,aAAa,CAAC,wBAAwB,UAAU,WAAW,CAAC;AAElE,QAAO,aAAa,UAAU;EAC5B,KAAK,SAAS,YAAY,YAAY;EACtC,UACE,qBAAA,UAAA,EAAA,UAAA,CACG,SACA,cAAc,oBAAC,gBAAD;GAAgB,MAAM;GAAgB;GAAM,CAAA,CAC1D,EAAA,CAAA;EAEN,CAA4B;;AAK/B,SAAS,aAAa,EACpB,MACA,UACA,YACA,IACA,OACA,UACA,QACA,YACA,iBAWC;CACD,MAAM,uBAAuB,kBAAkB;CAC/C,MAAM,WAAW,OAAO,SAAS,WAAW,CAAC,EAAE,MAAM,CAAC,GAAG;CACzD,MAAM,CAAC,UAAU,eAAe,SAAS,MAAM;CAC/C,MAAM,CAAC,MAAM,WAAW,SAAS,MAAM;AAEvC,iBAAgB;AACd,MAAI,wBAAwB,CAAC,iBAAiB,SAAU;EACxD,MAAM,QAAQ,iBAAiB,YAAY,KAAK,EAAE,QAAQ,IAAK;AAC/D,eAAa,aAAa,MAAM;IAC/B;EAAC;EAAsB;EAAe;EAAO;EAAS,CAAC;AAE1D,iBAAgB;AACd,MAAI,wBAAwB,CAAC,SAAU;EACvC,MAAM,QAAQ,iBAAiB,QAAQ,KAAK,EAAE,WAAW,IAAK;AAC9D,eAAa,aAAa,MAAM;IAC/B;EAAC;EAAsB;EAAU;EAAS,CAAC;AAE9C,KAAI,CAAC,eAAe,SAAS,CAAE,QAAO;CAEtC,MAAM,aAAa,SAAS;CAC5B,MAAM,cAAe,WAA0C;CAC/D,MAAM,gBAAiB,WAAW,SAAS,EAAE;CAE7C,MAAM,WAAW,aAAa;CAC9B,MAAM,aAAa,CAAC,wBAAwB,UAAU,YAAY,CAAC;CAGnE,MAAM,cAAc,SAAS,KAAK,KAAK,MACrC,IAAI,YACF,oBAAC,QAAD;EAAc,WAAW,IAAI;YAC1B,IAAI;EACA,EAFI,EAEJ,GAEP,IAAI,KAEP;AAED,QAAO,aAAa,UAAU;EAC5B,KAAK,SAAS,YAAY,YAAY;EACtC,OAAO;GACL,GAAG;GACH,YAAY;GACZ,UAAU;GAGV,GAAI,uBACA,EACE,UAAU,QACX,GACD,WACE,EACE,WAAW,GAAG,SAAS,GAAG,SAAS,oBACpC,GACD,EACE,UAAU,GACX;GACR;EACD,UACE,qBAAA,UAAA,EAAA,UAAA;GACE,oBAAC,SAAD,EAAA,UAAQ,cAAc,SAAS,sDAA6D,CAAA;GAC3F;GACA,cAAc,oBAAC,gBAAD;IAAgB,MAAM;IAAgB;IAAM,CAAA;GAC1D,EAAA,CAAA;EAEN,CAA4B"}
|