@vegastack/design 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/verify-registry-item.mjs +1 -0
- package/dist/chunk-LRQSVCP6.js +182 -0
- package/dist/create-animated-icon.cjs +437 -0
- package/dist/create-animated-icon.d.cts +210 -0
- package/dist/create-animated-icon.d.ts +210 -0
- package/dist/create-animated-icon.js +350 -0
- package/dist/icons/index.cjs +26 -0
- package/dist/icons/index.d.cts +15 -8
- package/dist/icons/index.d.ts +15 -8
- package/dist/icons/index.js +1 -1
- package/dist/index.cjs +142 -3
- package/dist/index.d.cts +273 -1
- package/dist/index.d.ts +273 -1
- package/dist/index.js +21 -3
- package/package.json +20 -9
- package/preset.css +1 -1
- package/skills/vegastack-consume/SKILL.md +5 -5
- package/skills/vegastack-design-audit/SKILL.md +3 -1
- package/skills/vegastack-design-system/SKILL.md +105 -3
- package/skills/vegastack-design-system/references/components.md +21 -15
- package/dist/chunk-FBU37ITM.js +0 -52
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
cn
|
|
4
|
+
} from "./chunk-LRQSVCP6.js";
|
|
5
|
+
|
|
6
|
+
// src/icons/create-animated-icon.tsx
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import {
|
|
9
|
+
AnimatePresence,
|
|
10
|
+
MotionConfigContext,
|
|
11
|
+
motion,
|
|
12
|
+
useAnimation
|
|
13
|
+
} from "motion/react";
|
|
14
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
var REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
|
|
16
|
+
function subscribeToReducedMotion(onStoreChange) {
|
|
17
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function")
|
|
18
|
+
return () => {
|
|
19
|
+
};
|
|
20
|
+
const query = window.matchMedia(REDUCED_MOTION_QUERY);
|
|
21
|
+
query.addEventListener("change", onStoreChange);
|
|
22
|
+
return () => query.removeEventListener("change", onStoreChange);
|
|
23
|
+
}
|
|
24
|
+
function readReducedMotion() {
|
|
25
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function")
|
|
26
|
+
return false;
|
|
27
|
+
return window.matchMedia(REDUCED_MOTION_QUERY).matches;
|
|
28
|
+
}
|
|
29
|
+
function readReducedMotionOnServer() {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
function useLiveReducedMotion() {
|
|
33
|
+
const preference = React.useSyncExternalStore(
|
|
34
|
+
subscribeToReducedMotion,
|
|
35
|
+
readReducedMotion,
|
|
36
|
+
readReducedMotionOnServer
|
|
37
|
+
);
|
|
38
|
+
const { reducedMotion } = React.useContext(MotionConfigContext);
|
|
39
|
+
if (reducedMotion === "always") return true;
|
|
40
|
+
return preference;
|
|
41
|
+
}
|
|
42
|
+
var TAGS = {
|
|
43
|
+
circle: "circle",
|
|
44
|
+
defs: "defs",
|
|
45
|
+
ellipse: "ellipse",
|
|
46
|
+
g: "g",
|
|
47
|
+
line: "line",
|
|
48
|
+
path: "path",
|
|
49
|
+
pattern: "pattern",
|
|
50
|
+
polygon: "polygon",
|
|
51
|
+
polyline: "polyline",
|
|
52
|
+
rect: "rect",
|
|
53
|
+
text: "text",
|
|
54
|
+
"motion.circle": motion.circle,
|
|
55
|
+
"motion.ellipse": motion.ellipse,
|
|
56
|
+
"motion.g": motion.g,
|
|
57
|
+
"motion.line": motion.line,
|
|
58
|
+
"motion.path": motion.path,
|
|
59
|
+
"motion.pattern": motion.pattern,
|
|
60
|
+
"motion.polygon": motion.polygon,
|
|
61
|
+
"motion.polyline": motion.polyline,
|
|
62
|
+
"motion.rect": motion.rect,
|
|
63
|
+
"motion.text": motion.text
|
|
64
|
+
};
|
|
65
|
+
var ANIMATED_ICON_RESERVED_KEYS = /* @__PURE__ */ new Set([
|
|
66
|
+
"animate",
|
|
67
|
+
"children",
|
|
68
|
+
"className",
|
|
69
|
+
"custom",
|
|
70
|
+
"exit",
|
|
71
|
+
"group",
|
|
72
|
+
"inherit",
|
|
73
|
+
"initial",
|
|
74
|
+
"style",
|
|
75
|
+
"tag",
|
|
76
|
+
"text",
|
|
77
|
+
"transition",
|
|
78
|
+
"variants"
|
|
79
|
+
]);
|
|
80
|
+
var ROOT_DEFAULTS = {
|
|
81
|
+
viewBox: "0 0 24 24",
|
|
82
|
+
fill: "none",
|
|
83
|
+
stroke: "currentColor",
|
|
84
|
+
strokeWidth: "2",
|
|
85
|
+
strokeLinecap: "round",
|
|
86
|
+
strokeLinejoin: "round"
|
|
87
|
+
};
|
|
88
|
+
function isPresence(node) {
|
|
89
|
+
return typeof node === "object" && node.tag === "presence";
|
|
90
|
+
}
|
|
91
|
+
function isMotionTag(tag) {
|
|
92
|
+
return tag.startsWith("motion.");
|
|
93
|
+
}
|
|
94
|
+
function asNode(child) {
|
|
95
|
+
return typeof child === "string" ? { tag: "path", d: child } : child;
|
|
96
|
+
}
|
|
97
|
+
function specUsesControls(spec) {
|
|
98
|
+
if (spec.start || spec.stop) return true;
|
|
99
|
+
if (spec.svg?.tag === "motion.svg" && !spec.svg.inherit) return true;
|
|
100
|
+
const walk = (nodes) => nodes.some((child) => {
|
|
101
|
+
if (isPresence(child)) return false;
|
|
102
|
+
const node = asNode(child);
|
|
103
|
+
if (isMotionTag(node.tag) && !node.inherit && !node.animate) return true;
|
|
104
|
+
return node.children ? walk(node.children) : false;
|
|
105
|
+
});
|
|
106
|
+
return walk(spec.elements);
|
|
107
|
+
}
|
|
108
|
+
function hasPresenceNode(spec) {
|
|
109
|
+
return spec.elements.some(isPresence);
|
|
110
|
+
}
|
|
111
|
+
function svgAttributes(node) {
|
|
112
|
+
const attributes = {};
|
|
113
|
+
for (const [key, value] of Object.entries(node)) {
|
|
114
|
+
if (ANIMATED_ICON_RESERVED_KEYS.has(key)) continue;
|
|
115
|
+
if (value !== void 0) attributes[key] = value;
|
|
116
|
+
}
|
|
117
|
+
return attributes;
|
|
118
|
+
}
|
|
119
|
+
function renderNode(child, key, controls) {
|
|
120
|
+
const node = asNode(child);
|
|
121
|
+
const Tag = TAGS[node.tag];
|
|
122
|
+
const motionProps = isMotionTag(node.tag) ? {
|
|
123
|
+
animate: node.animate ?? (node.inherit ? void 0 : controls[node.group ?? "default"]),
|
|
124
|
+
initial: node.initial,
|
|
125
|
+
exit: node.exit,
|
|
126
|
+
variants: node.variants,
|
|
127
|
+
transition: node.transition,
|
|
128
|
+
custom: node.custom
|
|
129
|
+
} : void 0;
|
|
130
|
+
return /* @__PURE__ */ jsxs(
|
|
131
|
+
Tag,
|
|
132
|
+
{
|
|
133
|
+
...svgAttributes(node),
|
|
134
|
+
className: node.className,
|
|
135
|
+
style: node.style,
|
|
136
|
+
...motionProps,
|
|
137
|
+
children: [
|
|
138
|
+
node.text,
|
|
139
|
+
node.children?.map(
|
|
140
|
+
(grandchild, index) => renderNode(grandchild, index, controls)
|
|
141
|
+
)
|
|
142
|
+
]
|
|
143
|
+
},
|
|
144
|
+
key
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
function createAnimatedIcon(spec) {
|
|
148
|
+
const groupNames = spec.groups ?? ["default"];
|
|
149
|
+
const usesControls = specUsesControls(spec);
|
|
150
|
+
const usesPresence = hasPresenceNode(spec);
|
|
151
|
+
const root = spec.svg ?? {};
|
|
152
|
+
const rootTag = root.tag ?? "svg";
|
|
153
|
+
const RootTag = rootTag === "motion.svg" ? motion.svg : "svg";
|
|
154
|
+
function AnimatedIconComponent({
|
|
155
|
+
className,
|
|
156
|
+
size = "var(--icon-default)",
|
|
157
|
+
ref,
|
|
158
|
+
onPointerEnter,
|
|
159
|
+
onPointerLeave,
|
|
160
|
+
onPointerDown,
|
|
161
|
+
onFocus,
|
|
162
|
+
onBlur,
|
|
163
|
+
...props
|
|
164
|
+
}) {
|
|
165
|
+
const controlList = groupNames.map(() => useAnimation());
|
|
166
|
+
const shouldReduceMotion = useLiveReducedMotion();
|
|
167
|
+
const [isActive, setIsActive] = React.useState(false);
|
|
168
|
+
const reduceRef = React.useRef(shouldReduceMotion);
|
|
169
|
+
reduceRef.current = shouldReduceMotion;
|
|
170
|
+
const isControlledRef = React.useRef(false);
|
|
171
|
+
const timersRef = React.useRef([]);
|
|
172
|
+
const controls = React.useMemo(
|
|
173
|
+
() => Object.fromEntries(
|
|
174
|
+
groupNames.map((name, index) => [name, controlList[index]])
|
|
175
|
+
),
|
|
176
|
+
// Each entry is referentially stable for the life of the component.
|
|
177
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
178
|
+
controlList
|
|
179
|
+
);
|
|
180
|
+
const clearTimers = React.useCallback(() => {
|
|
181
|
+
for (const timer of timersRef.current) clearTimeout(timer);
|
|
182
|
+
timersRef.current = [];
|
|
183
|
+
}, []);
|
|
184
|
+
React.useEffect(() => clearTimers, [clearTimers]);
|
|
185
|
+
const run = React.useCallback(
|
|
186
|
+
(control2, definition, transition) => {
|
|
187
|
+
if (reduceRef.current) {
|
|
188
|
+
control2.stop();
|
|
189
|
+
return Promise.resolve();
|
|
190
|
+
}
|
|
191
|
+
return Promise.resolve(control2.start(definition, transition)).then(
|
|
192
|
+
() => void 0,
|
|
193
|
+
() => void 0
|
|
194
|
+
);
|
|
195
|
+
},
|
|
196
|
+
[]
|
|
197
|
+
);
|
|
198
|
+
const reset = React.useCallback(
|
|
199
|
+
(control2, definition, transition) => {
|
|
200
|
+
if (reduceRef.current) {
|
|
201
|
+
control2.stop();
|
|
202
|
+
control2.set(definition);
|
|
203
|
+
return Promise.resolve();
|
|
204
|
+
}
|
|
205
|
+
return Promise.resolve(control2.start(definition, transition)).then(
|
|
206
|
+
() => void 0,
|
|
207
|
+
() => void 0
|
|
208
|
+
);
|
|
209
|
+
},
|
|
210
|
+
[]
|
|
211
|
+
);
|
|
212
|
+
const set = React.useCallback(
|
|
213
|
+
(control2, definition) => {
|
|
214
|
+
control2.stop();
|
|
215
|
+
control2.set(definition);
|
|
216
|
+
},
|
|
217
|
+
[]
|
|
218
|
+
);
|
|
219
|
+
const after = React.useCallback(
|
|
220
|
+
(delayMs, task) => {
|
|
221
|
+
timersRef.current.push(setTimeout(task, delayMs));
|
|
222
|
+
},
|
|
223
|
+
[]
|
|
224
|
+
);
|
|
225
|
+
const flagsRef = React.useRef({});
|
|
226
|
+
const control = React.useCallback(
|
|
227
|
+
(group) => {
|
|
228
|
+
const index = group ? groupNames.indexOf(group) : 0;
|
|
229
|
+
return controlList[index === -1 ? 0 : index];
|
|
230
|
+
},
|
|
231
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- stable entries
|
|
232
|
+
controlList
|
|
233
|
+
);
|
|
234
|
+
const choreography = React.useMemo(
|
|
235
|
+
() => ({
|
|
236
|
+
control,
|
|
237
|
+
shouldReduceMotion,
|
|
238
|
+
run,
|
|
239
|
+
reset,
|
|
240
|
+
set,
|
|
241
|
+
after,
|
|
242
|
+
flags: flagsRef.current
|
|
243
|
+
}),
|
|
244
|
+
[control, shouldReduceMotion, run, reset, set, after]
|
|
245
|
+
);
|
|
246
|
+
const primaryControl = control();
|
|
247
|
+
const startAnimation = React.useCallback(() => {
|
|
248
|
+
clearTimers();
|
|
249
|
+
if (usesPresence) setIsActive(!reduceRef.current);
|
|
250
|
+
if (spec.start) {
|
|
251
|
+
void spec.start(choreography);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (usesControls) void run(primaryControl, "animate");
|
|
255
|
+
}, [choreography, clearTimers, run, primaryControl]);
|
|
256
|
+
const stopAnimation = React.useCallback(() => {
|
|
257
|
+
clearTimers();
|
|
258
|
+
if (usesPresence) setIsActive(false);
|
|
259
|
+
if (spec.stop) {
|
|
260
|
+
void spec.stop(choreography);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (usesControls) void reset(primaryControl, "normal");
|
|
264
|
+
}, [choreography, clearTimers, reset, primaryControl]);
|
|
265
|
+
React.useImperativeHandle(ref, () => {
|
|
266
|
+
isControlledRef.current = true;
|
|
267
|
+
return { startAnimation, stopAnimation };
|
|
268
|
+
});
|
|
269
|
+
React.useEffect(() => {
|
|
270
|
+
if (shouldReduceMotion) stopAnimation();
|
|
271
|
+
}, [shouldReduceMotion, stopAnimation]);
|
|
272
|
+
const handlePointerEnter = (event) => {
|
|
273
|
+
onPointerEnter?.(event);
|
|
274
|
+
if (!isControlledRef.current && event.pointerType !== "touch")
|
|
275
|
+
startAnimation();
|
|
276
|
+
};
|
|
277
|
+
const handlePointerLeave = (event) => {
|
|
278
|
+
onPointerLeave?.(event);
|
|
279
|
+
if (!isControlledRef.current && event.pointerType !== "touch")
|
|
280
|
+
stopAnimation();
|
|
281
|
+
};
|
|
282
|
+
const handlePointerDown = (event) => {
|
|
283
|
+
onPointerDown?.(event);
|
|
284
|
+
if (!isControlledRef.current && event.pointerType === "touch")
|
|
285
|
+
startAnimation();
|
|
286
|
+
};
|
|
287
|
+
const handleFocus = (event) => {
|
|
288
|
+
onFocus?.(event);
|
|
289
|
+
if (!isControlledRef.current) startAnimation();
|
|
290
|
+
};
|
|
291
|
+
const handleBlur = (event) => {
|
|
292
|
+
onBlur?.(event);
|
|
293
|
+
if (!isControlledRef.current) stopAnimation();
|
|
294
|
+
};
|
|
295
|
+
const rootMotionProps = rootTag === "motion.svg" ? {
|
|
296
|
+
animate: root.animate ?? (root.inherit ? void 0 : controls[root.group ?? "default"]),
|
|
297
|
+
initial: root.initial,
|
|
298
|
+
variants: root.variants,
|
|
299
|
+
transition: root.transition,
|
|
300
|
+
custom: root.custom
|
|
301
|
+
} : void 0;
|
|
302
|
+
return /* @__PURE__ */ jsx(
|
|
303
|
+
"span",
|
|
304
|
+
{
|
|
305
|
+
className: cn(
|
|
306
|
+
"inline-flex shrink-0 items-center justify-center",
|
|
307
|
+
className
|
|
308
|
+
),
|
|
309
|
+
onPointerEnter: handlePointerEnter,
|
|
310
|
+
onPointerLeave: handlePointerLeave,
|
|
311
|
+
onPointerDown: handlePointerDown,
|
|
312
|
+
onFocus: handleFocus,
|
|
313
|
+
onBlur: handleBlur,
|
|
314
|
+
...props,
|
|
315
|
+
children: /* @__PURE__ */ jsx(
|
|
316
|
+
RootTag,
|
|
317
|
+
{
|
|
318
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
319
|
+
width: size,
|
|
320
|
+
height: size,
|
|
321
|
+
viewBox: root.viewBox ?? ROOT_DEFAULTS.viewBox,
|
|
322
|
+
fill: root.fill ?? ROOT_DEFAULTS.fill,
|
|
323
|
+
stroke: root.stroke ?? ROOT_DEFAULTS.stroke,
|
|
324
|
+
strokeWidth: root.strokeWidth ?? ROOT_DEFAULTS.strokeWidth,
|
|
325
|
+
strokeLinecap: root.strokeLinecap ?? ROOT_DEFAULTS.strokeLinecap,
|
|
326
|
+
strokeLinejoin: root.strokeLinejoin ?? ROOT_DEFAULTS.strokeLinejoin,
|
|
327
|
+
overflow: root.overflow,
|
|
328
|
+
className: root.className,
|
|
329
|
+
style: root.style,
|
|
330
|
+
...rootMotionProps,
|
|
331
|
+
children: spec.elements.map((node, index) => {
|
|
332
|
+
if (!isPresence(node)) return renderNode(node, index, controls);
|
|
333
|
+
return /* @__PURE__ */ jsx(AnimatePresence, { initial: false, mode: "wait", children: isActive ? /* @__PURE__ */ jsx(React.Fragment, { children: node.active.map(
|
|
334
|
+
(child, childIndex) => renderNode(child, childIndex, controls)
|
|
335
|
+
) }, `${spec.name}-active`) : /* @__PURE__ */ jsx(React.Fragment, { children: node.rest.map(
|
|
336
|
+
(child, childIndex) => renderNode(child, childIndex, controls)
|
|
337
|
+
) }, `${spec.name}-rest`) }, index);
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
)
|
|
341
|
+
}
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
AnimatedIconComponent.displayName = spec.name;
|
|
345
|
+
return AnimatedIconComponent;
|
|
346
|
+
}
|
|
347
|
+
export {
|
|
348
|
+
ANIMATED_ICON_RESERVED_KEYS,
|
|
349
|
+
createAnimatedIcon
|
|
350
|
+
};
|
package/dist/icons/index.cjs
CHANGED
|
@@ -83,6 +83,32 @@ var twMerge = (0, import_tailwind_merge.extendTailwindMerge)({
|
|
|
83
83
|
function cn(...inputs) {
|
|
84
84
|
return twMerge((0, import_clsx.clsx)(inputs));
|
|
85
85
|
}
|
|
86
|
+
var fieldControl = [
|
|
87
|
+
"rounded-md border border-input bg-transparent dark:bg-input/(--alpha-input)",
|
|
88
|
+
"placeholder:text-muted-foreground-faint",
|
|
89
|
+
"not-disabled:not-data-disabled:hover:border-foreground/(--alpha-border-subtle)",
|
|
90
|
+
"focus:border-ring/(--alpha-tint-border)",
|
|
91
|
+
"not-focus:aria-invalid:border-destructive-border/(--alpha-tint-border)",
|
|
92
|
+
"not-focus:data-invalid:border-destructive-border/(--alpha-tint-border)",
|
|
93
|
+
"disabled:cursor-not-allowed disabled:bg-surface-1 disabled:opacity-(--opacity-dim)",
|
|
94
|
+
"data-disabled:cursor-not-allowed data-disabled:bg-surface-1 data-disabled:opacity-(--opacity-dim)"
|
|
95
|
+
].join(" ");
|
|
96
|
+
var fieldControlGroup = [
|
|
97
|
+
"rounded-md border border-input bg-transparent dark:bg-input/(--alpha-input)",
|
|
98
|
+
"not-has-disabled:not-data-disabled:hover:border-foreground/(--alpha-border-subtle)",
|
|
99
|
+
"focus-within:border-ring/(--alpha-tint-border)",
|
|
100
|
+
"data-focused:border-ring/(--alpha-tint-border)",
|
|
101
|
+
// Both the SELF and the DESCENDANT form of the invalid tint (2026-09-09). `has-aria-invalid:` is
|
|
102
|
+
// a `:has()` over descendants, so a group that carries `aria-invalid` on ITSELF — which is where
|
|
103
|
+
// `<NumberField aria-invalid />` lands the attribute, on the `[data-field-group]` element —
|
|
104
|
+
// matched nothing and measured the neutral `--input` border. Inside a `Field` the state arrives
|
|
105
|
+
// as `data-invalid` on the group and was always correct; this is the standalone path.
|
|
106
|
+
"not-focus-within:aria-invalid:border-destructive-border/(--alpha-tint-border)",
|
|
107
|
+
"not-focus-within:has-aria-invalid:border-destructive-border/(--alpha-tint-border)",
|
|
108
|
+
"not-focus-within:data-invalid:border-destructive-border/(--alpha-tint-border)",
|
|
109
|
+
"has-disabled:cursor-not-allowed has-disabled:bg-surface-1 has-disabled:opacity-(--opacity-dim)",
|
|
110
|
+
"data-disabled:cursor-not-allowed data-disabled:bg-surface-1 data-disabled:opacity-(--opacity-dim)"
|
|
111
|
+
].join(" ");
|
|
86
112
|
|
|
87
113
|
// src/icons/brand-icon.tsx
|
|
88
114
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
package/dist/icons/index.d.cts
CHANGED
|
@@ -110,15 +110,18 @@ interface AnimatedIconHandle {
|
|
|
110
110
|
}
|
|
111
111
|
/**
|
|
112
112
|
* Shape of a mirrored `lucide-animated` icon component: a React 19 function
|
|
113
|
-
* component that accepts a runtime CSS length, spreads `
|
|
114
|
-
* {@link AnimatedIconHandle}. This is what `shadcn add @vegastack/<icon>`
|
|
113
|
+
* component that accepts a runtime CSS length, spreads `span` props, and exposes
|
|
114
|
+
* an {@link AnimatedIconHandle}. This is what `shadcn add @vegastack/<icon>`
|
|
115
115
|
* copies in (e.g. `ActivityIcon`).
|
|
116
|
+
*
|
|
117
|
+
* The host is an `inline-flex` `<span>`: an icon sits inside a line of text, and
|
|
118
|
+
* a block-level box there is a layout bug.
|
|
116
119
|
*/
|
|
117
120
|
type AnimatedIconComponent = React.ComponentType<{
|
|
118
121
|
size?: number | string;
|
|
119
122
|
ref?: React.Ref<AnimatedIconHandle>;
|
|
120
|
-
} & Omit<React.HTMLAttributes<
|
|
121
|
-
interface AnimatedIconProps extends Omit<React.HTMLAttributes<
|
|
123
|
+
} & Omit<React.HTMLAttributes<HTMLSpanElement>, "ref">>;
|
|
124
|
+
interface AnimatedIconProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "ref"> {
|
|
122
125
|
/** A mirrored lucide-animated icon component, e.g. `import { ActivityIcon } from '@/components/ui/activity'`. */
|
|
123
126
|
as: AnimatedIconComponent;
|
|
124
127
|
/**
|
|
@@ -141,10 +144,14 @@ interface AnimatedIconProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "
|
|
|
141
144
|
* animation (pointer/focus/touch auto-trigger where provided upstream; or call
|
|
142
145
|
* `ref.current.startAnimation()`).
|
|
143
146
|
*
|
|
144
|
-
* Deliberately imports no `motion` — the mirrored icon
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
147
|
+
* Deliberately imports no `motion` — the mirrored icon carries that dependency
|
|
148
|
+
* through `@vegastack/design/create-animated-icon`, so this entry stays
|
|
149
|
+
* lightweight for consumers who only use `Icon`/`BrandIcon`. Every mirrored icon
|
|
150
|
+
* is gated on `prefers-reduced-motion` intrinsically (in the factory, once, as a
|
|
151
|
+
* live subscription) and settles immediately at its static resting state when
|
|
152
|
+
* the user requests reduced motion. No <MotionConfig> is required;
|
|
153
|
+
* <MotionConfig reducedMotion="always"> can add reduction, but nothing can take
|
|
154
|
+
* the OS preference away.
|
|
148
155
|
*
|
|
149
156
|
* @example
|
|
150
157
|
* 'use client';
|
package/dist/icons/index.d.ts
CHANGED
|
@@ -110,15 +110,18 @@ interface AnimatedIconHandle {
|
|
|
110
110
|
}
|
|
111
111
|
/**
|
|
112
112
|
* Shape of a mirrored `lucide-animated` icon component: a React 19 function
|
|
113
|
-
* component that accepts a runtime CSS length, spreads `
|
|
114
|
-
* {@link AnimatedIconHandle}. This is what `shadcn add @vegastack/<icon>`
|
|
113
|
+
* component that accepts a runtime CSS length, spreads `span` props, and exposes
|
|
114
|
+
* an {@link AnimatedIconHandle}. This is what `shadcn add @vegastack/<icon>`
|
|
115
115
|
* copies in (e.g. `ActivityIcon`).
|
|
116
|
+
*
|
|
117
|
+
* The host is an `inline-flex` `<span>`: an icon sits inside a line of text, and
|
|
118
|
+
* a block-level box there is a layout bug.
|
|
116
119
|
*/
|
|
117
120
|
type AnimatedIconComponent = React.ComponentType<{
|
|
118
121
|
size?: number | string;
|
|
119
122
|
ref?: React.Ref<AnimatedIconHandle>;
|
|
120
|
-
} & Omit<React.HTMLAttributes<
|
|
121
|
-
interface AnimatedIconProps extends Omit<React.HTMLAttributes<
|
|
123
|
+
} & Omit<React.HTMLAttributes<HTMLSpanElement>, "ref">>;
|
|
124
|
+
interface AnimatedIconProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "ref"> {
|
|
122
125
|
/** A mirrored lucide-animated icon component, e.g. `import { ActivityIcon } from '@/components/ui/activity'`. */
|
|
123
126
|
as: AnimatedIconComponent;
|
|
124
127
|
/**
|
|
@@ -141,10 +144,14 @@ interface AnimatedIconProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "
|
|
|
141
144
|
* animation (pointer/focus/touch auto-trigger where provided upstream; or call
|
|
142
145
|
* `ref.current.startAnimation()`).
|
|
143
146
|
*
|
|
144
|
-
* Deliberately imports no `motion` — the mirrored icon
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
147
|
+
* Deliberately imports no `motion` — the mirrored icon carries that dependency
|
|
148
|
+
* through `@vegastack/design/create-animated-icon`, so this entry stays
|
|
149
|
+
* lightweight for consumers who only use `Icon`/`BrandIcon`. Every mirrored icon
|
|
150
|
+
* is gated on `prefers-reduced-motion` intrinsically (in the factory, once, as a
|
|
151
|
+
* live subscription) and settles immediately at its static resting state when
|
|
152
|
+
* the user requests reduced motion. No <MotionConfig> is required;
|
|
153
|
+
* <MotionConfig reducedMotion="always"> can add reduction, but nothing can take
|
|
154
|
+
* the OS preference away.
|
|
148
155
|
*
|
|
149
156
|
* @example
|
|
150
157
|
* 'use client';
|
package/dist/icons/index.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -22,11 +22,65 @@ var src_exports = {};
|
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
FLOATING: () => FLOATING,
|
|
24
24
|
TIMINGS: () => TIMINGS,
|
|
25
|
-
cn: () => cn
|
|
25
|
+
cn: () => cn,
|
|
26
|
+
fieldControl: () => fieldControl,
|
|
27
|
+
fieldControlGroup: () => fieldControlGroup,
|
|
28
|
+
fillInteractive: () => fillInteractive,
|
|
29
|
+
mergeRefs: () => mergeRefs,
|
|
30
|
+
prose: () => prose,
|
|
31
|
+
proseClassName: () => proseClassName,
|
|
32
|
+
selectedChipVariants: () => selectedChipVariants,
|
|
33
|
+
surfaceInteractive: () => surfaceInteractive,
|
|
34
|
+
surfaceInteractiveGroup: () => surfaceInteractiveGroup
|
|
26
35
|
});
|
|
27
36
|
module.exports = __toCommonJS(src_exports);
|
|
28
37
|
var import_clsx = require("clsx");
|
|
29
38
|
var import_tailwind_merge = require("tailwind-merge");
|
|
39
|
+
|
|
40
|
+
// src/prose.ts
|
|
41
|
+
var prose = {
|
|
42
|
+
/** The root's own ink and size — every rule below is relative to this. */
|
|
43
|
+
root: "text-base text-foreground",
|
|
44
|
+
// Headings. `scroll-m-20` keeps an anchored heading clear of a sticky header; the weight ladder
|
|
45
|
+
// caps at 500, which the `text-h*` roles already carry.
|
|
46
|
+
h1: "[&_h1]:mt-6 [&_h1]:mb-3 [&_h1]:scroll-m-20 [&_h1]:text-h1 [&_h1]:text-foreground [&_h1]:first:mt-0",
|
|
47
|
+
h2: "[&_h2]:mt-6 [&_h2]:mb-3 [&_h2]:scroll-m-20 [&_h2]:text-h2 [&_h2]:text-foreground [&_h2]:first:mt-0",
|
|
48
|
+
h3: "[&_h3]:mt-5 [&_h3]:mb-2 [&_h3]:scroll-m-20 [&_h3]:text-h3 [&_h3]:text-foreground [&_h3]:first:mt-0",
|
|
49
|
+
h4: "[&_h4]:mt-4 [&_h4]:mb-2 [&_h4]:scroll-m-20 [&_h4]:text-h4 [&_h4]:text-foreground [&_h4]:first:mt-0",
|
|
50
|
+
// h5/h6 leave the display tier and become labels — the type scale has no sixth heading size.
|
|
51
|
+
h5: "[&_h5]:mt-4 [&_h5]:mb-2 [&_h5]:text-label [&_h5]:text-foreground [&_h5]:first:mt-0",
|
|
52
|
+
h6: "[&_h6]:mt-4 [&_h6]:mb-2 [&_h6]:text-label [&_h6]:text-muted-foreground [&_h6]:first:mt-0",
|
|
53
|
+
p: "[&_p]:my-3 [&_p]:leading-relaxed [&_p]:text-foreground [&_p]:first:mt-0 [&_p]:last:mb-0",
|
|
54
|
+
// Marks. Links are `info` ink per design.md §Colours; `s` is Tiptap's strike element and `del`
|
|
55
|
+
// is GFM's — the same mark from two producers.
|
|
56
|
+
a: "[&_a]:font-medium [&_a]:text-info-text [&_a]:underline [&_a]:underline-offset-4 [&_a]:hover:text-info-text/(--alpha-link-hover)",
|
|
57
|
+
strong: "[&_strong]:font-medium [&_strong]:text-foreground",
|
|
58
|
+
em: "[&_em]:italic",
|
|
59
|
+
del: "[&_del]:text-muted-foreground [&_del]:line-through [&_s]:text-muted-foreground [&_s]:line-through",
|
|
60
|
+
// Lists. A GFM task list carries `contains-task-list` and its checkbox IS the marker, so the
|
|
61
|
+
// disc goes — expressed as a second, more specific descendant rule rather than a class on the
|
|
62
|
+
// element, which would lose the cascade (see the header).
|
|
63
|
+
ul: "[&_ul]:my-3 [&_ul]:ms-6 [&_ul]:list-disc [&_ul]:text-foreground [&_ul]:marker:text-muted-foreground [&_ul.contains-task-list]:list-none",
|
|
64
|
+
ol: "[&_ol]:my-3 [&_ol]:ms-6 [&_ol]:list-decimal [&_ol]:text-foreground [&_ol]:marker:text-muted-foreground",
|
|
65
|
+
li: "[&_li]:mt-1.5 [&_li]:leading-relaxed",
|
|
66
|
+
blockquote: "[&_blockquote]:my-3 [&_blockquote]:border-s-2 [&_blockquote]:border-border [&_blockquote]:ps-4 [&_blockquote]:text-muted-foreground [&_blockquote]:italic",
|
|
67
|
+
// Inline code is a chip. Code inside a `pre` is not: it inherits the panel's ground, so the chip
|
|
68
|
+
// is undone at higher specificity (`[&_pre_code]` is (0,1,2) against the chip's (0,1,1)).
|
|
69
|
+
code: "[&_code]:rounded-sm [&_code]:bg-muted [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-base [&_code]:text-foreground",
|
|
70
|
+
// A bare `<pre>` wears `CodeBlock`'s surface — the same sunken panel, one vocabulary (B4-09).
|
|
71
|
+
// `CodeBlock`'s own `pre` is excluded: it already sits inside a `figure` carrying the border,
|
|
72
|
+
// radius and ground, and re-applying them here would draw a second panel inside the first.
|
|
73
|
+
pre: "[&_pre:not([data-slot='code-block-pre'])]:my-3 [&_pre:not([data-slot='code-block-pre'])]:overflow-x-auto [&_pre:not([data-slot='code-block-pre'])]:rounded-lg [&_pre:not([data-slot='code-block-pre'])]:border [&_pre:not([data-slot='code-block-pre'])]:border-border [&_pre:not([data-slot='code-block-pre'])]:bg-muted [&_pre:not([data-slot='code-block-pre'])]:p-4 [&_pre:not([data-slot='code-block-pre'])]:text-foreground",
|
|
74
|
+
preCode: "[&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_pre_code]:font-mono [&_pre_code]:text-code [&_pre_code]:text-foreground",
|
|
75
|
+
hr: "[&_hr]:my-6 [&_hr]:border-border",
|
|
76
|
+
// GFM tables. The scroll container is structural and belongs to the consumer (MarkdownView wraps
|
|
77
|
+
// the table in an `overflow-x-auto` div); these are the cell and rule tokens.
|
|
78
|
+
table: "[&_table]:w-full [&_table]:border-collapse [&_table]:text-base [&_table]:text-foreground [&_thead]:border-b [&_thead]:border-border [&_tr]:border-b [&_tr]:border-border [&_tr]:last:border-0 [&_th]:px-3 [&_th]:py-2 [&_th]:text-start [&_th]:font-medium [&_th]:text-foreground [&_td]:px-3 [&_td]:py-2 [&_td]:text-muted-foreground",
|
|
79
|
+
img: "[&_img]:my-3 [&_img]:max-w-full [&_img]:rounded-lg [&_img]:border [&_img]:border-border"
|
|
80
|
+
};
|
|
81
|
+
var proseClassName = Object.values(prose).join(" ");
|
|
82
|
+
|
|
83
|
+
// src/index.ts
|
|
30
84
|
var twMerge = (0, import_tailwind_merge.extendTailwindMerge)({
|
|
31
85
|
extend: {
|
|
32
86
|
classGroups: {
|
|
@@ -55,6 +109,56 @@ var twMerge = (0, import_tailwind_merge.extendTailwindMerge)({
|
|
|
55
109
|
function cn(...inputs) {
|
|
56
110
|
return twMerge((0, import_clsx.clsx)(inputs));
|
|
57
111
|
}
|
|
112
|
+
var surfaceInteractive = "hover:bg-surface-2 active:bg-surface-3";
|
|
113
|
+
var surfaceInteractiveGroup = "group-hover/wash:bg-surface-2 group-active/wash:bg-surface-3";
|
|
114
|
+
var fillInteractive = {
|
|
115
|
+
foreground: "hover:bg-foreground/(--alpha-hover) active:bg-foreground/(--alpha-pressed)",
|
|
116
|
+
primary: "hover:bg-primary/(--alpha-hover) active:bg-primary/(--alpha-pressed)",
|
|
117
|
+
destructive: "hover:bg-destructive/(--alpha-hover) active:bg-destructive/(--alpha-pressed)",
|
|
118
|
+
success: "hover:bg-success/(--alpha-hover) active:bg-success/(--alpha-pressed)",
|
|
119
|
+
warning: "hover:bg-warning/(--alpha-hover) active:bg-warning/(--alpha-pressed)",
|
|
120
|
+
info: "hover:bg-info/(--alpha-hover) active:bg-info/(--alpha-pressed)",
|
|
121
|
+
brand: "hover:bg-brand/(--alpha-hover) active:bg-brand/(--alpha-pressed)"
|
|
122
|
+
};
|
|
123
|
+
var fieldControl = [
|
|
124
|
+
"rounded-md border border-input bg-transparent dark:bg-input/(--alpha-input)",
|
|
125
|
+
"placeholder:text-muted-foreground-faint",
|
|
126
|
+
"not-disabled:not-data-disabled:hover:border-foreground/(--alpha-border-subtle)",
|
|
127
|
+
"focus:border-ring/(--alpha-tint-border)",
|
|
128
|
+
"not-focus:aria-invalid:border-destructive-border/(--alpha-tint-border)",
|
|
129
|
+
"not-focus:data-invalid:border-destructive-border/(--alpha-tint-border)",
|
|
130
|
+
"disabled:cursor-not-allowed disabled:bg-surface-1 disabled:opacity-(--opacity-dim)",
|
|
131
|
+
"data-disabled:cursor-not-allowed data-disabled:bg-surface-1 data-disabled:opacity-(--opacity-dim)"
|
|
132
|
+
].join(" ");
|
|
133
|
+
var fieldControlGroup = [
|
|
134
|
+
"rounded-md border border-input bg-transparent dark:bg-input/(--alpha-input)",
|
|
135
|
+
"not-has-disabled:not-data-disabled:hover:border-foreground/(--alpha-border-subtle)",
|
|
136
|
+
"focus-within:border-ring/(--alpha-tint-border)",
|
|
137
|
+
"data-focused:border-ring/(--alpha-tint-border)",
|
|
138
|
+
// Both the SELF and the DESCENDANT form of the invalid tint (2026-09-09). `has-aria-invalid:` is
|
|
139
|
+
// a `:has()` over descendants, so a group that carries `aria-invalid` on ITSELF — which is where
|
|
140
|
+
// `<NumberField aria-invalid />` lands the attribute, on the `[data-field-group]` element —
|
|
141
|
+
// matched nothing and measured the neutral `--input` border. Inside a `Field` the state arrives
|
|
142
|
+
// as `data-invalid` on the group and was always correct; this is the standalone path.
|
|
143
|
+
"not-focus-within:aria-invalid:border-destructive-border/(--alpha-tint-border)",
|
|
144
|
+
"not-focus-within:has-aria-invalid:border-destructive-border/(--alpha-tint-border)",
|
|
145
|
+
"not-focus-within:data-invalid:border-destructive-border/(--alpha-tint-border)",
|
|
146
|
+
"has-disabled:cursor-not-allowed has-disabled:bg-surface-1 has-disabled:opacity-(--opacity-dim)",
|
|
147
|
+
"data-disabled:cursor-not-allowed data-disabled:bg-surface-1 data-disabled:opacity-(--opacity-dim)"
|
|
148
|
+
].join(" ");
|
|
149
|
+
var selectedChipVariants = {
|
|
150
|
+
/** The muted track the chips sit in — the ladder's well rung. */
|
|
151
|
+
track: "bg-surface-1",
|
|
152
|
+
/**
|
|
153
|
+
* Chrome shared by every chip: a transparent hairline reserved at rest (so selecting adds no
|
|
154
|
+
* layout shift) and the muted→ink text step.
|
|
155
|
+
*/
|
|
156
|
+
item: "border border-transparent hover:text-foreground",
|
|
157
|
+
/** Selected keyed on Base UI's `data-pressed` — Toggle, ToggleGroup, Segmented. */
|
|
158
|
+
pressed: "not-data-pressed:hover:bg-foreground/(--alpha-hover) not-data-pressed:active:bg-foreground/(--alpha-pressed) data-pressed:border-border data-pressed:bg-foreground/(--alpha-ink-tint) data-pressed:text-foreground data-pressed:hover:bg-foreground/(--alpha-ink-tint-strong) data-pressed:active:bg-foreground/(--alpha-ink-tint)",
|
|
159
|
+
/** Selected keyed on Base UI's `data-active` — Tabs. */
|
|
160
|
+
active: "not-data-[active]:hover:bg-foreground/(--alpha-hover) not-data-[active]:active:bg-foreground/(--alpha-pressed) data-[active]:border-border data-[active]:bg-foreground/(--alpha-ink-tint) data-[active]:text-foreground data-[active]:hover:bg-foreground/(--alpha-ink-tint-strong) data-[active]:active:bg-foreground/(--alpha-ink-tint)"
|
|
161
|
+
};
|
|
58
162
|
var TIMINGS = {
|
|
59
163
|
/** How long transient success feedback holds before reverting (CopyButton "Copied ✓"). */
|
|
60
164
|
feedbackRevertMs: 1500,
|
|
@@ -63,16 +167,51 @@ var TIMINGS = {
|
|
|
63
167
|
/** Hover delay before a rich preview (HoverCard) opens — guards accidental opens. */
|
|
64
168
|
hoverOpenDelayMs: 700,
|
|
65
169
|
/** Hover delay before a rich preview closes — lets the pointer travel into the card. */
|
|
66
|
-
hoverCloseDelayMs: 300
|
|
170
|
+
hoverCloseDelayMs: 300,
|
|
171
|
+
/**
|
|
172
|
+
* Hover delay before a tooltip opens. A tooltip is a cheap label, not a preview, so it is
|
|
173
|
+
* deliberately much faster than {@link TIMINGS.hoverOpenDelayMs} — long enough that sweeping
|
|
174
|
+
* across a toolbar does not flash five tips, short enough that a deliberate hover feels
|
|
175
|
+
* instant. Base UI's `Tooltip.Provider` grouping then opens adjacent tips with no delay at
|
|
176
|
+
* all. Set once, in the app-root provider.
|
|
177
|
+
*/
|
|
178
|
+
tooltipOpenDelayMs: 300,
|
|
179
|
+
/**
|
|
180
|
+
* Delay before a tooltip closes. Zero: a tooltip has nothing to travel into (it is not
|
|
181
|
+
* hoverable content, unlike a HoverCard), so holding it after the pointer leaves only
|
|
182
|
+
* obscures what the user moved on to.
|
|
183
|
+
*/
|
|
184
|
+
tooltipCloseDelayMs: 0
|
|
67
185
|
};
|
|
68
186
|
var FLOATING = {
|
|
69
187
|
sideOffsetAttached: 4,
|
|
70
188
|
sideOffsetDetached: 8,
|
|
71
189
|
collisionPadding: 8
|
|
72
190
|
};
|
|
191
|
+
function mergeRefs(...refs) {
|
|
192
|
+
return (node) => {
|
|
193
|
+
for (const ref of refs) {
|
|
194
|
+
if (ref == null) continue;
|
|
195
|
+
if (typeof ref === "function") {
|
|
196
|
+
ref(node);
|
|
197
|
+
} else {
|
|
198
|
+
ref.current = node;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
73
203
|
// Annotate the CommonJS export names for ESM import in node:
|
|
74
204
|
0 && (module.exports = {
|
|
75
205
|
FLOATING,
|
|
76
206
|
TIMINGS,
|
|
77
|
-
cn
|
|
207
|
+
cn,
|
|
208
|
+
fieldControl,
|
|
209
|
+
fieldControlGroup,
|
|
210
|
+
fillInteractive,
|
|
211
|
+
mergeRefs,
|
|
212
|
+
prose,
|
|
213
|
+
proseClassName,
|
|
214
|
+
selectedChipVariants,
|
|
215
|
+
surfaceInteractive,
|
|
216
|
+
surfaceInteractiveGroup
|
|
78
217
|
});
|