@vegastack/design 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,154 @@
1
+ import * as React from 'react';
2
+ import { LucideIcon } from 'lucide-react';
3
+
4
+ /** Functional icon size scale (px). */
5
+ declare const SIZES$1: {
6
+ readonly xs: 14;
7
+ readonly sm: 16;
8
+ readonly md: 20;
9
+ readonly lg: 24;
10
+ };
11
+ type IconSize = keyof typeof SIZES$1;
12
+ interface IconProps extends Omit<React.SVGProps<SVGSVGElement>, 'ref'> {
13
+ /** The lucide icon component to render. */
14
+ as: LucideIcon;
15
+ /**
16
+ * Size token — maps to 14/16/20/24px.
17
+ * @default 'md'
18
+ */
19
+ size?: IconSize;
20
+ /**
21
+ * Accessible label. When provided the icon is exposed to assistive tech;
22
+ * otherwise it is `aria-hidden`.
23
+ */
24
+ 'aria-label'?: string;
25
+ }
26
+ /**
27
+ * `Icon` — the one sanctioned wrapper for functional UI icons (lucide-react).
28
+ *
29
+ * Color inherits `currentColor` (themes automatically); stroke width is fixed
30
+ * to the design-system standard. `aria-hidden` unless an `aria-label` is given.
31
+ *
32
+ * @example
33
+ * import { Check } from 'lucide-react';
34
+ * <Icon as={Check} size="sm" aria-label="Done" />
35
+ */
36
+ declare function Icon({ as: Cmp, size, 'aria-label': label, ...props }: IconProps): React.JSX.Element;
37
+
38
+ /** Structural shape of a `thesvg` icon module (its default export). */
39
+ interface BrandIconModule {
40
+ slug: string;
41
+ title: string;
42
+ hex: string;
43
+ svg: string;
44
+ variants: Record<string, string>;
45
+ }
46
+ /** Brand-icon size scale → Tailwind size utilities (14/16/20/24px). */
47
+ declare const SIZE_CLASS: {
48
+ readonly xs: "size-3.5";
49
+ readonly sm: "size-4";
50
+ readonly md: "size-5";
51
+ readonly lg: "size-6";
52
+ };
53
+ type BrandIconSize = keyof typeof SIZE_CLASS;
54
+ interface BrandIconProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'children'> {
55
+ /** A `thesvg` icon module (default export), e.g. `import github from 'thesvg/github'`. */
56
+ icon: BrandIconModule;
57
+ /**
58
+ * Which `thesvg` variant to render:
59
+ * - `color` — the brand's official colors (the module `default` variant).
60
+ * - `mono` — single-color, inherits `currentColor`.
61
+ * - `light` / `dark` — variants tuned for light/dark surfaces.
62
+ * - `wordmark` — the brand's logotype/wordmark lockup.
63
+ *
64
+ * Falls back to the icon's base `svg` when a brand doesn't ship the variant.
65
+ * @default 'color'
66
+ */
67
+ variant?: 'color' | 'mono' | 'light' | 'dark' | 'wordmark';
68
+ /**
69
+ * Size token — maps to 14/16/20/24px.
70
+ * @default 'md'
71
+ */
72
+ size?: BrandIconSize;
73
+ /**
74
+ * Accessible label. Defaults to the brand title; pass `''` to hide from
75
+ * assistive tech (decorative).
76
+ */
77
+ 'aria-label'?: string;
78
+ }
79
+ /**
80
+ * `BrandIcon` — the one sanctioned wrapper for brand/logo icons (`thesvg`).
81
+ *
82
+ * @example
83
+ * import github from 'thesvg/github';
84
+ * <BrandIcon icon={github} size="md" /> // brand colors
85
+ * <BrandIcon icon={github} variant="mono" /> // inherits currentColor
86
+ */
87
+ declare function BrandIcon({ icon, variant, size, 'aria-label': label, className, ...props }: BrandIconProps): React.JSX.Element;
88
+
89
+ /** Functional icon size scale (px) — matches `Icon`/`BrandIcon`. */
90
+ declare const SIZES: {
91
+ readonly xs: 14;
92
+ readonly sm: 16;
93
+ readonly md: 20;
94
+ readonly lg: 24;
95
+ };
96
+ type AnimatedIconSize = keyof typeof SIZES;
97
+ /**
98
+ * The imperative handle every `lucide-animated` icon exposes on its ref —
99
+ * drive the animation programmatically instead of (or in addition to) hover.
100
+ */
101
+ interface AnimatedIconHandle {
102
+ startAnimation: () => void;
103
+ stopAnimation: () => void;
104
+ }
105
+ /**
106
+ * Shape of a mirrored `lucide-animated` icon component: a `forwardRef` that
107
+ * accepts a numeric `size`, spreads `div` props, and exposes an
108
+ * {@link AnimatedIconHandle}. This is what `shadcn add @vegastack/<icon>`
109
+ * copies in (e.g. `ActivityIcon`).
110
+ */
111
+ type AnimatedIconComponent = React.ForwardRefExoticComponent<{
112
+ size?: number;
113
+ } & Omit<React.HTMLAttributes<HTMLDivElement>, 'ref'> & React.RefAttributes<AnimatedIconHandle>>;
114
+ interface AnimatedIconProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'ref'> {
115
+ /** A mirrored lucide-animated icon component, e.g. `import { ActivityIcon } from '@/components/ui/activity'`. */
116
+ as: AnimatedIconComponent;
117
+ /**
118
+ * Size token — maps to 14/16/20/24px (overrides the icon's default 28px).
119
+ * @default 'md'
120
+ */
121
+ size?: AnimatedIconSize;
122
+ /**
123
+ * Accessible label. When provided the icon is exposed to assistive tech as an
124
+ * image; otherwise it is `aria-hidden` (decorative).
125
+ */
126
+ 'aria-label'?: string;
127
+ }
128
+ /**
129
+ * `AnimatedIcon` — the one sanctioned wrapper for motion icons (`lucide-animated`,
130
+ * mirrored into the VegaStack registry). It standardizes the size scale + a11y and
131
+ * forwards the icon's {@link AnimatedIconHandle} ref, while the icon itself owns the
132
+ * animation (animates on hover; or call `ref.current.startAnimation()`).
133
+ *
134
+ * Deliberately imports no `motion` — the mirrored icon component carries that
135
+ * dependency, so the base package stays lightweight. For app-wide
136
+ * reduced-motion, wrap your tree once in Motion's
137
+ * `<MotionConfig reducedMotion="user">`.
138
+ *
139
+ * @example
140
+ * 'use client';
141
+ * import { AnimatedIcon } from '@vegastack/design/icons';
142
+ * import { ActivityIcon } from '@/components/ui/activity';
143
+ *
144
+ * <AnimatedIcon as={ActivityIcon} size="sm" aria-label="Activity" />
145
+ *
146
+ * @example
147
+ * // Programmatic control via the forwarded handle
148
+ * const ref = React.useRef<AnimatedIconHandle>(null);
149
+ * <AnimatedIcon as={ActivityIcon} ref={ref} />
150
+ * <button onClick={() => ref.current?.startAnimation()}>Play</button>
151
+ */
152
+ declare const AnimatedIcon: React.ForwardRefExoticComponent<AnimatedIconProps & React.RefAttributes<AnimatedIconHandle>>;
153
+
154
+ export { AnimatedIcon, type AnimatedIconComponent, type AnimatedIconHandle, type AnimatedIconProps, type AnimatedIconSize, BrandIcon, type BrandIconModule, type BrandIconProps, type BrandIconSize, Icon, type IconProps, type IconSize };
@@ -0,0 +1,73 @@
1
+ import {
2
+ cn
3
+ } from "../chunk-FBU37ITM.js";
4
+
5
+ // src/icons/icon.tsx
6
+ import "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ var SIZES = { xs: 14, sm: 16, md: 20, lg: 24 };
9
+ function Icon({ as: Cmp, size = "md", "aria-label": label, ...props }) {
10
+ return /* @__PURE__ */ jsx(
11
+ Cmp,
12
+ {
13
+ width: SIZES[size],
14
+ height: SIZES[size],
15
+ strokeWidth: 1.75,
16
+ "aria-hidden": label ? void 0 : true,
17
+ "aria-label": label,
18
+ ...props
19
+ }
20
+ );
21
+ }
22
+
23
+ // src/icons/brand-icon.tsx
24
+ import "react";
25
+ import { jsx as jsx2 } from "react/jsx-runtime";
26
+ var SIZE_CLASS = { xs: "size-3.5", sm: "size-4", md: "size-5", lg: "size-6" };
27
+ function BrandIcon({
28
+ icon,
29
+ variant = "color",
30
+ size = "md",
31
+ "aria-label": label,
32
+ className,
33
+ ...props
34
+ }) {
35
+ const variantKey = variant === "color" ? "default" : variant;
36
+ const svg = icon.variants?.[variantKey] ?? icon.svg;
37
+ const accessibleLabel = label === void 0 ? icon.title : label;
38
+ return /* @__PURE__ */ jsx2(
39
+ "span",
40
+ {
41
+ role: accessibleLabel ? "img" : void 0,
42
+ "aria-label": accessibleLabel || void 0,
43
+ "aria-hidden": accessibleLabel ? void 0 : true,
44
+ className: cn("inline-flex shrink-0 [&>svg]:block [&>svg]:size-full", SIZE_CLASS[size], className),
45
+ dangerouslySetInnerHTML: { __html: svg },
46
+ ...props
47
+ }
48
+ );
49
+ }
50
+
51
+ // src/icons/animated-icon.tsx
52
+ import * as React3 from "react";
53
+ import { jsx as jsx3 } from "react/jsx-runtime";
54
+ var SIZES2 = { xs: 14, sm: 16, md: 20, lg: 24 };
55
+ var AnimatedIcon = React3.forwardRef(
56
+ ({ as: Cmp, size = "md", "aria-label": label, ...props }, ref) => /* @__PURE__ */ jsx3(
57
+ Cmp,
58
+ {
59
+ ref,
60
+ size: SIZES2[size],
61
+ role: label ? "img" : void 0,
62
+ "aria-hidden": label ? void 0 : true,
63
+ "aria-label": label,
64
+ ...props
65
+ }
66
+ )
67
+ );
68
+ AnimatedIcon.displayName = "AnimatedIcon";
69
+ export {
70
+ AnimatedIcon,
71
+ BrandIcon,
72
+ Icon
73
+ };
@@ -0,0 +1,49 @@
1
+ import { ClassValue } from 'clsx';
2
+ export { ClassValue } from 'clsx';
3
+
4
+ /**
5
+ * Merges Tailwind CSS class names with intelligent conflict resolution.
6
+ *
7
+ * Combines `clsx` for conditional classes and `tailwind-merge` to handle
8
+ * conflicting Tailwind utilities (e.g., `px-2` and `px-4` → keeps last one).
9
+ *
10
+ * @param inputs - Class names, objects, arrays, or conditional values
11
+ * @returns Merged and deduplicated class string
12
+ *
13
+ * @example
14
+ * cn('px-2 py-1', 'px-4') // 'py-1 px-4'
15
+ * cn('text-foreground', isError && 'text-destructive')
16
+ */
17
+ declare function cn(...inputs: ClassValue[]): string;
18
+
19
+ /**
20
+ * Interaction timing constants (ms) — design decisions, not magic numbers (register P2-14).
21
+ * Single source for every JS-driven delay in the registry; change one value and every
22
+ * component that expresses that role follows.
23
+ */
24
+ declare const TIMINGS: {
25
+ /** How long transient success feedback holds before reverting (CopyButton "Copied ✓"). */
26
+ readonly feedbackRevertMs: 1500;
27
+ /** Debounce before auto-persisting a text field (AutoSaveInput). */
28
+ readonly autoSaveDebounceMs: 800;
29
+ /** Hover delay before a rich preview (HoverCard) opens — guards accidental opens. */
30
+ readonly hoverOpenDelayMs: 700;
31
+ /** Hover delay before a rich preview closes — lets the pointer travel into the card. */
32
+ readonly hoverCloseDelayMs: 300;
33
+ };
34
+ /**
35
+ * Floating-surface positioning constants (px) — the unified sideOffset/collisionPadding
36
+ * pair (register P2-14). Two offset roles, one collision padding:
37
+ * - `sideOffsetAttached` (4): menu-like popups that read as attached to their trigger
38
+ * (dropdown, context menu, select, emoji picker).
39
+ * - `sideOffsetDetached` (8): floating panels that read as detached (popover, hover-card,
40
+ * tooltip).
41
+ * Submenus deliberately use 0 (flush) and are not part of this contract.
42
+ */
43
+ declare const FLOATING: {
44
+ readonly sideOffsetAttached: 4;
45
+ readonly sideOffsetDetached: 8;
46
+ readonly collisionPadding: 8;
47
+ };
48
+
49
+ export { FLOATING, TIMINGS, cn };
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ import {
2
+ FLOATING,
3
+ TIMINGS,
4
+ cn
5
+ } from "./chunk-FBU37ITM.js";
6
+ export {
7
+ FLOATING,
8
+ TIMINGS,
9
+ cn
10
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `@vegastack/design/preset` — Tailwind v4 setup metadata for VegaStack.
3
+ *
4
+ * Tailwind v4 is CSS-first (no JS `presets` array), so the real "preset" is the
5
+ * shipped `preset.css` (`@import "@vegastack/design/preset.css"`), which
6
+ * wires Tailwind + tw-animate-css + the VegaStack token theme + base reset in one
7
+ * import. This module exposes machine-readable metadata for tooling/agents.
8
+ */
9
+ declare const vegastackPreset: {
10
+ /** The token package this preset is bound to. */
11
+ readonly tokens: "@vegastack/design-tokens";
12
+ /** CSS entry consumers import to get the full setup. */
13
+ readonly css: "@vegastack/design/preset.css";
14
+ /** Tailwind major version this preset targets. */
15
+ readonly tailwind: 4;
16
+ };
17
+ type VegastackPreset = typeof vegastackPreset;
18
+
19
+ export { type VegastackPreset, vegastackPreset };
package/dist/preset.js ADDED
@@ -0,0 +1,12 @@
1
+ // src/preset.ts
2
+ var vegastackPreset = {
3
+ /** The token package this preset is bound to. */
4
+ tokens: "@vegastack/design-tokens",
5
+ /** CSS entry consumers import to get the full setup. */
6
+ css: "@vegastack/design/preset.css",
7
+ /** Tailwind major version this preset targets. */
8
+ tailwind: 4
9
+ };
10
+ export {
11
+ vegastackPreset
12
+ };
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "@vegastack/design",
3
+ "version": "0.1.0",
4
+ "description": "VegaStack design system — cn utility, icon runtime, Tailwind v4 preset, and the vegastack-design CLI (tokens ship separately as @vegastack/design-tokens)",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/VegaStack/vegastack-design.git",
9
+ "directory": "packages/design"
10
+ },
11
+ "type": "module",
12
+ "sideEffects": [
13
+ "**/*.css"
14
+ ],
15
+ "files": [
16
+ "dist",
17
+ "bin",
18
+ "css",
19
+ "preset.css"
20
+ ],
21
+ "bin": {
22
+ "vegastack-design": "./bin/vegastack-design.mjs"
23
+ },
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js"
28
+ },
29
+ "./icons": {
30
+ "types": "./dist/icons/index.d.ts",
31
+ "import": "./dist/icons/index.js"
32
+ },
33
+ "./preset": {
34
+ "types": "./dist/preset.d.ts",
35
+ "import": "./dist/preset.js"
36
+ },
37
+ "./preset.css": "./preset.css",
38
+ "./theme.css": "./css/theme.css",
39
+ "./base.css": "./css/base.css",
40
+ "./utilities.css": "./css/utilities.css"
41
+ },
42
+ "dependencies": {
43
+ "clsx": "^2.1.1",
44
+ "lucide-react": "^1.24.0",
45
+ "tailwind-merge": "^3.6.0",
46
+ "thesvg": "^3.2.6",
47
+ "@vegastack/design-tokens": "0.1.0"
48
+ },
49
+ "peerDependencies": {
50
+ "react": "^19.0.0",
51
+ "react-dom": "^19.0.0",
52
+ "tailwindcss": "^4.3.2",
53
+ "tw-animate-css": "^1.4.0"
54
+ },
55
+ "peerDependenciesMeta": {
56
+ "tailwindcss": {
57
+ "optional": true
58
+ },
59
+ "tw-animate-css": {
60
+ "optional": true
61
+ }
62
+ },
63
+ "devDependencies": {
64
+ "@tailwindcss/node": "4.3.1",
65
+ "@tailwindcss/oxide": "4.3.1",
66
+ "@types/react": "^19.2.17",
67
+ "@types/react-dom": "^19.2.3",
68
+ "eslint": "^10.7.0",
69
+ "react": "^19.2.7",
70
+ "react-dom": "^19.2.7",
71
+ "tailwindcss": "^4.3.2",
72
+ "tw-animate-css": "^1.4.0",
73
+ "typescript": "^6.0.3",
74
+ "@vegastack/typescript-config": "0.0.0",
75
+ "@vegastack/eslint-config": "0.0.0"
76
+ },
77
+ "publishConfig": {
78
+ "access": "public",
79
+ "provenance": true
80
+ },
81
+ "scripts": {
82
+ "build": "tsup",
83
+ "lint": "eslint . && pnpm run verify",
84
+ "verify": "node ../../tooling/verify-preset-source.mjs",
85
+ "typecheck": "tsc --noEmit",
86
+ "test": "node test/compare.test.mjs && node test/check-updates.test.mjs"
87
+ }
88
+ }
package/preset.css ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @vegastack/design/preset.css
3
+ * One-import Tailwind v4 setup for VegaStack consumers.
4
+ */
5
+ @import "tailwindcss";
6
+ @import "tw-animate-css";
7
+ @import "@vegastack/design-tokens/theme.css";
8
+ @import "@vegastack/design-tokens/base.css";
9
+ @import "@vegastack/design-tokens/utilities.css"; /* shimmer / scroll-fade / scrollbar @utility helpers */
10
+
11
+ /**
12
+ * Distribution contract — scan the classes shipped INSIDE our own packages.
13
+ *
14
+ * This package's own `dist` ships the icon runtime (`BrandIcon`'s
15
+ * `inline-flex shrink-0 [&>svg]:size-full`), and `@vegastack/ui` ships a compiled
16
+ * provider + `Toaster` whose sonner `classNames` (e.g. `group-[.toaster]:bg-popover`)
17
+ * live in its published `dist/*.js`, NOT in the consumer's own source — so without
18
+ * an explicit `@source` a real npm consumer importing ONLY this preset would get
19
+ * a partially-unstyled provider/Toaster + icon UI.
20
+ *
21
+ * Why `./dist` + a RELATIVE `../ui/dist` path and NOT `@source "@vegastack/ui"`:
22
+ * - Tailwind v4.3.1 (the pinned version) does NOT resolve a bare package-name
23
+ * `@source` through node_modules — it treats `@vegastack/ui` as a literal
24
+ * glob and silently scans nothing (verified: tooling/verify-preset-source.mjs).
25
+ * - Tailwind resolves `@source` relative to this file's realpath. The icon runtime
26
+ * is INSIDE this package now (`./dist`). `@vegastack/ui` — when present — sits as
27
+ * a sibling under the same `@vegastack` scope in node_modules, making `../ui`
28
+ * resolvable from any consumer layout (flat npm OR strict pnpm). When a consumer
29
+ * does NOT have ui installed, the path simply doesn't exist and Tailwind skips
30
+ * it — no error, graceful degradation. (ui is deliberately NOT a peerDependency:
31
+ * it is a private workspace package, and declaring it would create a
32
+ * ui → design → ui cycle in the workspace graph.)
33
+ *
34
+ * Consumers need to do NOTHING extra: importing this preset self-contains the
35
+ * `@source`. (The packages' `dist` is what ships per their `files` field, so we
36
+ * point at `dist`, never `src`.)
37
+ */
38
+ @source "./dist";
39
+ @source "../ui/dist";