@vegastack/design 0.1.0 → 0.2.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/check-updates.mjs +706 -108
- package/bin/skills.mjs +262 -0
- package/bin/vegastack-design.mjs +32 -14
- package/bin/verify-registry-item.mjs +775 -214
- package/css/base.css +1 -1
- package/css/theme.css +1 -1
- package/css/utilities.css +1 -1
- package/dist/icons/index.cjs +199 -0
- package/dist/icons/index.d.cts +164 -0
- package/dist/icons/index.d.ts +39 -29
- package/dist/icons/index.js +81 -14
- package/dist/index.cjs +78 -0
- package/dist/index.d.cts +56 -0
- package/dist/index.d.ts +7 -0
- package/dist/preset.cjs +37 -0
- package/dist/preset.d.cts +19 -0
- package/dist/theme-scope.cjs +56 -0
- package/dist/theme-scope.d.cts +23 -0
- package/dist/theme-scope.d.ts +23 -0
- package/dist/theme-scope.js +21 -0
- package/package.json +40 -18
- package/skills/vegastack-brand/SKILL.md +29 -0
- package/skills/vegastack-consume/SKILL.md +182 -0
- package/skills/vegastack-consume/references/registry-integrity.md +132 -0
- package/skills/vegastack-design-audit/SKILL.md +123 -0
- package/skills/vegastack-design-system/SKILL.md +110 -0
- package/skills/vegastack-design-system/references/components.md +151 -0
package/css/base.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/* Re-export of @vegastack/design-tokens/base.css — see theme.css for rationale. */
|
|
2
|
-
@import
|
|
2
|
+
@import "@vegastack/design-tokens/base.css";
|
package/css/theme.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/* Re-export: token values live in @vegastack/design-tokens (a direct dependency of this package),
|
|
2
2
|
* so pnpm-strict consumers can import "@vegastack/design/theme.css" without declaring the
|
|
3
3
|
* transitive tokens package themselves. Tokens-only consumers install @vegastack/design-tokens. */
|
|
4
|
-
@import
|
|
4
|
+
@import "@vegastack/design-tokens/theme.css";
|
package/css/utilities.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/* Re-export of @vegastack/design-tokens/utilities.css — see theme.css for rationale. */
|
|
2
|
-
@import
|
|
2
|
+
@import "@vegastack/design-tokens/utilities.css";
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/icons/index.tsx
|
|
21
|
+
var icons_exports = {};
|
|
22
|
+
__export(icons_exports, {
|
|
23
|
+
AnimatedIcon: () => AnimatedIcon,
|
|
24
|
+
BrandIcon: () => BrandIcon,
|
|
25
|
+
Icon: () => Icon
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(icons_exports);
|
|
28
|
+
|
|
29
|
+
// src/icons/icon.tsx
|
|
30
|
+
var React = require("react");
|
|
31
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
32
|
+
var SIZES = { xs: 14, sm: 16, md: 20, lg: 24 };
|
|
33
|
+
function Icon({
|
|
34
|
+
as: Cmp,
|
|
35
|
+
size = "md",
|
|
36
|
+
"aria-label": label,
|
|
37
|
+
...props
|
|
38
|
+
}) {
|
|
39
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
40
|
+
Cmp,
|
|
41
|
+
{
|
|
42
|
+
width: SIZES[size],
|
|
43
|
+
height: SIZES[size],
|
|
44
|
+
strokeWidth: 1.75,
|
|
45
|
+
"aria-hidden": label ? void 0 : true,
|
|
46
|
+
"aria-label": label,
|
|
47
|
+
...props
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/icons/brand-icon.tsx
|
|
53
|
+
var React2 = require("react");
|
|
54
|
+
|
|
55
|
+
// src/index.ts
|
|
56
|
+
var import_clsx = require("clsx");
|
|
57
|
+
var import_tailwind_merge = require("tailwind-merge");
|
|
58
|
+
var twMerge = (0, import_tailwind_merge.extendTailwindMerge)({
|
|
59
|
+
extend: {
|
|
60
|
+
classGroups: {
|
|
61
|
+
"font-size": [
|
|
62
|
+
{
|
|
63
|
+
text: [
|
|
64
|
+
"h1",
|
|
65
|
+
"h2",
|
|
66
|
+
"h3",
|
|
67
|
+
"h4",
|
|
68
|
+
"label",
|
|
69
|
+
"label-sm",
|
|
70
|
+
"code",
|
|
71
|
+
"code-sm",
|
|
72
|
+
"mono-label",
|
|
73
|
+
"display-sm",
|
|
74
|
+
"display-md",
|
|
75
|
+
"display-lg",
|
|
76
|
+
"display-xl"
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
function cn(...inputs) {
|
|
84
|
+
return twMerge((0, import_clsx.clsx)(inputs));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/icons/brand-icon.tsx
|
|
88
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
89
|
+
var SIZE_CLASS = {
|
|
90
|
+
xs: "size-3.5",
|
|
91
|
+
sm: "size-4",
|
|
92
|
+
md: "size-5",
|
|
93
|
+
lg: "size-6"
|
|
94
|
+
};
|
|
95
|
+
function fitToBox(svg) {
|
|
96
|
+
return svg.replace(/<svg\b[^>]*>/i, (openTag) => {
|
|
97
|
+
const cleaned = openTag.replace(/\s(?:width|height)\s*=\s*"[^"]*"/gi, "").replace(/\s(?:width|height)\s*=\s*'[^']*'/gi, "").replace(/\spreserveAspectRatio\s*=\s*"[^"]*"/gi, "");
|
|
98
|
+
return cleaned.replace(
|
|
99
|
+
/\s*\/?>$/,
|
|
100
|
+
(end) => ` preserveAspectRatio="xMidYMid meet"${end.trimStart()}`
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function BrandIcon({
|
|
105
|
+
icon,
|
|
106
|
+
variant = "color",
|
|
107
|
+
size = "md",
|
|
108
|
+
"aria-label": label,
|
|
109
|
+
className,
|
|
110
|
+
...props
|
|
111
|
+
}) {
|
|
112
|
+
const accessibleLabel = label === void 0 ? icon.title : label;
|
|
113
|
+
const boxClassName = cn(
|
|
114
|
+
"inline-flex shrink-0 [&_svg]:block [&_svg]:size-full",
|
|
115
|
+
SIZE_CLASS[size],
|
|
116
|
+
className
|
|
117
|
+
);
|
|
118
|
+
const pick = (key) => icon.variants?.[key] ?? icon.variants?.default ?? icon.svg;
|
|
119
|
+
if (variant === "auto") {
|
|
120
|
+
const lightSvg = pick("light");
|
|
121
|
+
const darkSvg = pick("dark");
|
|
122
|
+
if (lightSvg !== darkSvg) {
|
|
123
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
124
|
+
"span",
|
|
125
|
+
{
|
|
126
|
+
role: accessibleLabel ? "img" : void 0,
|
|
127
|
+
"aria-label": accessibleLabel || void 0,
|
|
128
|
+
"aria-hidden": accessibleLabel ? void 0 : true,
|
|
129
|
+
className: boxClassName,
|
|
130
|
+
...props,
|
|
131
|
+
children: [
|
|
132
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
133
|
+
"span",
|
|
134
|
+
{
|
|
135
|
+
"data-brand-artwork": "light",
|
|
136
|
+
dangerouslySetInnerHTML: { __html: fitToBox(lightSvg) }
|
|
137
|
+
}
|
|
138
|
+
),
|
|
139
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
140
|
+
"span",
|
|
141
|
+
{
|
|
142
|
+
"data-brand-artwork": "dark",
|
|
143
|
+
dangerouslySetInnerHTML: { __html: fitToBox(darkSvg) }
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const variantKey = variant === "color" || variant === "auto" ? "default" : variant;
|
|
152
|
+
const svg = icon.variants?.[variantKey] ?? icon.svg;
|
|
153
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
154
|
+
"span",
|
|
155
|
+
{
|
|
156
|
+
role: accessibleLabel ? "img" : void 0,
|
|
157
|
+
"aria-label": accessibleLabel || void 0,
|
|
158
|
+
"aria-hidden": accessibleLabel ? void 0 : true,
|
|
159
|
+
className: boxClassName,
|
|
160
|
+
dangerouslySetInnerHTML: { __html: fitToBox(svg) },
|
|
161
|
+
...props
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/icons/animated-icon.tsx
|
|
167
|
+
var React3 = require("react");
|
|
168
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
169
|
+
var SIZES2 = {
|
|
170
|
+
xs: "var(--icon-inline)",
|
|
171
|
+
sm: "var(--icon-default)",
|
|
172
|
+
md: "var(--icon-action)",
|
|
173
|
+
lg: "var(--icon-feature)"
|
|
174
|
+
};
|
|
175
|
+
function AnimatedIcon({
|
|
176
|
+
as: Cmp,
|
|
177
|
+
size = "md",
|
|
178
|
+
ref,
|
|
179
|
+
"aria-label": label,
|
|
180
|
+
...props
|
|
181
|
+
}) {
|
|
182
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
183
|
+
Cmp,
|
|
184
|
+
{
|
|
185
|
+
ref,
|
|
186
|
+
size: SIZES2[size],
|
|
187
|
+
role: label ? "img" : void 0,
|
|
188
|
+
"aria-hidden": label ? void 0 : true,
|
|
189
|
+
"aria-label": label,
|
|
190
|
+
...props
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
195
|
+
0 && (module.exports = {
|
|
196
|
+
AnimatedIcon,
|
|
197
|
+
BrandIcon,
|
|
198
|
+
Icon
|
|
199
|
+
});
|
|
@@ -0,0 +1,164 @@
|
|
|
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
|
+
* - `auto` — theme-aware brand colors: the brand's `light` artwork on light
|
|
60
|
+
* surfaces and its `dark` artwork on dark ones, swapped in pure CSS (no JS,
|
|
61
|
+
* no hydration flash). **Use this on any surface that flips theme** —
|
|
62
|
+
* `color` renders the literal brand palette, and single-colour brands like
|
|
63
|
+
* GitHub (`#181717`) disappear against a dark background.
|
|
64
|
+
* - `color` — the brand's official colors (the module `default` variant).
|
|
65
|
+
* - `mono` — single-color, inherits `currentColor` (always theme-safe).
|
|
66
|
+
* - `light` / `dark` — pin one surface-tuned variant explicitly.
|
|
67
|
+
* - `wordmark` — the brand's logotype/wordmark lockup.
|
|
68
|
+
*
|
|
69
|
+
* Falls back to the icon's base `svg` when a brand doesn't ship the variant.
|
|
70
|
+
* @default 'color'
|
|
71
|
+
*/
|
|
72
|
+
variant?: "auto" | "color" | "mono" | "light" | "dark" | "wordmark";
|
|
73
|
+
/**
|
|
74
|
+
* Size token — maps to 14/16/20/24px.
|
|
75
|
+
* @default 'md'
|
|
76
|
+
*/
|
|
77
|
+
size?: BrandIconSize;
|
|
78
|
+
/**
|
|
79
|
+
* Accessible label. Defaults to the brand title; pass `''` to hide from
|
|
80
|
+
* assistive tech (decorative).
|
|
81
|
+
*/
|
|
82
|
+
"aria-label"?: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* `BrandIcon` — the one sanctioned wrapper for brand/logo icons (`thesvg`).
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* import github from 'thesvg/github';
|
|
89
|
+
* <BrandIcon icon={github} size="md" /> // literal brand colors
|
|
90
|
+
* <BrandIcon icon={github} variant="auto" /> // theme-aware (light/dark artwork)
|
|
91
|
+
* <BrandIcon icon={github} variant="mono" /> // inherits currentColor
|
|
92
|
+
*/
|
|
93
|
+
declare function BrandIcon({ icon, variant, size, "aria-label": label, className, ...props }: BrandIconProps): React.JSX.Element;
|
|
94
|
+
|
|
95
|
+
/** Runtime icon-role tokens — resolves in the consumer's active theme. */
|
|
96
|
+
declare const SIZES: {
|
|
97
|
+
readonly xs: "var(--icon-inline)";
|
|
98
|
+
readonly sm: "var(--icon-default)";
|
|
99
|
+
readonly md: "var(--icon-action)";
|
|
100
|
+
readonly lg: "var(--icon-feature)";
|
|
101
|
+
};
|
|
102
|
+
type AnimatedIconSize = keyof typeof SIZES;
|
|
103
|
+
/**
|
|
104
|
+
* The imperative handle every `lucide-animated` icon exposes on its ref —
|
|
105
|
+
* drive the animation programmatically instead of (or in addition to) hover.
|
|
106
|
+
*/
|
|
107
|
+
interface AnimatedIconHandle {
|
|
108
|
+
startAnimation: () => void;
|
|
109
|
+
stopAnimation: () => void;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Shape of a mirrored `lucide-animated` icon component: a React 19 function
|
|
113
|
+
* component that accepts a runtime CSS length, spreads `div` props, and exposes an
|
|
114
|
+
* {@link AnimatedIconHandle}. This is what `shadcn add @vegastack/<icon>`
|
|
115
|
+
* copies in (e.g. `ActivityIcon`).
|
|
116
|
+
*/
|
|
117
|
+
type AnimatedIconComponent = React.ComponentType<{
|
|
118
|
+
size?: number | string;
|
|
119
|
+
ref?: React.Ref<AnimatedIconHandle>;
|
|
120
|
+
} & Omit<React.HTMLAttributes<HTMLDivElement>, "ref">>;
|
|
121
|
+
interface AnimatedIconProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "ref"> {
|
|
122
|
+
/** A mirrored lucide-animated icon component, e.g. `import { ActivityIcon } from '@/components/ui/activity'`. */
|
|
123
|
+
as: AnimatedIconComponent;
|
|
124
|
+
/**
|
|
125
|
+
* Size role — resolves through `--icon-inline/default/action/feature` at runtime.
|
|
126
|
+
* @default 'md'
|
|
127
|
+
*/
|
|
128
|
+
size?: AnimatedIconSize;
|
|
129
|
+
/** Imperative animation handle forwarded to the mirrored icon. */
|
|
130
|
+
ref?: React.Ref<AnimatedIconHandle>;
|
|
131
|
+
/**
|
|
132
|
+
* Accessible label. When provided the icon is exposed to assistive tech as an
|
|
133
|
+
* image; otherwise it is `aria-hidden` (decorative).
|
|
134
|
+
*/
|
|
135
|
+
"aria-label"?: string;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* `AnimatedIcon` — the one sanctioned wrapper for motion icons (`lucide-animated`,
|
|
139
|
+
* mirrored into the VegaStack registry). It standardizes the size scale + a11y and
|
|
140
|
+
* forwards the icon's {@link AnimatedIconHandle} ref, while the icon itself owns the
|
|
141
|
+
* animation (pointer/focus/touch auto-trigger where provided upstream; or call
|
|
142
|
+
* `ref.current.startAnimation()`).
|
|
143
|
+
*
|
|
144
|
+
* Deliberately imports no `motion` — the mirrored icon component carries that
|
|
145
|
+
* dependency, so the base package stays lightweight. Every generated mirror reads
|
|
146
|
+
* Motion's `useReducedMotion()` preference intrinsically and settles immediately at
|
|
147
|
+
* its static resting state when the user requests reduced motion.
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* 'use client';
|
|
151
|
+
* import { AnimatedIcon } from '@vegastack/design/icons';
|
|
152
|
+
* import { ActivityIcon } from '@/components/ui/activity';
|
|
153
|
+
*
|
|
154
|
+
* <AnimatedIcon as={ActivityIcon} size="sm" aria-label="Activity" />
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* // Programmatic control via the forwarded handle
|
|
158
|
+
* const ref = React.useRef<AnimatedIconHandle>(null);
|
|
159
|
+
* <AnimatedIcon as={ActivityIcon} ref={ref} />
|
|
160
|
+
* <button onClick={() => ref.current?.startAnimation()}>Play</button>
|
|
161
|
+
*/
|
|
162
|
+
declare function AnimatedIcon({ as: Cmp, size, ref, "aria-label": label, ...props }: AnimatedIconProps): React.JSX.Element;
|
|
163
|
+
|
|
164
|
+
export { AnimatedIcon, type AnimatedIconComponent, type AnimatedIconHandle, type AnimatedIconProps, type AnimatedIconSize, BrandIcon, type BrandIconModule, type BrandIconProps, type BrandIconSize, Icon, type IconProps, type IconSize };
|
package/dist/icons/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ declare const SIZES$1: {
|
|
|
9
9
|
readonly lg: 24;
|
|
10
10
|
};
|
|
11
11
|
type IconSize = keyof typeof SIZES$1;
|
|
12
|
-
interface IconProps extends Omit<React.SVGProps<SVGSVGElement>,
|
|
12
|
+
interface IconProps extends Omit<React.SVGProps<SVGSVGElement>, "ref"> {
|
|
13
13
|
/** The lucide icon component to render. */
|
|
14
14
|
as: LucideIcon;
|
|
15
15
|
/**
|
|
@@ -21,7 +21,7 @@ interface IconProps extends Omit<React.SVGProps<SVGSVGElement>, 'ref'> {
|
|
|
21
21
|
* Accessible label. When provided the icon is exposed to assistive tech;
|
|
22
22
|
* otherwise it is `aria-hidden`.
|
|
23
23
|
*/
|
|
24
|
-
|
|
24
|
+
"aria-label"?: string;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* `Icon` — the one sanctioned wrapper for functional UI icons (lucide-react).
|
|
@@ -33,7 +33,7 @@ interface IconProps extends Omit<React.SVGProps<SVGSVGElement>, 'ref'> {
|
|
|
33
33
|
* import { Check } from 'lucide-react';
|
|
34
34
|
* <Icon as={Check} size="sm" aria-label="Done" />
|
|
35
35
|
*/
|
|
36
|
-
declare function Icon({ as: Cmp, size,
|
|
36
|
+
declare function Icon({ as: Cmp, size, "aria-label": label, ...props }: IconProps): React.JSX.Element;
|
|
37
37
|
|
|
38
38
|
/** Structural shape of a `thesvg` icon module (its default export). */
|
|
39
39
|
interface BrandIconModule {
|
|
@@ -51,20 +51,25 @@ declare const SIZE_CLASS: {
|
|
|
51
51
|
readonly lg: "size-6";
|
|
52
52
|
};
|
|
53
53
|
type BrandIconSize = keyof typeof SIZE_CLASS;
|
|
54
|
-
interface BrandIconProps extends Omit<React.HTMLAttributes<HTMLSpanElement>,
|
|
54
|
+
interface BrandIconProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "children"> {
|
|
55
55
|
/** A `thesvg` icon module (default export), e.g. `import github from 'thesvg/github'`. */
|
|
56
56
|
icon: BrandIconModule;
|
|
57
57
|
/**
|
|
58
58
|
* Which `thesvg` variant to render:
|
|
59
|
+
* - `auto` — theme-aware brand colors: the brand's `light` artwork on light
|
|
60
|
+
* surfaces and its `dark` artwork on dark ones, swapped in pure CSS (no JS,
|
|
61
|
+
* no hydration flash). **Use this on any surface that flips theme** —
|
|
62
|
+
* `color` renders the literal brand palette, and single-colour brands like
|
|
63
|
+
* GitHub (`#181717`) disappear against a dark background.
|
|
59
64
|
* - `color` — the brand's official colors (the module `default` variant).
|
|
60
|
-
* - `mono` — single-color, inherits `currentColor
|
|
61
|
-
* - `light` / `dark` —
|
|
65
|
+
* - `mono` — single-color, inherits `currentColor` (always theme-safe).
|
|
66
|
+
* - `light` / `dark` — pin one surface-tuned variant explicitly.
|
|
62
67
|
* - `wordmark` — the brand's logotype/wordmark lockup.
|
|
63
68
|
*
|
|
64
69
|
* Falls back to the icon's base `svg` when a brand doesn't ship the variant.
|
|
65
70
|
* @default 'color'
|
|
66
71
|
*/
|
|
67
|
-
variant?:
|
|
72
|
+
variant?: "auto" | "color" | "mono" | "light" | "dark" | "wordmark";
|
|
68
73
|
/**
|
|
69
74
|
* Size token — maps to 14/16/20/24px.
|
|
70
75
|
* @default 'md'
|
|
@@ -74,24 +79,25 @@ interface BrandIconProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'ch
|
|
|
74
79
|
* Accessible label. Defaults to the brand title; pass `''` to hide from
|
|
75
80
|
* assistive tech (decorative).
|
|
76
81
|
*/
|
|
77
|
-
|
|
82
|
+
"aria-label"?: string;
|
|
78
83
|
}
|
|
79
84
|
/**
|
|
80
85
|
* `BrandIcon` — the one sanctioned wrapper for brand/logo icons (`thesvg`).
|
|
81
86
|
*
|
|
82
87
|
* @example
|
|
83
88
|
* import github from 'thesvg/github';
|
|
84
|
-
* <BrandIcon icon={github} size="md" />
|
|
85
|
-
* <BrandIcon icon={github} variant="
|
|
89
|
+
* <BrandIcon icon={github} size="md" /> // literal brand colors
|
|
90
|
+
* <BrandIcon icon={github} variant="auto" /> // theme-aware (light/dark artwork)
|
|
91
|
+
* <BrandIcon icon={github} variant="mono" /> // inherits currentColor
|
|
86
92
|
*/
|
|
87
|
-
declare function BrandIcon({ icon, variant, size,
|
|
93
|
+
declare function BrandIcon({ icon, variant, size, "aria-label": label, className, ...props }: BrandIconProps): React.JSX.Element;
|
|
88
94
|
|
|
89
|
-
/**
|
|
95
|
+
/** Runtime icon-role tokens — resolves in the consumer's active theme. */
|
|
90
96
|
declare const SIZES: {
|
|
91
|
-
readonly xs:
|
|
92
|
-
readonly sm:
|
|
93
|
-
readonly md:
|
|
94
|
-
readonly lg:
|
|
97
|
+
readonly xs: "var(--icon-inline)";
|
|
98
|
+
readonly sm: "var(--icon-default)";
|
|
99
|
+
readonly md: "var(--icon-action)";
|
|
100
|
+
readonly lg: "var(--icon-feature)";
|
|
95
101
|
};
|
|
96
102
|
type AnimatedIconSize = keyof typeof SIZES;
|
|
97
103
|
/**
|
|
@@ -103,38 +109,42 @@ interface AnimatedIconHandle {
|
|
|
103
109
|
stopAnimation: () => void;
|
|
104
110
|
}
|
|
105
111
|
/**
|
|
106
|
-
* Shape of a mirrored `lucide-animated` icon component: a
|
|
107
|
-
* accepts a
|
|
112
|
+
* Shape of a mirrored `lucide-animated` icon component: a React 19 function
|
|
113
|
+
* component that accepts a runtime CSS length, spreads `div` props, and exposes an
|
|
108
114
|
* {@link AnimatedIconHandle}. This is what `shadcn add @vegastack/<icon>`
|
|
109
115
|
* copies in (e.g. `ActivityIcon`).
|
|
110
116
|
*/
|
|
111
|
-
type AnimatedIconComponent = React.
|
|
112
|
-
size?: number;
|
|
113
|
-
|
|
114
|
-
|
|
117
|
+
type AnimatedIconComponent = React.ComponentType<{
|
|
118
|
+
size?: number | string;
|
|
119
|
+
ref?: React.Ref<AnimatedIconHandle>;
|
|
120
|
+
} & Omit<React.HTMLAttributes<HTMLDivElement>, "ref">>;
|
|
121
|
+
interface AnimatedIconProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "ref"> {
|
|
115
122
|
/** A mirrored lucide-animated icon component, e.g. `import { ActivityIcon } from '@/components/ui/activity'`. */
|
|
116
123
|
as: AnimatedIconComponent;
|
|
117
124
|
/**
|
|
118
|
-
* Size
|
|
125
|
+
* Size role — resolves through `--icon-inline/default/action/feature` at runtime.
|
|
119
126
|
* @default 'md'
|
|
120
127
|
*/
|
|
121
128
|
size?: AnimatedIconSize;
|
|
129
|
+
/** Imperative animation handle forwarded to the mirrored icon. */
|
|
130
|
+
ref?: React.Ref<AnimatedIconHandle>;
|
|
122
131
|
/**
|
|
123
132
|
* Accessible label. When provided the icon is exposed to assistive tech as an
|
|
124
133
|
* image; otherwise it is `aria-hidden` (decorative).
|
|
125
134
|
*/
|
|
126
|
-
|
|
135
|
+
"aria-label"?: string;
|
|
127
136
|
}
|
|
128
137
|
/**
|
|
129
138
|
* `AnimatedIcon` — the one sanctioned wrapper for motion icons (`lucide-animated`,
|
|
130
139
|
* mirrored into the VegaStack registry). It standardizes the size scale + a11y and
|
|
131
140
|
* forwards the icon's {@link AnimatedIconHandle} ref, while the icon itself owns the
|
|
132
|
-
* animation (
|
|
141
|
+
* animation (pointer/focus/touch auto-trigger where provided upstream; or call
|
|
142
|
+
* `ref.current.startAnimation()`).
|
|
133
143
|
*
|
|
134
144
|
* Deliberately imports no `motion` — the mirrored icon component carries that
|
|
135
|
-
* dependency, so the base package stays lightweight.
|
|
136
|
-
*
|
|
137
|
-
*
|
|
145
|
+
* dependency, so the base package stays lightweight. Every generated mirror reads
|
|
146
|
+
* Motion's `useReducedMotion()` preference intrinsically and settles immediately at
|
|
147
|
+
* its static resting state when the user requests reduced motion.
|
|
138
148
|
*
|
|
139
149
|
* @example
|
|
140
150
|
* 'use client';
|
|
@@ -149,6 +159,6 @@ interface AnimatedIconProps extends Omit<React.HTMLAttributes<HTMLDivElement>, '
|
|
|
149
159
|
* <AnimatedIcon as={ActivityIcon} ref={ref} />
|
|
150
160
|
* <button onClick={() => ref.current?.startAnimation()}>Play</button>
|
|
151
161
|
*/
|
|
152
|
-
declare
|
|
162
|
+
declare function AnimatedIcon({ as: Cmp, size, ref, "aria-label": label, ...props }: AnimatedIconProps): React.JSX.Element;
|
|
153
163
|
|
|
154
164
|
export { AnimatedIcon, type AnimatedIconComponent, type AnimatedIconHandle, type AnimatedIconProps, type AnimatedIconSize, BrandIcon, type BrandIconModule, type BrandIconProps, type BrandIconSize, Icon, type IconProps, type IconSize };
|
package/dist/icons/index.js
CHANGED
|
@@ -6,7 +6,12 @@ import {
|
|
|
6
6
|
import "react";
|
|
7
7
|
import { jsx } from "react/jsx-runtime";
|
|
8
8
|
var SIZES = { xs: 14, sm: 16, md: 20, lg: 24 };
|
|
9
|
-
function Icon({
|
|
9
|
+
function Icon({
|
|
10
|
+
as: Cmp,
|
|
11
|
+
size = "md",
|
|
12
|
+
"aria-label": label,
|
|
13
|
+
...props
|
|
14
|
+
}) {
|
|
10
15
|
return /* @__PURE__ */ jsx(
|
|
11
16
|
Cmp,
|
|
12
17
|
{
|
|
@@ -22,8 +27,22 @@ function Icon({ as: Cmp, size = "md", "aria-label": label, ...props }) {
|
|
|
22
27
|
|
|
23
28
|
// src/icons/brand-icon.tsx
|
|
24
29
|
import "react";
|
|
25
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
26
|
-
var SIZE_CLASS = {
|
|
30
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
31
|
+
var SIZE_CLASS = {
|
|
32
|
+
xs: "size-3.5",
|
|
33
|
+
sm: "size-4",
|
|
34
|
+
md: "size-5",
|
|
35
|
+
lg: "size-6"
|
|
36
|
+
};
|
|
37
|
+
function fitToBox(svg) {
|
|
38
|
+
return svg.replace(/<svg\b[^>]*>/i, (openTag) => {
|
|
39
|
+
const cleaned = openTag.replace(/\s(?:width|height)\s*=\s*"[^"]*"/gi, "").replace(/\s(?:width|height)\s*=\s*'[^']*'/gi, "").replace(/\spreserveAspectRatio\s*=\s*"[^"]*"/gi, "");
|
|
40
|
+
return cleaned.replace(
|
|
41
|
+
/\s*\/?>$/,
|
|
42
|
+
(end) => ` preserveAspectRatio="xMidYMid meet"${end.trimStart()}`
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
27
46
|
function BrandIcon({
|
|
28
47
|
icon,
|
|
29
48
|
variant = "color",
|
|
@@ -32,28 +51,77 @@ function BrandIcon({
|
|
|
32
51
|
className,
|
|
33
52
|
...props
|
|
34
53
|
}) {
|
|
35
|
-
const variantKey = variant === "color" ? "default" : variant;
|
|
36
|
-
const svg = icon.variants?.[variantKey] ?? icon.svg;
|
|
37
54
|
const accessibleLabel = label === void 0 ? icon.title : label;
|
|
55
|
+
const boxClassName = cn(
|
|
56
|
+
"inline-flex shrink-0 [&_svg]:block [&_svg]:size-full",
|
|
57
|
+
SIZE_CLASS[size],
|
|
58
|
+
className
|
|
59
|
+
);
|
|
60
|
+
const pick = (key) => icon.variants?.[key] ?? icon.variants?.default ?? icon.svg;
|
|
61
|
+
if (variant === "auto") {
|
|
62
|
+
const lightSvg = pick("light");
|
|
63
|
+
const darkSvg = pick("dark");
|
|
64
|
+
if (lightSvg !== darkSvg) {
|
|
65
|
+
return /* @__PURE__ */ jsxs(
|
|
66
|
+
"span",
|
|
67
|
+
{
|
|
68
|
+
role: accessibleLabel ? "img" : void 0,
|
|
69
|
+
"aria-label": accessibleLabel || void 0,
|
|
70
|
+
"aria-hidden": accessibleLabel ? void 0 : true,
|
|
71
|
+
className: boxClassName,
|
|
72
|
+
...props,
|
|
73
|
+
children: [
|
|
74
|
+
/* @__PURE__ */ jsx2(
|
|
75
|
+
"span",
|
|
76
|
+
{
|
|
77
|
+
"data-brand-artwork": "light",
|
|
78
|
+
dangerouslySetInnerHTML: { __html: fitToBox(lightSvg) }
|
|
79
|
+
}
|
|
80
|
+
),
|
|
81
|
+
/* @__PURE__ */ jsx2(
|
|
82
|
+
"span",
|
|
83
|
+
{
|
|
84
|
+
"data-brand-artwork": "dark",
|
|
85
|
+
dangerouslySetInnerHTML: { __html: fitToBox(darkSvg) }
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const variantKey = variant === "color" || variant === "auto" ? "default" : variant;
|
|
94
|
+
const svg = icon.variants?.[variantKey] ?? icon.svg;
|
|
38
95
|
return /* @__PURE__ */ jsx2(
|
|
39
96
|
"span",
|
|
40
97
|
{
|
|
41
98
|
role: accessibleLabel ? "img" : void 0,
|
|
42
99
|
"aria-label": accessibleLabel || void 0,
|
|
43
100
|
"aria-hidden": accessibleLabel ? void 0 : true,
|
|
44
|
-
className:
|
|
45
|
-
dangerouslySetInnerHTML: { __html: svg },
|
|
101
|
+
className: boxClassName,
|
|
102
|
+
dangerouslySetInnerHTML: { __html: fitToBox(svg) },
|
|
46
103
|
...props
|
|
47
104
|
}
|
|
48
105
|
);
|
|
49
106
|
}
|
|
50
107
|
|
|
51
108
|
// src/icons/animated-icon.tsx
|
|
52
|
-
import
|
|
109
|
+
import "react";
|
|
53
110
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
54
|
-
var SIZES2 = {
|
|
55
|
-
var
|
|
56
|
-
|
|
111
|
+
var SIZES2 = {
|
|
112
|
+
xs: "var(--icon-inline)",
|
|
113
|
+
sm: "var(--icon-default)",
|
|
114
|
+
md: "var(--icon-action)",
|
|
115
|
+
lg: "var(--icon-feature)"
|
|
116
|
+
};
|
|
117
|
+
function AnimatedIcon({
|
|
118
|
+
as: Cmp,
|
|
119
|
+
size = "md",
|
|
120
|
+
ref,
|
|
121
|
+
"aria-label": label,
|
|
122
|
+
...props
|
|
123
|
+
}) {
|
|
124
|
+
return /* @__PURE__ */ jsx3(
|
|
57
125
|
Cmp,
|
|
58
126
|
{
|
|
59
127
|
ref,
|
|
@@ -63,9 +131,8 @@ var AnimatedIcon = React3.forwardRef(
|
|
|
63
131
|
"aria-label": label,
|
|
64
132
|
...props
|
|
65
133
|
}
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
AnimatedIcon.displayName = "AnimatedIcon";
|
|
134
|
+
);
|
|
135
|
+
}
|
|
69
136
|
export {
|
|
70
137
|
AnimatedIcon,
|
|
71
138
|
BrandIcon,
|