react-marketing-ui 0.0.3 → 0.0.4
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/accordion.js +1 -1
- package/dist/accordion.js.map +1 -1
- package/dist/accordion.mjs +222 -2
- package/dist/accordion.mjs.map +1 -1
- package/dist/action-swap.js +1 -0
- package/dist/action-swap.mjs +77 -3
- package/dist/action-swap.mjs.map +1 -1
- package/dist/animated-beam.js +1 -0
- package/dist/animated-beam.mjs +127 -2
- package/dist/animated-beam.mjs.map +1 -1
- package/dist/button.js +1 -1
- package/dist/button.js.map +1 -1
- package/dist/button.mjs +115 -2
- package/dist/button.mjs.map +1 -1
- package/dist/card.js +1 -2
- package/dist/card.js.map +1 -1
- package/dist/card.mjs +135 -3
- package/dist/card.mjs.map +1 -1
- package/dist/dia-text-reveal.js +1 -0
- package/dist/dia-text-reveal.mjs +331 -2
- package/dist/dia-text-reveal.mjs.map +1 -1
- package/dist/elevated-input.js +1 -1
- package/dist/elevated-input.js.map +1 -1
- package/dist/elevated-input.mjs +48 -2
- package/dist/elevated-input.mjs.map +1 -1
- package/dist/fade-in.js +1 -0
- package/dist/fade-in.mjs +84 -2
- package/dist/fade-in.mjs.map +1 -1
- package/dist/feature-card.js +1 -1
- package/dist/feature-card.js.map +1 -1
- package/dist/feature-card.mjs +34 -2
- package/dist/feature-card.mjs.map +1 -1
- package/dist/flowchart-parts.js +1 -1
- package/dist/flowchart-parts.js.map +1 -1
- package/dist/flowchart-parts.mjs +91 -2
- package/dist/flowchart-parts.mjs.map +1 -1
- package/dist/glossy-button.js +1 -1
- package/dist/glossy-button.js.map +1 -1
- package/dist/glossy-button.mjs +82 -2
- package/dist/glossy-button.mjs.map +1 -1
- package/dist/grainient.js +1 -0
- package/dist/grainient.mjs +327 -2
- package/dist/grainient.mjs.map +1 -1
- package/dist/highlighter.js +1 -0
- package/dist/highlighter.mjs +122 -3
- package/dist/highlighter.mjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2642 -32
- package/dist/index.mjs.map +1 -1
- package/dist/input.js +1 -0
- package/dist/input.mjs +107 -2
- package/dist/input.mjs.map +1 -1
- package/dist/lib/breakpoints.js +1 -1
- package/dist/lib/breakpoints.js.map +1 -1
- package/dist/lib/breakpoints.mjs +34 -1
- package/dist/lib/breakpoints.mjs.map +1 -1
- package/dist/lib/cn.js +1 -1
- package/dist/lib/cn.js.map +1 -1
- package/dist/lib/cn.mjs +17 -1
- package/dist/lib/cn.mjs.map +1 -1
- package/dist/lib/ease.js +1 -0
- package/dist/lib/ease.mjs +9 -1
- package/dist/lib/ease.mjs.map +1 -1
- package/dist/lib/surface.js +1 -0
- package/dist/lib/surface.mjs +37 -1
- package/dist/lib/surface.mjs.map +1 -1
- package/dist/lib/use-media-query.js +1 -0
- package/dist/lib/use-media-query.mjs +16 -1
- package/dist/lib/use-media-query.mjs.map +1 -1
- package/dist/liquid-metal.js +1 -0
- package/dist/liquid-metal.mjs +40 -2
- package/dist/liquid-metal.mjs.map +1 -1
- package/dist/navigation-menu-icons.js +1 -1
- package/dist/navigation-menu-icons.js.map +1 -1
- package/dist/navigation-menu-icons.mjs +36 -1
- package/dist/navigation-menu-icons.mjs.map +1 -1
- package/dist/navigation-menu-styles.js +1 -1
- package/dist/navigation-menu-styles.js.map +1 -1
- package/dist/navigation-menu-styles.mjs +11 -1
- package/dist/navigation-menu-styles.mjs.map +1 -1
- package/dist/navigation-menu.js +1 -2
- package/dist/navigation-menu.js.map +1 -1
- package/dist/navigation-menu.mjs +350 -5
- package/dist/navigation-menu.mjs.map +1 -1
- package/dist/password-input.js +1 -0
- package/dist/password-input.mjs +171 -3
- package/dist/password-input.mjs.map +1 -1
- package/dist/rainbow-button.js +1 -0
- package/dist/rainbow-button.mjs +45 -2
- package/dist/rainbow-button.mjs.map +1 -1
- package/dist/range-slider.js +1 -0
- package/dist/range-slider.mjs +82 -1
- package/dist/range-slider.mjs.map +1 -1
- package/dist/rolling-badge.js +1 -0
- package/dist/rolling-badge.mjs +68 -2
- package/dist/rolling-badge.mjs.map +1 -1
- package/dist/savings-badge.js +1 -1
- package/dist/savings-badge.js.map +1 -1
- package/dist/savings-badge.mjs +22 -2
- package/dist/savings-badge.mjs.map +1 -1
- package/dist/shine-border.js +1 -0
- package/dist/shine-border.mjs +44 -2
- package/dist/shine-border.mjs.map +1 -1
- package/dist/sparkles-text.js +1 -0
- package/dist/sparkles-text.mjs +84 -3
- package/dist/sparkles-text.mjs.map +1 -1
- package/dist/tabs.js +1 -0
- package/dist/tabs.mjs +118 -2
- package/dist/tabs.mjs.map +1 -1
- package/dist/theme-toggle.js +1 -0
- package/dist/theme-toggle.mjs +248 -5
- package/dist/theme-toggle.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-3SB6HUBA.mjs +0 -31
- package/dist/chunk-3SB6HUBA.mjs.map +0 -1
- package/dist/chunk-4PBVBXGY.mjs +0 -84
- package/dist/chunk-4PBVBXGY.mjs.map +0 -1
- package/dist/chunk-4VCFWVSJ.mjs +0 -42
- package/dist/chunk-4VCFWVSJ.mjs.map +0 -1
- package/dist/chunk-5QQAPGR2.mjs +0 -109
- package/dist/chunk-5QQAPGR2.mjs.map +0 -1
- package/dist/chunk-6MB4UKRX.mjs +0 -19
- package/dist/chunk-6MB4UKRX.mjs.map +0 -1
- package/dist/chunk-6T5LR34O.mjs +0 -45
- package/dist/chunk-6T5LR34O.mjs.map +0 -1
- package/dist/chunk-AYLCYDUM.mjs +0 -275
- package/dist/chunk-AYLCYDUM.mjs.map +0 -1
- package/dist/chunk-AYY7D2RJ.mjs +0 -112
- package/dist/chunk-AYY7D2RJ.mjs.map +0 -1
- package/dist/chunk-CTZEI2YS.mjs +0 -324
- package/dist/chunk-CTZEI2YS.mjs.map +0 -1
- package/dist/chunk-DA3I3Y5O.mjs +0 -71
- package/dist/chunk-DA3I3Y5O.mjs.map +0 -1
- package/dist/chunk-EYHT2EWJ.mjs +0 -18
- package/dist/chunk-EYHT2EWJ.mjs.map +0 -1
- package/dist/chunk-IHLGCS63.mjs +0 -36
- package/dist/chunk-IHLGCS63.mjs.map +0 -1
- package/dist/chunk-JVXJ6SFX.mjs +0 -38
- package/dist/chunk-JVXJ6SFX.mjs.map +0 -1
- package/dist/chunk-KBHYXLRG.mjs +0 -13
- package/dist/chunk-KBHYXLRG.mjs.map +0 -1
- package/dist/chunk-KMXUQYR3.mjs +0 -74
- package/dist/chunk-KMXUQYR3.mjs.map +0 -1
- package/dist/chunk-KNPOZ34H.mjs +0 -88
- package/dist/chunk-KNPOZ34H.mjs.map +0 -1
- package/dist/chunk-KRNWCJ4D.mjs +0 -219
- package/dist/chunk-KRNWCJ4D.mjs.map +0 -1
- package/dist/chunk-M5MESV26.mjs +0 -124
- package/dist/chunk-M5MESV26.mjs.map +0 -1
- package/dist/chunk-MJYBPJBA.mjs +0 -115
- package/dist/chunk-MJYBPJBA.mjs.map +0 -1
- package/dist/chunk-NNHPQ67J.mjs +0 -10
- package/dist/chunk-NNHPQ67J.mjs.map +0 -1
- package/dist/chunk-OCZFHVVD.mjs +0 -71
- package/dist/chunk-OCZFHVVD.mjs.map +0 -1
- package/dist/chunk-QAWSH567.mjs +0 -19
- package/dist/chunk-QAWSH567.mjs.map +0 -1
- package/dist/chunk-R2E2J42H.mjs +0 -38
- package/dist/chunk-R2E2J42H.mjs.map +0 -1
- package/dist/chunk-TPGLLSYV.mjs +0 -65
- package/dist/chunk-TPGLLSYV.mjs.map +0 -1
- package/dist/chunk-VJENJHXJ.mjs +0 -72
- package/dist/chunk-VJENJHXJ.mjs.map +0 -1
- package/dist/chunk-W22G7XHR.mjs +0 -104
- package/dist/chunk-W22G7XHR.mjs.map +0 -1
- package/dist/chunk-WDR5VPZJ.mjs +0 -79
- package/dist/chunk-WDR5VPZJ.mjs.map +0 -1
- package/dist/chunk-WLUGKBH7.mjs +0 -37
- package/dist/chunk-WLUGKBH7.mjs.map +0 -1
- package/dist/chunk-WYJYLDUA.mjs +0 -84
- package/dist/chunk-WYJYLDUA.mjs.map +0 -1
- package/dist/chunk-XZEDZVXT.mjs +0 -38
- package/dist/chunk-XZEDZVXT.mjs.map +0 -1
- package/dist/chunk-YKGP5JIL.mjs +0 -328
- package/dist/chunk-YKGP5JIL.mjs.map +0 -1
- package/dist/chunk-YM3U2OBE.mjs +0 -99
- package/dist/chunk-YM3U2OBE.mjs.map +0 -1
package/dist/sparkles-text.mjs
CHANGED
|
@@ -1,5 +1,86 @@
|
|
|
1
|
-
|
|
2
|
-
import '
|
|
3
|
-
import '
|
|
1
|
+
"use client";
|
|
2
|
+
import { motion } from 'framer-motion';
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { clsx } from 'clsx';
|
|
5
|
+
import { twMerge } from 'tailwind-merge';
|
|
6
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
function useMediaQuery(query, defaultState = false) {
|
|
9
|
+
const [matches, setMatches] = useState(defaultState);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const mql = globalThis.matchMedia(query);
|
|
12
|
+
setMatches(mql.matches);
|
|
13
|
+
const onChange = (e) => setMatches(e.matches);
|
|
14
|
+
mql.addEventListener("change", onChange);
|
|
15
|
+
return () => mql.removeEventListener("change", onChange);
|
|
16
|
+
}, [query]);
|
|
17
|
+
return matches;
|
|
18
|
+
}
|
|
19
|
+
function cn(...inputs) {
|
|
20
|
+
return twMerge(clsx(inputs));
|
|
21
|
+
}
|
|
22
|
+
var STAR_PATH = "M9.82531 0.843845C10.0553 0.215178 10.9446 0.215178 11.1746 0.843845L11.8618 2.72026C12.4006 4.19229 12.3916 6.39157 13.5 7.5C14.6084 8.60843 16.8077 8.59935 18.2797 9.13822L20.1561 9.82534C20.7858 10.0553 20.7858 10.9447 20.1561 11.1747L18.2797 11.8618C16.8077 12.4007 14.6084 12.3916 13.5 13.5C12.3916 14.6084 12.4006 16.8077 11.8618 18.2798L11.1746 20.1562C10.9446 20.7858 10.0553 20.7858 9.82531 20.1562L9.13819 18.2798C8.59932 16.8077 8.60843 14.6084 7.5 13.5C6.39157 12.3916 4.19225 12.4007 2.72023 11.8618L0.843814 11.1747C0.215148 10.9447 0.215148 10.0553 0.843814 9.82534L2.72023 9.13822C4.19225 8.59935 6.39157 8.60843 7.5 7.5C8.60843 6.39157 8.59932 4.19229 9.13819 2.72026L9.82531 0.843845Z";
|
|
23
|
+
var DEFAULT_COLORS = {
|
|
24
|
+
first: "var(--color-accent-brand)",
|
|
25
|
+
second: "var(--color-accent-brand-soft)"
|
|
26
|
+
};
|
|
27
|
+
function SparkleStar({ x, y, color, delay, scale }) {
|
|
28
|
+
return /* @__PURE__ */ jsx(
|
|
29
|
+
motion.svg,
|
|
30
|
+
{
|
|
31
|
+
className: "pointer-events-none absolute z-20",
|
|
32
|
+
initial: { opacity: 0, left: x, top: y },
|
|
33
|
+
animate: {
|
|
34
|
+
opacity: [0, 1, 0],
|
|
35
|
+
scale: [0, scale, 0],
|
|
36
|
+
rotate: [75, 120, 150]
|
|
37
|
+
},
|
|
38
|
+
transition: {
|
|
39
|
+
duration: 0.8,
|
|
40
|
+
repeat: Number.POSITIVE_INFINITY,
|
|
41
|
+
delay
|
|
42
|
+
},
|
|
43
|
+
width: "21",
|
|
44
|
+
height: "21",
|
|
45
|
+
viewBox: "0 0 21 21",
|
|
46
|
+
"aria-hidden": true,
|
|
47
|
+
children: /* @__PURE__ */ jsx("path", { d: STAR_PATH, fill: color })
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
function SparklesText({ children, className, sparklesCount = 10, colors = DEFAULT_COLORS }) {
|
|
52
|
+
const reducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
|
|
53
|
+
const [sparkles, setSparkles] = useState([]);
|
|
54
|
+
const { first, second } = colors;
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (reducedMotion) {
|
|
57
|
+
setSparkles([]);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const spawn = () => ({
|
|
61
|
+
x: `${Math.random() * 100}%`,
|
|
62
|
+
y: `${Math.random() * 100}%`,
|
|
63
|
+
color: Math.random() > 0.5 ? first : second,
|
|
64
|
+
delay: Math.random() * 2,
|
|
65
|
+
scale: Math.random() + 0.3,
|
|
66
|
+
expiresAt: performance.now() + (Math.random() * 10 + 5) * 1e3
|
|
67
|
+
});
|
|
68
|
+
setSparkles(Array.from({ length: sparklesCount }, spawn));
|
|
69
|
+
const interval = setInterval(() => {
|
|
70
|
+
setSparkles((current) => {
|
|
71
|
+
const now = performance.now();
|
|
72
|
+
if (!current.some((sparkle) => sparkle.expiresAt <= now)) return current;
|
|
73
|
+
return current.map((sparkle) => sparkle.expiresAt <= now ? spawn() : sparkle);
|
|
74
|
+
});
|
|
75
|
+
}, 500);
|
|
76
|
+
return () => clearInterval(interval);
|
|
77
|
+
}, [first, second, sparklesCount, reducedMotion]);
|
|
78
|
+
return /* @__PURE__ */ jsxs("span", { className: cn("relative inline-block", className), children: [
|
|
79
|
+
sparkles.map((sparkle) => /* @__PURE__ */ jsx(SparkleStar, { ...sparkle }, `${sparkle.x}-${sparkle.y}-${sparkle.expiresAt}`)),
|
|
80
|
+
children
|
|
81
|
+
] });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export { SparklesText };
|
|
4
85
|
//# sourceMappingURL=sparkles-text.mjs.map
|
|
5
86
|
//# sourceMappingURL=sparkles-text.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"sparkles-text.mjs"}
|
|
1
|
+
{"version":3,"sources":["../src/lib/use-media-query.ts","../src/lib/utils.ts","../src/components/sparkles-text.tsx"],"names":["useState","useEffect"],"mappings":"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","file":"sparkles-text.mjs","sourcesContent":["'use client';\n\nimport { useEffect, useState } from 'react';\n\n/**\n * Tracks a CSS media query, returning `true` when it matches.\n *\n * The default return before hydration is `false`; pass `defaultState`\n * to set a different initial value (e.g. `true` for dark-mode queries\n * whose SSR default is dark).\n */\nexport function useMediaQuery(query: string, defaultState = false): boolean {\n const [matches, setMatches] = useState(defaultState);\n\n // biome-ignore lint/plugin: subscribes to a matchMedia listener — an imperative side effect with no derived-state equivalent.\n useEffect(() => {\n const mql = globalThis.matchMedia(query);\n setMatches(mql.matches);\n\n const onChange = (e: MediaQueryListEvent) => setMatches(e.matches);\n mql.addEventListener('change', onChange);\n return () => mql.removeEventListener('change', onChange);\n }, [query]);\n\n return matches;\n}\n","import { type ClassValue, clsx } from 'clsx';\nimport type { CSSProperties } from 'react';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\n/**\n * A style object that may carry CSS custom properties (CSS variables)\n * such as `--duration`. React's `CSSProperties` intentionally drops\n * its index signature, so custom properties are not accepted without help.\n */\nexport type CSSPropertiesWithVars = CSSProperties & Record<`--${string}`, string | number>;\n\n/**\n * Builds a {@link CSSProperties} value that also allows CSS custom properties,\n * so `style` props can be written without an `as React.CSSProperties` cast while\n * still type-checking both the known style keys and the custom-property values.\n *\n * @example\n * <div style={validCssVars({ '--duration': '0.35s', color: 'red' })} />\n */\nexport function validCssVars(style: CSSPropertiesWithVars): CSSProperties {\n return style;\n}\n\n/**\n * Narrows a union to the member(s) that declare `key` — a type-guard\n * replacement for the `in` operator's discriminated-union narrowing.\n */\nexport function hasKey<K extends PropertyKey>(key: K, obj: unknown): obj is Record<K, unknown> {\n return obj !== null && typeof obj === 'object' && Object.hasOwn(obj, key);\n}\n\nexport function noop(): void {\n // intentional no-op\n}\n","'use client';\n\nimport { motion } from 'framer-motion';\nimport { type ReactNode, useEffect, useState } from 'react';\nimport { useMediaQuery } from '../lib/use-media-query.js';\nimport { cn } from '../lib/utils.js';\n\n/** Four-pointed star, drawn once and reused by every sparkle. */\nconst STAR_PATH =\n 'M9.82531 0.843845C10.0553 0.215178 10.9446 0.215178 11.1746 0.843845L11.8618 2.72026C12.4006 4.19229 12.3916 6.39157 13.5 7.5C14.6084 8.60843 16.8077 8.59935 18.2797 9.13822L20.1561 9.82534C20.7858 10.0553 20.7858 10.9447 20.1561 11.1747L18.2797 11.8618C16.8077 12.4007 14.6084 12.3916 13.5 13.5C12.3916 14.6084 12.4006 16.8077 11.8618 18.2798L11.1746 20.1562C10.9446 20.7858 10.0553 20.7858 9.82531 20.1562L9.13819 18.2798C8.59932 16.8077 8.60843 14.6084 7.5 13.5C6.39157 12.3916 4.19225 12.4007 2.72023 11.8618L0.843814 11.1747C0.215148 10.9447 0.215148 10.0553 0.843814 9.82534L2.72023 9.13822C4.19225 8.59935 6.39157 8.60843 7.5 7.5C8.60843 6.39157 8.59932 4.19229 9.13819 2.72026L9.82531 0.843845Z';\n\nconst DEFAULT_COLORS = {\n first: 'var(--color-accent-brand)',\n second: 'var(--color-accent-brand-soft)',\n};\n\ntype Sparkle = {\n /** Position within the text's box, as percentages. */\n x: string;\n y: string;\n color: string;\n /** Offsets the twinkle so the group never pulses in unison. */\n delay: number;\n scale: number;\n /** `performance.now()` timestamp after which this sparkle is replaced by a fresh one. */\n expiresAt: number;\n};\n\nfunction SparkleStar({ x, y, color, delay, scale }: Omit<Sparkle, 'expiresAt'>) {\n return (\n <motion.svg\n className=\"pointer-events-none absolute z-20\"\n initial={{ opacity: 0, left: x, top: y }}\n animate={{\n opacity: [0, 1, 0],\n scale: [0, scale, 0],\n rotate: [75, 120, 150],\n }}\n transition={{\n duration: 0.8,\n repeat: Number.POSITIVE_INFINITY,\n delay,\n }}\n width=\"21\"\n height=\"21\"\n viewBox=\"0 0 21 21\"\n aria-hidden={true}\n >\n <path d={STAR_PATH} fill={color} />\n </motion.svg>\n );\n}\n\nexport type SparklesTextProps = {\n /** The text to sparkle. Rendered inline, so it inherits the surrounding type. */\n children: ReactNode;\n className?: string;\n /** How many sparkles orbit the text at once. @default 10 */\n sparklesCount?: number;\n /** The two colors sparkles alternate between. */\n colors?: { first: string; second: string };\n};\n\n/**\n * Wraps a word or phrase in continuously respawning sparkles.\n *\n * Sparkles are generated on the client only — the exported HTML ships the bare\n * text, so nothing about the layout depends on JS having run. Readers who ask\n * for reduced motion get that same bare text.\n */\nexport function SparklesText({ children, className, sparklesCount = 10, colors = DEFAULT_COLORS }: SparklesTextProps) {\n const reducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');\n const [sparkles, setSparkles] = useState<Sparkle[]>([]);\n const { first, second } = colors;\n\n // biome-ignore lint/plugin: spawns and retires sparkles on a timer — an imperative animation loop with no derived-state equivalent.\n useEffect(() => {\n if (reducedMotion) {\n setSparkles([]);\n return;\n }\n\n const spawn = (): Sparkle => ({\n x: `${Math.random() * 100}%`,\n y: `${Math.random() * 100}%`,\n color: Math.random() > 0.5 ? first : second,\n delay: Math.random() * 2,\n scale: Math.random() + 0.3,\n expiresAt: performance.now() + (Math.random() * 10 + 5) * 1000,\n });\n\n setSparkles(Array.from({ length: sparklesCount }, spawn));\n\n const interval = setInterval(() => {\n setSparkles((current) => {\n const now = performance.now();\n if (!current.some((sparkle) => sparkle.expiresAt <= now)) return current;\n return current.map((sparkle) => (sparkle.expiresAt <= now ? spawn() : sparkle));\n });\n }, 500);\n\n return () => clearInterval(interval);\n }, [first, second, sparklesCount, reducedMotion]);\n\n return (\n <span className={cn('relative inline-block', className)}>\n {sparkles.map((sparkle) => (\n <SparkleStar key={`${sparkle.x}-${sparkle.y}-${sparkle.expiresAt}`} {...sparkle} />\n ))}\n {children}\n </span>\n );\n}\n"]}
|
package/dist/tabs.js
CHANGED
package/dist/tabs.mjs
CHANGED
|
@@ -1,4 +1,120 @@
|
|
|
1
|
-
|
|
2
|
-
import '
|
|
1
|
+
"use client";
|
|
2
|
+
import { useReducedMotion, useSpring, useTransform, motion } from 'framer-motion';
|
|
3
|
+
import { useRef, useState, useCallback, useEffect } from 'react';
|
|
4
|
+
import { clsx } from 'clsx';
|
|
5
|
+
import { twMerge } from 'tailwind-merge';
|
|
6
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
function cn(...inputs) {
|
|
9
|
+
return twMerge(clsx(inputs));
|
|
10
|
+
}
|
|
11
|
+
var PULL = 0.035;
|
|
12
|
+
function pillTarget(activeIdx, hoverIdx, rects, pull) {
|
|
13
|
+
const active = rects[activeIdx];
|
|
14
|
+
if (!active) throw new Error(`No rect at index ${activeIdx}`);
|
|
15
|
+
if (hoverIdx === null || hoverIdx === activeIdx) return active;
|
|
16
|
+
const hover = rects[hoverIdx];
|
|
17
|
+
if (!hover) return active;
|
|
18
|
+
const activeRight = active.left + active.width;
|
|
19
|
+
const hoverRight = hover.left + hover.width;
|
|
20
|
+
const left = Math.min(active.left, active.left + (hover.left - active.left) * pull);
|
|
21
|
+
const right = Math.max(activeRight, activeRight + (hoverRight - activeRight) * pull);
|
|
22
|
+
return { left, width: right - left };
|
|
23
|
+
}
|
|
24
|
+
var labelBox = "flex items-center gap-2.5 px-5 py-1.5 font-semibold text-[13px]";
|
|
25
|
+
var badgeBox = "font-bold text-(--color-accent-brand-soft) text-[11px] whitespace-nowrap";
|
|
26
|
+
function Tabs({ tabs, value, onChange, label, className }) {
|
|
27
|
+
const reduced = useReducedMotion();
|
|
28
|
+
const pull = reduced ? 0 : PULL;
|
|
29
|
+
const trackRef = useRef(null);
|
|
30
|
+
const [rects, setRects] = useState([]);
|
|
31
|
+
const [trackWidth, setTrackWidth] = useState(0);
|
|
32
|
+
const [hovered, setHovered] = useState(null);
|
|
33
|
+
const [ready, setReady] = useState(false);
|
|
34
|
+
const foundIdx = tabs.findIndex((t) => t.id === value);
|
|
35
|
+
const activeIdx = foundIdx === -1 ? 0 : foundIdx;
|
|
36
|
+
const springConfig = reduced ? { stiffness: 700, damping: 60 } : { stiffness: 420, damping: 34, mass: 0.7 };
|
|
37
|
+
const pillLeft = useSpring(0, springConfig);
|
|
38
|
+
const pillWidth = useSpring(0, springConfig);
|
|
39
|
+
const innerX = useTransform(pillLeft, (v) => -v);
|
|
40
|
+
const measure = useCallback(() => {
|
|
41
|
+
const track = trackRef.current;
|
|
42
|
+
if (!track) return;
|
|
43
|
+
setTrackWidth(track.offsetWidth);
|
|
44
|
+
const btns = track.querySelectorAll("button[data-index]");
|
|
45
|
+
setRects(
|
|
46
|
+
Array.from(btns, (el) => ({
|
|
47
|
+
left: el.offsetLeft,
|
|
48
|
+
width: el.offsetWidth
|
|
49
|
+
}))
|
|
50
|
+
);
|
|
51
|
+
}, []);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
measure();
|
|
54
|
+
const track = trackRef.current;
|
|
55
|
+
if (!track) return;
|
|
56
|
+
const ro = new ResizeObserver(measure);
|
|
57
|
+
ro.observe(track);
|
|
58
|
+
return () => ro.disconnect();
|
|
59
|
+
}, [measure]);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (rects.length !== tabs.length) return;
|
|
62
|
+
const target = pillTarget(activeIdx, hovered, rects, pull);
|
|
63
|
+
if (ready) {
|
|
64
|
+
pillLeft.set(target.left);
|
|
65
|
+
pillWidth.set(target.width);
|
|
66
|
+
} else {
|
|
67
|
+
pillLeft.jump(target.left);
|
|
68
|
+
pillWidth.jump(target.width);
|
|
69
|
+
setReady(true);
|
|
70
|
+
}
|
|
71
|
+
}, [rects, tabs.length, activeIdx, hovered, pull, ready, pillLeft, pillWidth]);
|
|
72
|
+
const handleClick = useCallback(
|
|
73
|
+
(e) => onChange(tabs[Number(e.currentTarget.dataset.index)].id),
|
|
74
|
+
[onChange, tabs]
|
|
75
|
+
);
|
|
76
|
+
const handleEnter = useCallback((e) => setHovered(Number(e.currentTarget.dataset.index)), []);
|
|
77
|
+
const handleLeave = useCallback(() => setHovered(null), []);
|
|
78
|
+
return /* @__PURE__ */ jsx(
|
|
79
|
+
"fieldset",
|
|
80
|
+
{
|
|
81
|
+
"aria-label": label,
|
|
82
|
+
className: cn("inline-flex items-center rounded-full border border-(--color-border) bg-(--color-surface) p-1", className),
|
|
83
|
+
children: /* @__PURE__ */ jsxs("div", { ref: trackRef, className: "relative inline-flex items-center", children: [
|
|
84
|
+
tabs.map((tab, i) => /* @__PURE__ */ jsxs(
|
|
85
|
+
"button",
|
|
86
|
+
{
|
|
87
|
+
type: "button",
|
|
88
|
+
"data-index": i,
|
|
89
|
+
onClick: handleClick,
|
|
90
|
+
onMouseEnter: handleEnter,
|
|
91
|
+
onMouseLeave: handleLeave,
|
|
92
|
+
"aria-pressed": value === tab.id,
|
|
93
|
+
className: cn(labelBox, "text-(--color-muted-foreground) hover:text-(--color-foreground)"),
|
|
94
|
+
children: [
|
|
95
|
+
tab.label,
|
|
96
|
+
tab.badge ? /* @__PURE__ */ jsx("span", { className: badgeBox, children: tab.badge }) : null
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
tab.id
|
|
100
|
+
)),
|
|
101
|
+
/* @__PURE__ */ jsx(
|
|
102
|
+
motion.div,
|
|
103
|
+
{
|
|
104
|
+
"aria-hidden": true,
|
|
105
|
+
className: "pointer-events-none absolute inset-y-0 select-none overflow-hidden rounded-full bg-(--color-foreground)",
|
|
106
|
+
style: { left: pillLeft, width: pillWidth, opacity: ready ? 1 : 0 },
|
|
107
|
+
children: /* @__PURE__ */ jsx(motion.div, { className: "absolute inset-y-0 left-0 flex items-center", style: { width: trackWidth, x: innerX }, children: tabs.map((tab) => /* @__PURE__ */ jsxs("span", { className: cn(labelBox, "text-(--color-surface)"), children: [
|
|
108
|
+
tab.label,
|
|
109
|
+
tab.badge ? /* @__PURE__ */ jsx("span", { className: badgeBox, children: tab.badge }) : null
|
|
110
|
+
] }, tab.id)) })
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
] })
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export { Tabs };
|
|
3
119
|
//# sourceMappingURL=tabs.mjs.map
|
|
4
120
|
//# sourceMappingURL=tabs.mjs.map
|
package/dist/tabs.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"tabs.mjs"}
|
|
1
|
+
{"version":3,"sources":["../src/lib/utils.ts","../src/components/tabs.tsx"],"names":[],"mappings":"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","file":"tabs.mjs","sourcesContent":["import { type ClassValue, clsx } from 'clsx';\nimport type { CSSProperties } from 'react';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\n/**\n * A style object that may carry CSS custom properties (CSS variables)\n * such as `--duration`. React's `CSSProperties` intentionally drops\n * its index signature, so custom properties are not accepted without help.\n */\nexport type CSSPropertiesWithVars = CSSProperties & Record<`--${string}`, string | number>;\n\n/**\n * Builds a {@link CSSProperties} value that also allows CSS custom properties,\n * so `style` props can be written without an `as React.CSSProperties` cast while\n * still type-checking both the known style keys and the custom-property values.\n *\n * @example\n * <div style={validCssVars({ '--duration': '0.35s', color: 'red' })} />\n */\nexport function validCssVars(style: CSSPropertiesWithVars): CSSProperties {\n return style;\n}\n\n/**\n * Narrows a union to the member(s) that declare `key` — a type-guard\n * replacement for the `in` operator's discriminated-union narrowing.\n */\nexport function hasKey<K extends PropertyKey>(key: K, obj: unknown): obj is Record<K, unknown> {\n return obj !== null && typeof obj === 'object' && Object.hasOwn(obj, key);\n}\n\nexport function noop(): void {\n // intentional no-op\n}\n","'use client';\n\nimport { motion, useReducedMotion, useSpring, useTransform } from 'framer-motion';\nimport { type MouseEvent, useCallback, useEffect, useRef, useState } from 'react';\nimport { cn } from '../lib/utils.js';\n\n/* ── Tabs ───────────────────────────────────────────────────────────────\n * A segmented control whose active pill slides between tabs and, while an\n * inactive tab is hovered, stretches (\"pulls\") toward the cursor for a\n * magnetic feel. A clipped, inverted duplicate of the labels stays glued to\n * the base labels underneath, so text flips colour exactly as the pill\n * passes over it — including the partial reveal during the pull.\n *\n * Single-select only: it switches one value, so the buttons carry\n * `aria-pressed` rather than the tab/tabpanel roles, which would promise\n * associated panels and arrow-key navigation this control doesn't provide.\n */\n\n/** One segment. `id` is the value handed back to `onChange`. */\nexport type Tab<T extends string = string> = {\n id: T;\n label: string;\n badge?: string;\n};\n\n// How far the pill reaches toward the hovered tab (fraction of the gap).\nconst PULL = 0.035;\n\ntype Rect = { left: number; width: number };\n\n// Target pill rect: the active tab at rest, or stretched toward a hovered\n// neighbour so its leading edge reaches partway across the gap.\nfunction pillTarget(activeIdx: number, hoverIdx: number | null, rects: Rect[], pull: number): Rect {\n const active = rects[activeIdx];\n if (!active) throw new Error(`No rect at index ${activeIdx}`);\n if (hoverIdx === null || hoverIdx === activeIdx) return active;\n\n const hover = rects[hoverIdx];\n if (!hover) return active;\n const activeRight = active.left + active.width;\n const hoverRight = hover.left + hover.width;\n\n const left = Math.min(active.left, active.left + (hover.left - active.left) * pull);\n const right = Math.max(activeRight, activeRight + (hoverRight - activeRight) * pull);\n return { left, width: right - left };\n}\n\n// Shared box so the base labels and the inverted duplicate measure identically.\nconst labelBox = 'flex items-center gap-2.5 px-5 py-1.5 font-semibold text-[13px]';\nconst badgeBox = 'font-bold text-(--color-accent-brand-soft) text-[11px] whitespace-nowrap';\n\nexport type TabsProps<T extends string> = {\n tabs: readonly Tab<T>[];\n value: T;\n onChange: (v: T) => void;\n /** Accessible name for the group, e.g. \"Billing period\". */\n label: string;\n className?: string;\n};\n\nexport function Tabs<T extends string>({ tabs, value, onChange, label, className }: TabsProps<T>) {\n const reduced = useReducedMotion();\n const pull = reduced ? 0 : PULL;\n\n const trackRef = useRef<HTMLDivElement>(null);\n\n const [rects, setRects] = useState<Rect[]>([]);\n const [trackWidth, setTrackWidth] = useState(0);\n const [hovered, setHovered] = useState<number | null>(null);\n const [ready, setReady] = useState(false);\n\n const foundIdx = tabs.findIndex((t) => t.id === value);\n const activeIdx = foundIdx === -1 ? 0 : foundIdx;\n\n const springConfig = reduced ? { stiffness: 700, damping: 60 } : { stiffness: 420, damping: 34, mass: 0.7 };\n const pillLeft = useSpring(0, springConfig);\n const pillWidth = useSpring(0, springConfig);\n const innerX = useTransform(pillLeft, (v) => -v);\n\n const measure = useCallback(() => {\n const track = trackRef.current;\n if (!track) return;\n setTrackWidth(track.offsetWidth);\n const btns = track.querySelectorAll<HTMLButtonElement>('button[data-index]');\n setRects(\n Array.from(btns, (el) => ({\n left: el.offsetLeft,\n width: el.offsetWidth,\n })),\n );\n }, []);\n\n // biome-ignore lint/plugin: measures live DOM layout under a ResizeObserver — post-render measurement, no render-time equivalent.\n useEffect(() => {\n measure();\n const track = trackRef.current;\n if (!track) return;\n const ro = new ResizeObserver(measure);\n ro.observe(track);\n return () => ro.disconnect();\n }, [measure]);\n\n // biome-ignore lint/plugin: imperatively drives the pill's spring motion values toward a layout-derived target.\n useEffect(() => {\n if (rects.length !== tabs.length) return;\n const target = pillTarget(activeIdx, hovered, rects, pull);\n if (ready) {\n pillLeft.set(target.left);\n pillWidth.set(target.width);\n } else {\n pillLeft.jump(target.left);\n pillWidth.jump(target.width);\n setReady(true);\n }\n }, [rects, tabs.length, activeIdx, hovered, pull, ready, pillLeft, pillWidth]);\n\n const handleClick = useCallback(\n (e: MouseEvent<HTMLButtonElement>) => onChange(tabs[Number(e.currentTarget.dataset.index)]!.id),\n [onChange, tabs],\n );\n const handleEnter = useCallback((e: MouseEvent<HTMLButtonElement>) => setHovered(Number(e.currentTarget.dataset.index)), []);\n const handleLeave = useCallback(() => setHovered(null), []);\n\n return (\n <fieldset\n aria-label={label}\n className={cn('inline-flex items-center rounded-full border border-(--color-border) bg-(--color-surface) p-1', className)}\n >\n <div ref={trackRef} className=\"relative inline-flex items-center\">\n {/* Base labels — muted at rest; hidden wherever the opaque pill sits over them. */}\n {tabs.map((tab, i) => (\n <button\n key={tab.id}\n type=\"button\"\n data-index={i}\n onClick={handleClick}\n onMouseEnter={handleEnter}\n onMouseLeave={handleLeave}\n aria-pressed={value === tab.id}\n className={cn(labelBox, 'text-(--color-muted-foreground) hover:text-(--color-foreground)')}\n >\n {tab.label}\n {tab.badge ? <span className={badgeBox}>{tab.badge}</span> : null}\n </button>\n ))}\n\n {/* Sliding, pulling pill carrying a clipped inverted copy of the labels. */}\n <motion.div\n aria-hidden={true}\n className=\"pointer-events-none absolute inset-y-0 select-none overflow-hidden rounded-full bg-(--color-foreground)\"\n style={{ left: pillLeft, width: pillWidth, opacity: ready ? 1 : 0 }}\n >\n <motion.div className=\"absolute inset-y-0 left-0 flex items-center\" style={{ width: trackWidth, x: innerX }}>\n {tabs.map((tab) => (\n <span key={tab.id} className={cn(labelBox, 'text-(--color-surface)')}>\n {tab.label}\n {tab.badge ? <span className={badgeBox}>{tab.badge}</span> : null}\n </span>\n ))}\n </motion.div>\n </motion.div>\n </div>\n </fieldset>\n );\n}\n"]}
|
package/dist/theme-toggle.js
CHANGED
package/dist/theme-toggle.mjs
CHANGED
|
@@ -1,7 +1,250 @@
|
|
|
1
|
-
|
|
2
|
-
import '
|
|
3
|
-
import '
|
|
4
|
-
import '
|
|
5
|
-
import '
|
|
1
|
+
"use client";
|
|
2
|
+
import { useReducedMotion, AnimatePresence, motion } from 'framer-motion';
|
|
3
|
+
import { useRef, useState, useLayoutEffect } from 'react';
|
|
4
|
+
import { clsx } from 'clsx';
|
|
5
|
+
import { twMerge } from 'tailwind-merge';
|
|
6
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
7
|
+
import { cva } from 'class-variance-authority';
|
|
8
|
+
|
|
9
|
+
// src/lib/ease.ts
|
|
10
|
+
var EASE_OUT = [0.16, 1, 0.3, 1];
|
|
11
|
+
var EASE_OUT_CSS = "cubic-bezier(0.16, 1, 0.3, 1)";
|
|
12
|
+
var SPRING_SWAP = { type: "spring", stiffness: 460, damping: 30, mass: 0.55 };
|
|
13
|
+
function cn(...inputs) {
|
|
14
|
+
return twMerge(clsx(inputs));
|
|
15
|
+
}
|
|
16
|
+
var ROLL_EXIT_TRANSITION = { duration: 0.14, ease: EASE_OUT };
|
|
17
|
+
var ROLL_BLUR = "blur(3px)";
|
|
18
|
+
var AT_REST = { opacity: 1, y: 0, filter: "blur(0px)" };
|
|
19
|
+
var TEXT_VARIANTS = {
|
|
20
|
+
initial: { opacity: 0, y: "90%", filter: ROLL_BLUR },
|
|
21
|
+
animate: { opacity: 1, y: "0%", filter: "blur(0px)", transition: SPRING_SWAP },
|
|
22
|
+
exit: { opacity: 0, y: "-90%", filter: ROLL_BLUR, transition: ROLL_EXIT_TRANSITION }
|
|
23
|
+
};
|
|
24
|
+
var ICON_VARIANTS = {
|
|
25
|
+
initial: { opacity: 0, y: 12, filter: ROLL_BLUR },
|
|
26
|
+
animate: { opacity: 1, y: 0, filter: "blur(0px)", transition: SPRING_SWAP },
|
|
27
|
+
exit: { opacity: 0, y: -12, filter: ROLL_BLUR, transition: ROLL_EXIT_TRANSITION }
|
|
28
|
+
};
|
|
29
|
+
function ActionSwapRollText({ value, children, className }) {
|
|
30
|
+
const reduce = useReducedMotion() ?? false;
|
|
31
|
+
const measureRef = useRef(null);
|
|
32
|
+
const [width, setWidth] = useState();
|
|
33
|
+
useLayoutEffect(() => {
|
|
34
|
+
const nextWidth = measureRef.current?.offsetWidth;
|
|
35
|
+
if (!nextWidth) return;
|
|
36
|
+
setWidth((current) => current === nextWidth ? current : nextWidth);
|
|
37
|
+
});
|
|
38
|
+
return /* @__PURE__ */ jsxs(
|
|
39
|
+
"span",
|
|
40
|
+
{
|
|
41
|
+
className: cn("relative inline-block overflow-hidden whitespace-nowrap align-bottom", className),
|
|
42
|
+
style: { width, transition: reduce ? void 0 : `width 220ms ${EASE_OUT_CSS}` },
|
|
43
|
+
children: [
|
|
44
|
+
/* @__PURE__ */ jsx("span", { ref: measureRef, "aria-hidden": true, className: "invisible inline-block whitespace-nowrap", children }),
|
|
45
|
+
/* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: /* @__PURE__ */ jsx(
|
|
46
|
+
motion.span,
|
|
47
|
+
{
|
|
48
|
+
variants: TEXT_VARIANTS,
|
|
49
|
+
initial: reduce ? false : "initial",
|
|
50
|
+
animate: reduce ? AT_REST : "animate",
|
|
51
|
+
exit: reduce ? void 0 : "exit",
|
|
52
|
+
className: "absolute top-0 left-0 inline-block will-change-[opacity,filter,transform]",
|
|
53
|
+
children
|
|
54
|
+
},
|
|
55
|
+
value
|
|
56
|
+
) })
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
function ActionSwapRollIcon({ value, children, className }) {
|
|
62
|
+
const reduce = useReducedMotion() ?? false;
|
|
63
|
+
return /* @__PURE__ */ jsx("span", { className: cn("relative inline-grid shrink-0 place-items-center overflow-hidden", className), children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "popLayout", initial: false, children: /* @__PURE__ */ jsx(
|
|
64
|
+
motion.span,
|
|
65
|
+
{
|
|
66
|
+
"aria-hidden": true,
|
|
67
|
+
variants: ICON_VARIANTS,
|
|
68
|
+
initial: reduce ? false : "initial",
|
|
69
|
+
animate: reduce ? AT_REST : "animate",
|
|
70
|
+
exit: reduce ? void 0 : "exit",
|
|
71
|
+
className: "col-start-1 row-start-1 inline-flex items-center justify-center will-change-[opacity,filter,transform]",
|
|
72
|
+
children
|
|
73
|
+
},
|
|
74
|
+
value
|
|
75
|
+
) }) });
|
|
76
|
+
}
|
|
77
|
+
var buttonVariants = cva(
|
|
78
|
+
"group inline-flex items-center justify-center gap-1.5 font-medium [transition:opacity_.24s_80ms,transform_.15s_cubic-bezier(.4,0,.2,1),box-shadow_.15s_cubic-bezier(.4,0,.2,1),filter_.15s_cubic-bezier(.4,0,.2,1)] disabled:[transition:opacity_.1s]",
|
|
79
|
+
{
|
|
80
|
+
variants: {
|
|
81
|
+
variant: {
|
|
82
|
+
filled: "bg-(--foreground) text-(--primary-foreground) shadow-elevated-3 hover:opacity-90",
|
|
83
|
+
bordered: "border border-(--border-hi) bg-surface-3 text-foreground shadow-elevated-3 hover:bg-(--muted)",
|
|
84
|
+
ghost: "text-(--foreground-secondary) hover:bg-(--surface-hover) hover:text-(--foreground)"
|
|
85
|
+
},
|
|
86
|
+
size: {
|
|
87
|
+
sm: "h-8 px-3.5 text-[15px]",
|
|
88
|
+
md: "h-9 px-5 text-[17px]",
|
|
89
|
+
lg: "h-11 px-6 text-[17px]",
|
|
90
|
+
icon: "size-11"
|
|
91
|
+
},
|
|
92
|
+
shape: {
|
|
93
|
+
rounded: "rounded-interactive",
|
|
94
|
+
pill: "rounded-full"
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
// Pill is the house standard — every CTA is fully rounded.
|
|
98
|
+
defaultVariants: { variant: "filled", size: "md", shape: "pill" }
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
function Button({
|
|
102
|
+
variant,
|
|
103
|
+
size,
|
|
104
|
+
shape,
|
|
105
|
+
showArrow = false,
|
|
106
|
+
external = false,
|
|
107
|
+
title,
|
|
108
|
+
actionOrLink,
|
|
109
|
+
children,
|
|
110
|
+
className,
|
|
111
|
+
"aria-label": ariaLabel,
|
|
112
|
+
"aria-expanded": ariaExpanded
|
|
113
|
+
}) {
|
|
114
|
+
const buttonClassName = cn(buttonVariants({ variant, size, shape }), className);
|
|
115
|
+
const arrowSlot = showArrow && /* @__PURE__ */ jsxs(
|
|
116
|
+
"svg",
|
|
117
|
+
{
|
|
118
|
+
viewBox: "0 0 14 14",
|
|
119
|
+
width: "14",
|
|
120
|
+
height: "14",
|
|
121
|
+
fill: "none",
|
|
122
|
+
stroke: "currentColor",
|
|
123
|
+
strokeWidth: "2",
|
|
124
|
+
strokeLinecap: "round",
|
|
125
|
+
strokeLinejoin: "round",
|
|
126
|
+
"aria-hidden": true,
|
|
127
|
+
className: "shrink-0",
|
|
128
|
+
children: [
|
|
129
|
+
/* @__PURE__ */ jsx(
|
|
130
|
+
"line",
|
|
131
|
+
{
|
|
132
|
+
x1: "0",
|
|
133
|
+
y1: "7",
|
|
134
|
+
x2: "9",
|
|
135
|
+
y2: "7",
|
|
136
|
+
strokeDasharray: "9",
|
|
137
|
+
className: "[stroke-dashoffset:9] [transition:stroke-dashoffset_.08s_cubic-bezier(.22,1,.36,1)] group-hover:[stroke-dashoffset:0] group-hover:[transition-duration:.22s]"
|
|
138
|
+
}
|
|
139
|
+
),
|
|
140
|
+
/* @__PURE__ */ jsx(
|
|
141
|
+
"path",
|
|
142
|
+
{
|
|
143
|
+
d: "M5 3l4 4-4 4",
|
|
144
|
+
className: "[transform:translateX(0)] [transition:transform_.22s_cubic-bezier(.22,1,.36,1)] group-hover:[transform:translateX(2px)]"
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
if (typeof actionOrLink === "string")
|
|
151
|
+
return /* @__PURE__ */ jsxs(
|
|
152
|
+
"a",
|
|
153
|
+
{
|
|
154
|
+
href: actionOrLink,
|
|
155
|
+
className: cn(buttonClassName, "no-underline"),
|
|
156
|
+
"aria-label": ariaLabel,
|
|
157
|
+
title,
|
|
158
|
+
...external ? { target: "_blank", rel: "noopener noreferrer" } : {},
|
|
159
|
+
children: [
|
|
160
|
+
children,
|
|
161
|
+
arrowSlot
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
return /* @__PURE__ */ jsxs(
|
|
166
|
+
"button",
|
|
167
|
+
{
|
|
168
|
+
type: "button",
|
|
169
|
+
className: buttonClassName,
|
|
170
|
+
onClick: actionOrLink,
|
|
171
|
+
"aria-label": ariaLabel,
|
|
172
|
+
"aria-expanded": ariaExpanded,
|
|
173
|
+
title,
|
|
174
|
+
children: [
|
|
175
|
+
children,
|
|
176
|
+
arrowSlot
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
var ICON_LAYOUT = "w-8 p-0";
|
|
182
|
+
var LABELED_LAYOUT = "gap-2 px-3 text-[13px]";
|
|
183
|
+
function SunIcon() {
|
|
184
|
+
return /* @__PURE__ */ jsxs(
|
|
185
|
+
"svg",
|
|
186
|
+
{
|
|
187
|
+
width: 14,
|
|
188
|
+
height: 14,
|
|
189
|
+
viewBox: "0 0 24 24",
|
|
190
|
+
fill: "none",
|
|
191
|
+
stroke: "currentColor",
|
|
192
|
+
strokeWidth: 2,
|
|
193
|
+
strokeLinecap: "round",
|
|
194
|
+
strokeLinejoin: "round",
|
|
195
|
+
"aria-hidden": true,
|
|
196
|
+
children: [
|
|
197
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "4" }),
|
|
198
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "3", x2: "12", y2: "1" }),
|
|
199
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "23", x2: "12", y2: "21" }),
|
|
200
|
+
/* @__PURE__ */ jsx("line", { x1: "3", y1: "12", x2: "1", y2: "12" }),
|
|
201
|
+
/* @__PURE__ */ jsx("line", { x1: "23", y1: "12", x2: "21", y2: "12" }),
|
|
202
|
+
/* @__PURE__ */ jsx("line", { x1: "5", y1: "5", x2: "3.5", y2: "3.5" }),
|
|
203
|
+
/* @__PURE__ */ jsx("line", { x1: "20.5", y1: "20.5", x2: "19", y2: "19" }),
|
|
204
|
+
/* @__PURE__ */ jsx("line", { x1: "19", y1: "5", x2: "20.5", y2: "3.5" }),
|
|
205
|
+
/* @__PURE__ */ jsx("line", { x1: "4.5", y1: "20.5", x2: "3", y2: "19" })
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
function MoonIcon() {
|
|
211
|
+
return /* @__PURE__ */ jsx(
|
|
212
|
+
"svg",
|
|
213
|
+
{
|
|
214
|
+
width: 14,
|
|
215
|
+
height: 14,
|
|
216
|
+
viewBox: "0 0 24 24",
|
|
217
|
+
fill: "none",
|
|
218
|
+
stroke: "currentColor",
|
|
219
|
+
strokeWidth: 2,
|
|
220
|
+
strokeLinecap: "round",
|
|
221
|
+
strokeLinejoin: "round",
|
|
222
|
+
"aria-hidden": true,
|
|
223
|
+
children: /* @__PURE__ */ jsx("path", { d: "M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8z" })
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
function ThemeToggle({ theme, onToggle, showLabel = false }) {
|
|
228
|
+
const size = showLabel ? "md" : "sm";
|
|
229
|
+
const layoutClassName = showLabel ? LABELED_LAYOUT : ICON_LAYOUT;
|
|
230
|
+
const isDark = theme === "dark";
|
|
231
|
+
const label = isDark ? "Switch to light mode" : "Switch to dark mode";
|
|
232
|
+
return /* @__PURE__ */ jsxs(
|
|
233
|
+
Button,
|
|
234
|
+
{
|
|
235
|
+
variant: "filled",
|
|
236
|
+
size,
|
|
237
|
+
actionOrLink: onToggle,
|
|
238
|
+
className: layoutClassName,
|
|
239
|
+
"aria-label": label,
|
|
240
|
+
children: [
|
|
241
|
+
/* @__PURE__ */ jsx(ActionSwapRollIcon, { value: theme, className: "h-4 w-4", children: isDark ? /* @__PURE__ */ jsx(SunIcon, {}) : /* @__PURE__ */ jsx(MoonIcon, {}) }),
|
|
242
|
+
showLabel ? /* @__PURE__ */ jsx(ActionSwapRollText, { value: theme, children: label }) : null
|
|
243
|
+
]
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export { ThemeToggle };
|
|
6
249
|
//# sourceMappingURL=theme-toggle.mjs.map
|
|
7
250
|
//# sourceMappingURL=theme-toggle.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"theme-toggle.mjs"}
|
|
1
|
+
{"version":3,"sources":["../src/lib/ease.ts","../src/lib/utils.ts","../src/components/action-swap.tsx","../src/components/button.tsx","../src/components/theme-toggle.tsx"],"names":["jsxs","jsx"],"mappings":"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","file":"theme-toggle.mjs","sourcesContent":["/**\n * Easing curves shared across landing-ui components.\n */\n\n/** Smooth deceleration — matches the former CSS `ease-out-expo`. */\nexport const easeOut = [0.22, 1, 0.36, 1] as const;\n\n/** Gentle overshoot — bounce-like settle for spring alternatives. */\nexport const easeOutBack = [0.34, 1.56, 0.64, 1] as const;\n\n// ── Content-swap tokens (action-swap) ─────────────────────────────────────\n// Values mirror beui.dev's `lib/ease.ts` so vendored action-swap primitives\n// keep their upstream timing.\n\n/** Strong ease-out — the default `ease-out` reads weak at these durations. */\nexport const EASE_OUT = [0.16, 1, 0.3, 1] as const;\n\n/** CSS string form of {@link EASE_OUT} for inline style transitions. */\nexport const EASE_OUT_CSS = 'cubic-bezier(0.16, 1, 0.3, 1)';\n\n/** Content swaps — label/icon slots trading places inside a control. */\nexport const SPRING_SWAP = { type: 'spring', stiffness: 460, damping: 30, mass: 0.55 } as const;\n","import { type ClassValue, clsx } from 'clsx';\nimport type { CSSProperties } from 'react';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\n/**\n * A style object that may carry CSS custom properties (CSS variables)\n * such as `--duration`. React's `CSSProperties` intentionally drops\n * its index signature, so custom properties are not accepted without help.\n */\nexport type CSSPropertiesWithVars = CSSProperties & Record<`--${string}`, string | number>;\n\n/**\n * Builds a {@link CSSProperties} value that also allows CSS custom properties,\n * so `style` props can be written without an `as React.CSSProperties` cast while\n * still type-checking both the known style keys and the custom-property values.\n *\n * @example\n * <div style={validCssVars({ '--duration': '0.35s', color: 'red' })} />\n */\nexport function validCssVars(style: CSSPropertiesWithVars): CSSProperties {\n return style;\n}\n\n/**\n * Narrows a union to the member(s) that declare `key` — a type-guard\n * replacement for the `in` operator's discriminated-union narrowing.\n */\nexport function hasKey<K extends PropertyKey>(key: K, obj: unknown): obj is Record<K, unknown> {\n return obj !== null && typeof obj === 'object' && Object.hasOwn(obj, key);\n}\n\nexport function noop(): void {\n // intentional no-op\n}\n","'use client';\n// Adapted from beui.dev/components/motion/action-swap (roll variant only).\n// Upstream imports `motion/react`; this project is on framer-motion, and its\n// blur/cascade animations and pre-styled <ActionSwapButton> are omitted — the\n// button's variant classes reference `bg-card`/`bg-primary`, which this site\n// doesn't define as --color-* tokens, so Tailwind would drop them silently.\n\nimport { AnimatePresence, motion, useReducedMotion, type Variants } from 'framer-motion';\nimport { type ReactNode, useLayoutEffect, useRef, useState } from 'react';\nimport { EASE_OUT, EASE_OUT_CSS, SPRING_SWAP } from '../lib/ease.js';\nimport { cn } from '../lib/utils.js';\n\n/** The outgoing half rolls out faster than the incoming half settles. */\nconst ROLL_EXIT_TRANSITION = { duration: 0.14, ease: EASE_OUT } as const;\nconst ROLL_BLUR = 'blur(3px)';\n\n/** Rest state used under reduced motion — no travel, no blur. */\nconst AT_REST = { opacity: 1, y: 0, filter: 'blur(0px)' } as const;\n\n/** Text travels in percentages so the roll scales with the line height. */\nconst TEXT_VARIANTS: Variants = {\n initial: { opacity: 0, y: '90%', filter: ROLL_BLUR },\n animate: { opacity: 1, y: '0%', filter: 'blur(0px)', transition: SPRING_SWAP },\n exit: { opacity: 0, y: '-90%', filter: ROLL_BLUR, transition: ROLL_EXIT_TRANSITION },\n};\n\n/** Icons travel in pixels — a glyph box has no line height to scale against. */\nconst ICON_VARIANTS: Variants = {\n initial: { opacity: 0, y: 12, filter: ROLL_BLUR },\n animate: { opacity: 1, y: 0, filter: 'blur(0px)', transition: SPRING_SWAP },\n exit: { opacity: 0, y: -12, filter: ROLL_BLUR, transition: ROLL_EXIT_TRANSITION },\n};\n\nexport type ActionSwapRollProps = {\n /** Identity of the current content — changing it triggers the roll. */\n value: string;\n children: ReactNode;\n className?: string;\n};\n\n/**\n * Rolls `children` up out of the box while the new content rolls in from below,\n * animating the box's width so labels of different lengths don't snap.\n *\n * The visible label is the accessible name, so for the duration of a swap both\n * the outgoing and incoming text are in the accessibility tree. Give the host\n * control an explicit `aria-label` when that ambiguity matters.\n */\nexport function ActionSwapRollText({ value, children, className }: ActionSwapRollProps) {\n const reduce = useReducedMotion() ?? false;\n const measureRef = useRef<HTMLSpanElement>(null);\n const [width, setWidth] = useState<number>();\n\n // Deliberately un-keyed: the invisible copy is re-measured after every render\n // so the box tracks content it can't diff (fonts finishing, text changing).\n useLayoutEffect(() => {\n const nextWidth = measureRef.current?.offsetWidth;\n if (!nextWidth) return;\n setWidth((current) => (current === nextWidth ? current : nextWidth));\n });\n\n return (\n <span\n className={cn('relative inline-block overflow-hidden whitespace-nowrap align-bottom', className)}\n style={{ width, transition: reduce ? undefined : `width 220ms ${EASE_OUT_CSS}` }}\n >\n {/* Width source: laid out in flow but never painted, so the absolutely\n positioned live copies below have something to size the box. */}\n <span ref={measureRef} aria-hidden={true} className=\"invisible inline-block whitespace-nowrap\">\n {children}\n </span>\n <AnimatePresence initial={false}>\n <motion.span\n key={value}\n variants={TEXT_VARIANTS}\n initial={reduce ? false : 'initial'}\n animate={reduce ? AT_REST : 'animate'}\n exit={reduce ? undefined : 'exit'}\n className=\"absolute top-0 left-0 inline-block will-change-[opacity,filter,transform]\"\n >\n {children}\n </motion.span>\n </AnimatePresence>\n </span>\n );\n}\n\n/**\n * Icon counterpart to {@link ActionSwapRollText}. `popLayout` pulls the outgoing\n * icon out of flow so the pair overlaps in one cell instead of sitting side by\n * side mid-swap. Always `aria-hidden` — icons here restate a visible label.\n */\nexport function ActionSwapRollIcon({ value, children, className }: ActionSwapRollProps) {\n const reduce = useReducedMotion() ?? false;\n\n return (\n <span className={cn('relative inline-grid shrink-0 place-items-center overflow-hidden', className)}>\n <AnimatePresence mode=\"popLayout\" initial={false}>\n <motion.span\n key={value}\n aria-hidden={true}\n variants={ICON_VARIANTS}\n initial={reduce ? false : 'initial'}\n animate={reduce ? AT_REST : 'animate'}\n exit={reduce ? undefined : 'exit'}\n className=\"col-start-1 row-start-1 inline-flex items-center justify-center will-change-[opacity,filter,transform]\"\n >\n {children}\n </motion.span>\n </AnimatePresence>\n </span>\n );\n}\n","import { cva, type VariantProps } from 'class-variance-authority';\nimport type { ReactNode } from 'react';\nimport { cn } from '../lib/utils.js';\n\n/**\n * Shared marketing-site button styles.\n *\n * The `transition` in the base string matches the former `.button-transition`\n * utility — a pure-CSS morph so the component stays server-renderable.\n */\nconst buttonVariants = cva(\n 'group inline-flex items-center justify-center gap-1.5 font-medium [transition:opacity_.24s_80ms,transform_.15s_cubic-bezier(.4,0,.2,1),box-shadow_.15s_cubic-bezier(.4,0,.2,1),filter_.15s_cubic-bezier(.4,0,.2,1)] disabled:[transition:opacity_.1s]',\n {\n variants: {\n variant: {\n filled: 'bg-(--foreground) text-(--primary-foreground) shadow-elevated-3 hover:opacity-90',\n bordered: 'border border-(--border-hi) bg-surface-3 text-foreground shadow-elevated-3 hover:bg-(--muted)',\n ghost: 'text-(--foreground-secondary) hover:bg-(--surface-hover) hover:text-(--foreground)',\n },\n size: {\n sm: 'h-8 px-3.5 text-[15px]',\n md: 'h-9 px-5 text-[17px]',\n lg: 'h-11 px-6 text-[17px]',\n icon: 'size-11',\n },\n shape: {\n rounded: 'rounded-interactive',\n pill: 'rounded-full',\n },\n },\n // Pill is the house standard — every CTA is fully rounded.\n defaultVariants: { variant: 'filled', size: 'md', shape: 'pill' },\n },\n);\n\nexport type ButtonProps = VariantProps<typeof buttonVariants> & {\n showArrow?: boolean;\n /**\n * Set when `actionOrLink` is an off-site URL — renders the anchor with\n * `target=\"_blank\"` and `rel=\"noopener noreferrer\"`.\n */\n external?: boolean;\n /** Hover tooltip on the rendered anchor/button. */\n title?: string;\n /**\n * String → renders as a plain anchor <a href={...}>.\n * Function → renders as a <button type=\"button\" onClick={...}>.\n */\n actionOrLink: string | (() => void);\n children: ReactNode;\n className?: string;\n 'aria-label'?: string;\n 'aria-expanded'?: boolean;\n};\n\n/**\n * Shared marketing-site button.\n *\n * variant=\"filled\" → primary CTA (inverse theming: bg-(--foreground) + text-(--primary-foreground)\n * + shadow-elevated-3 — flips with the theme so it pops against any surface)\n * variant=\"bordered\" → elevated surface + border (bg-surface-3 + shadow-elevated-3 + border)\n * variant=\"ghost\" → transparent, muted text that gains a surface on hover\n * size=\"sm\" → h-8, px-3.5, text-[15px] (navbar / compact)\n * size=\"md\" → h-9, px-5, text-[17px] (default)\n * size=\"lg\" → h-11, px-6, text-[17px] (hero CTAs)\n * size=\"icon\" → size-11 square (icon-only buttons)\n * shape=\"pill\" → fully rounded (rounded-full); pill is the default\n * showArrow → chevron right at rest, morphs to arrow-right on hover\n *\n * Server-renderable: the arrow morph is pure CSS (group-hover), so no client\n * state or animation library is needed. When `actionOrLink` is a function the\n * component renders a <button>; passing a function requires a client-component\n * parent (functions can't cross the server→client boundary), which is inherent\n * to any onClick prop and unchanged by this component being server-capable.\n */\nexport function Button({\n variant,\n size,\n shape,\n showArrow = false,\n external = false,\n title,\n actionOrLink,\n children,\n className,\n 'aria-label': ariaLabel,\n 'aria-expanded': ariaExpanded,\n}: ButtonProps) {\n const buttonClassName = cn(buttonVariants({ variant, size, shape }), className);\n\n /*\n * Single SVG that morphs from › to → on hover — pure CSS, no JS.\n * The shaft draws in from the left (stroke-dashoffset 9 → 0) and the arrowhead\n * nudges right. Draw-in is slower (.22s) than the reverse (.08s), matching the\n * asymmetric timing of the former framer-motion version. ease = cubic-bezier(.22,1,.36,1).\n */\n const arrowSlot = showArrow && (\n <svg\n viewBox=\"0 0 14 14\"\n width=\"14\"\n height=\"14\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden={true}\n className=\"shrink-0\"\n >\n {/* Shaft — draws in from the left */}\n <line\n x1=\"0\"\n y1=\"7\"\n x2=\"9\"\n y2=\"7\"\n strokeDasharray=\"9\"\n className=\"[stroke-dashoffset:9] [transition:stroke-dashoffset_.08s_cubic-bezier(.22,1,.36,1)] group-hover:[stroke-dashoffset:0] group-hover:[transition-duration:.22s]\"\n />\n {/* Arrowhead — nudges right so the shaft \"connects\" underneath */}\n <path\n d=\"M5 3l4 4-4 4\"\n className=\"[transform:translateX(0)] [transition:transform_.22s_cubic-bezier(.22,1,.36,1)] group-hover:[transform:translateX(2px)]\"\n />\n </svg>\n );\n\n if (typeof actionOrLink === 'string')\n return (\n <a\n href={actionOrLink}\n className={cn(buttonClassName, 'no-underline')}\n aria-label={ariaLabel}\n title={title}\n {...(external ? { target: '_blank', rel: 'noopener noreferrer' } : {})}\n >\n {children}\n {arrowSlot}\n </a>\n );\n\n return (\n <button\n type=\"button\"\n className={buttonClassName}\n onClick={actionOrLink}\n aria-label={ariaLabel}\n aria-expanded={ariaExpanded}\n title={title}\n >\n {children}\n {arrowSlot}\n </button>\n );\n}\n","'use client';\n\nimport { ActionSwapRollIcon, ActionSwapRollText } from './action-swap.js';\nimport { Button } from './button.js';\n\n/*\n * Controlled theme toggle: the parent owns the current theme and the flip\n * handler, so there's no next-themes, no hydration detection, and no\n * pre-hydration CSS fallback — the theme is always known and the roll renders\n * directly on first paint.\n */\n\n// Icon-only (navbar / footer) sits on the `sm` height; the labeled row (mobile\n// drawer) on `md`. The `w-8 p-0` / `gap-2 px-3 text-[13px]` overrides turn the\n// shared <Button> sizes into the toggle's square and labeled geometries.\nconst ICON_LAYOUT = 'w-8 p-0';\nconst LABELED_LAYOUT = 'gap-2 px-3 text-[13px]';\n\n/** Sun glyph — the icon shown while dark, i.e. the theme you switch *to*. */\nfunction SunIcon() {\n return (\n <svg\n width={14}\n height={14}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden={true}\n >\n <circle cx=\"12\" cy=\"12\" r=\"4\" />\n <line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"1\" />\n <line x1=\"12\" y1=\"23\" x2=\"12\" y2=\"21\" />\n <line x1=\"3\" y1=\"12\" x2=\"1\" y2=\"12\" />\n <line x1=\"23\" y1=\"12\" x2=\"21\" y2=\"12\" />\n <line x1=\"5\" y1=\"5\" x2=\"3.5\" y2=\"3.5\" />\n <line x1=\"20.5\" y1=\"20.5\" x2=\"19\" y2=\"19\" />\n <line x1=\"19\" y1=\"5\" x2=\"20.5\" y2=\"3.5\" />\n <line x1=\"4.5\" y1=\"20.5\" x2=\"3\" y2=\"19\" />\n </svg>\n );\n}\n\n/** Moon glyph — the icon shown while light, i.e. the theme you switch *to*. */\nfunction MoonIcon() {\n return (\n <svg\n width={14}\n height={14}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden={true}\n >\n <path d=\"M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8z\" />\n </svg>\n );\n}\n\nexport type ThemeToggleProps = {\n /** Current theme — drives which icon/label the roll shows. */\n theme: 'light' | 'dark';\n /** Flip handler; the parent persists the new theme. */\n onToggle: () => void;\n /** Render the labeled row (mobile drawer) instead of the icon-only square. */\n showLabel?: boolean;\n};\n\nexport function ThemeToggle({ theme, onToggle, showLabel = false }: ThemeToggleProps) {\n const size = showLabel ? 'md' : 'sm';\n const layoutClassName = showLabel ? LABELED_LAYOUT : ICON_LAYOUT;\n\n // The icon shows the theme you are switching *to*, matching the label.\n const isDark = theme === 'dark';\n const label = isDark ? 'Switch to light mode' : 'Switch to dark mode';\n\n return (\n <Button\n variant=\"filled\"\n size={size}\n actionOrLink={onToggle}\n className={layoutClassName}\n // The visible label rolls, so mid-swap both strings are briefly in the\n // tree. Naming the button explicitly keeps it unambiguous either way.\n aria-label={label}\n >\n <ActionSwapRollIcon value={theme} className=\"h-4 w-4\">\n {isDark ? <SunIcon /> : <MoonIcon />}\n </ActionSwapRollIcon>\n {showLabel ? <ActionSwapRollText value={theme}>{label}</ActionSwapRollText> : null}\n </Button>\n );\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-marketing-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Reusable landing & marketing page UI components for React — animated text reveals, shader effects, magnetic tabs, and scroll-triggered entrances.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -371,7 +371,7 @@
|
|
|
371
371
|
},
|
|
372
372
|
"scripts": {
|
|
373
373
|
"dev": "storybook dev -p 6006",
|
|
374
|
-
"build": "tsup && bun run build:types",
|
|
374
|
+
"build": "tsup && bun run build:types && bun scripts/add-client-directives.ts",
|
|
375
375
|
"build:types": "tsc -p tsconfig.build.json && bun scripts/gen-cjs-types.ts",
|
|
376
376
|
"build-storybook": "storybook build",
|
|
377
377
|
"typecheck": "tsc --noEmit",
|
package/dist/chunk-3SB6HUBA.mjs
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { cn } from './chunk-QAWSH567.mjs';
|
|
2
|
-
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
-
|
|
4
|
-
function FeatureCard({ icon, title, body, iconColor, className }) {
|
|
5
|
-
return /* @__PURE__ */ jsxs(
|
|
6
|
-
"div",
|
|
7
|
-
{
|
|
8
|
-
className: cn(
|
|
9
|
-
"flex w-full flex-col justify-between gap-6 rounded-[20px] bg-(--muted) p-6 lg:gap-8 lg:rounded-3xl lg:p-8 xl:gap-10 xl:p-10",
|
|
10
|
-
className
|
|
11
|
-
),
|
|
12
|
-
children: [
|
|
13
|
-
/* @__PURE__ */ jsx("div", { className: "flex size-10 items-center justify-center rounded-[10px] bg-(--surface) shadow-(--shadow-sm) lg:size-12 lg:rounded-md", children: /* @__PURE__ */ jsx(
|
|
14
|
-
"span",
|
|
15
|
-
{
|
|
16
|
-
className: cn("flex h-5 w-5 items-center justify-center lg:h-6 lg:w-6", iconColor ?? "text-(--foreground-secondary)"),
|
|
17
|
-
children: icon
|
|
18
|
-
}
|
|
19
|
-
) }),
|
|
20
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
21
|
-
/* @__PURE__ */ jsx("h3", { className: "font-semibold text-[15px] text-foreground lg:text-base", children: title }),
|
|
22
|
-
/* @__PURE__ */ jsx("p", { className: "text-(--foreground-secondary) text-[13px] leading-relaxed lg:text-sm", children: body })
|
|
23
|
-
] })
|
|
24
|
-
]
|
|
25
|
-
}
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export { FeatureCard };
|
|
30
|
-
//# sourceMappingURL=chunk-3SB6HUBA.mjs.map
|
|
31
|
-
//# sourceMappingURL=chunk-3SB6HUBA.mjs.map
|