@nice2dev/icons 1.1.5 → 1.1.7
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/CHANGELOG.md +52 -0
- package/dist/cjs/NiceDesktopIconExporter.js +0 -1
- package/dist/cjs/NiceFaviconGenerator.js +0 -1
- package/dist/cjs/NiceIconEditor.js +0 -1
- package/dist/cjs/NiceIconPicker.js +0 -1
- package/dist/cjs/NiceIconPreview.js +0 -1
- package/dist/cjs/NiceIconSetCreator.js +0 -1
- package/dist/cjs/NtdIconBadge.js +0 -1
- package/dist/cjs/NtdIconMorph.js +0 -1
- package/dist/cjs/NtdStateMorph.js +0 -1
- package/dist/cjs/advancedIconSearch.js +0 -1
- package/dist/cjs/animationControls.js +0 -1
- package/dist/cjs/client.js +33 -0
- package/dist/cjs/createIcon.js +0 -1
- package/dist/cjs/design-tokens.js +459 -0
- package/dist/cjs/figmaExport.js +0 -1
- package/dist/cjs/figmaSync.js +0 -1
- package/dist/cjs/headless-ui.js +430 -0
- package/dist/cjs/iconAnalytics.js +0 -1
- package/dist/cjs/iconMetadata.js +0 -1
- package/dist/cjs/iconStyles.js +0 -1
- package/dist/cjs/icons/accessibility.js +0 -1
- package/dist/cjs/icons/actions.js +0 -1
- package/dist/cjs/icons/ai.js +0 -1
- package/dist/cjs/icons/brand.js +0 -1
- package/dist/cjs/icons/business.js +0 -1
- package/dist/cjs/icons/devtech.js +0 -1
- package/dist/cjs/icons/ecommerce.js +0 -1
- package/dist/cjs/icons/education.js +0 -1
- package/dist/cjs/icons/files.js +0 -1
- package/dist/cjs/icons/fintech.js +0 -1
- package/dist/cjs/icons/food.js +0 -1
- package/dist/cjs/icons/fun.js +0 -1
- package/dist/cjs/icons/gaming.js +0 -1
- package/dist/cjs/icons/health.js +0 -1
- package/dist/cjs/icons/index.js +0 -1
- package/dist/cjs/icons/legal.js +0 -1
- package/dist/cjs/icons/media.js +0 -1
- package/dist/cjs/icons/navigation.js +0 -1
- package/dist/cjs/icons/omniverk.js +0 -1
- package/dist/cjs/icons/realestate.js +0 -1
- package/dist/cjs/icons/saas.js +0 -1
- package/dist/cjs/icons/science.js +0 -1
- package/dist/cjs/icons/shapes.js +0 -1
- package/dist/cjs/icons/smarthome.js +0 -1
- package/dist/cjs/icons/social.js +0 -1
- package/dist/cjs/icons/sports.js +0 -1
- package/dist/cjs/icons/sustainability.js +0 -1
- package/dist/cjs/icons/travel.js +0 -1
- package/dist/cjs/icons/ui.js +0 -1
- package/dist/cjs/icons/weather.js +0 -1
- package/dist/cjs/index.js +0 -1
- package/dist/cjs/lottieIntegration.js +0 -1
- package/dist/cjs/particleEffects.js +0 -1
- package/dist/cjs/resolver.js +0 -1
- package/dist/cjs/rsc.js +209 -0
- package/dist/cjs/sharpen.js +0 -1
- package/dist/cjs/smilAnimations.js +0 -1
- package/dist/cjs/tailwind-plugin.js +0 -1
- package/dist/cjs/types.js +0 -1
- package/dist/cjs/utilities.js +401 -0
- package/dist/cjs/variantProps.js +0 -1
- package/dist/esm/NiceDesktopIconExporter.js +0 -1
- package/dist/esm/NiceFaviconGenerator.js +0 -1
- package/dist/esm/NiceIconEditor.js +0 -1
- package/dist/esm/NiceIconPicker.js +0 -1
- package/dist/esm/NiceIconPreview.js +0 -1
- package/dist/esm/NiceIconSetCreator.js +0 -1
- package/dist/esm/NtdIconBadge.js +0 -1
- package/dist/esm/NtdIconMorph.js +0 -1
- package/dist/esm/NtdStateMorph.js +0 -1
- package/dist/esm/advancedIconSearch.js +0 -1
- package/dist/esm/animationControls.js +0 -1
- package/dist/esm/client.js +5 -0
- package/dist/esm/createIcon.js +0 -1
- package/dist/esm/design-tokens.js +446 -0
- package/dist/esm/figmaExport.js +0 -1
- package/dist/esm/figmaSync.js +0 -1
- package/dist/esm/headless-ui.js +413 -0
- package/dist/esm/iconAnalytics.js +0 -1
- package/dist/esm/iconMetadata.js +0 -1
- package/dist/esm/iconStyles.js +0 -1
- package/dist/esm/icons/accessibility.js +0 -1
- package/dist/esm/icons/actions.js +0 -1
- package/dist/esm/icons/ai.js +0 -1
- package/dist/esm/icons/brand.js +0 -1
- package/dist/esm/icons/business.js +0 -1
- package/dist/esm/icons/devtech.js +0 -1
- package/dist/esm/icons/ecommerce.js +0 -1
- package/dist/esm/icons/education.js +0 -1
- package/dist/esm/icons/files.js +0 -1
- package/dist/esm/icons/fintech.js +0 -1
- package/dist/esm/icons/food.js +0 -1
- package/dist/esm/icons/fun.js +0 -1
- package/dist/esm/icons/gaming.js +0 -1
- package/dist/esm/icons/health.js +0 -1
- package/dist/esm/icons/index.js +0 -1
- package/dist/esm/icons/legal.js +0 -1
- package/dist/esm/icons/media.js +0 -1
- package/dist/esm/icons/navigation.js +0 -1
- package/dist/esm/icons/omniverk.js +0 -1
- package/dist/esm/icons/realestate.js +0 -1
- package/dist/esm/icons/saas.js +0 -1
- package/dist/esm/icons/science.js +0 -1
- package/dist/esm/icons/shapes.js +0 -1
- package/dist/esm/icons/smarthome.js +0 -1
- package/dist/esm/icons/social.js +0 -1
- package/dist/esm/icons/sports.js +0 -1
- package/dist/esm/icons/sustainability.js +0 -1
- package/dist/esm/icons/travel.js +0 -1
- package/dist/esm/icons/ui.js +0 -1
- package/dist/esm/icons/weather.js +0 -1
- package/dist/esm/index.js +0 -1
- package/dist/esm/lottieIntegration.js +0 -1
- package/dist/esm/particleEffects.js +0 -1
- package/dist/esm/resolver.js +0 -1
- package/dist/esm/rsc.js +201 -0
- package/dist/esm/sharpen.js +0 -1
- package/dist/esm/smilAnimations.js +0 -1
- package/dist/esm/tailwind-plugin.js +0 -1
- package/dist/esm/types.js +0 -1
- package/dist/esm/utilities.js +384 -0
- package/dist/esm/variantProps.js +0 -1
- package/dist/types/headless-ui.d.ts +4 -4
- package/package.json +2 -2
package/dist/cjs/rsc.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
var sharpen = require('./sharpen.js');
|
|
5
|
+
var types = require('./types.js');
|
|
6
|
+
|
|
7
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
RSC-COMPATIBLE ICON FACTORY
|
|
9
|
+
───────────────────────────────────────────────────────────────────────────── */
|
|
10
|
+
/**
|
|
11
|
+
* Creates a React Server Component compatible icon.
|
|
12
|
+
*
|
|
13
|
+
* Unlike the standard `createIcon`, this version:
|
|
14
|
+
* - Does NOT use React hooks (useId, useSyncExternalStore, etc.)
|
|
15
|
+
* - Does NOT support animations
|
|
16
|
+
* - Does NOT support automatic dark/light mode detection
|
|
17
|
+
* - IS fully streamable and SSR-safe
|
|
18
|
+
*
|
|
19
|
+
* @param options - Icon configuration
|
|
20
|
+
* @returns A React function component (not using forwardRef)
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* export const NtdHomeRSC = createIconRSC({
|
|
25
|
+
* displayName: 'NtdHomeRSC',
|
|
26
|
+
* paths: {
|
|
27
|
+
* solid: ({ primaryColor }) => <path d="..." fill={primaryColor} />,
|
|
28
|
+
* outline: ({ primaryColor, strokeWidth }) => <path d="..." stroke={primaryColor} />,
|
|
29
|
+
* duotone: ({ primaryColor, secondaryColor }) => <path d="..." />,
|
|
30
|
+
* },
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
function createIconRSC(options) {
|
|
35
|
+
const { displayName, viewBox = '0 0 24 24', paths } = options;
|
|
36
|
+
function IconRSC(props) {
|
|
37
|
+
var _a;
|
|
38
|
+
const { size = 'md', variant = 'solid', cutoutShape = 'rounded-square', primaryColor: primaryColorProp, secondaryColor: secondaryColorProp, accentColor: accentColorProp, secondaryOpacity = 0.35, strokeWidth = 1.5, gradient, gradientDirection = 135, color, rotate, flipX, flipY, scale, className, style, 'aria-label': ariaLabel, 'aria-hidden': ariaHidden, title, role = ariaLabel ? 'img' : 'presentation', 'data-testid': testId, } = props;
|
|
39
|
+
// Resolve size
|
|
40
|
+
const resolvedSize = typeof size === 'number' ? size : types.SIZE_MAP[size] || types.SIZE_MAP.md;
|
|
41
|
+
// Resolve colors with semantic color support
|
|
42
|
+
let primaryColor = primaryColorProp !== null && primaryColorProp !== void 0 ? primaryColorProp : 'currentColor';
|
|
43
|
+
const secondaryColor = secondaryColorProp !== null && secondaryColorProp !== void 0 ? secondaryColorProp : 'currentColor';
|
|
44
|
+
const accentColor = accentColorProp !== null && accentColorProp !== void 0 ? accentColorProp : primaryColor;
|
|
45
|
+
if (color && types.SEMANTIC_COLOR_DEFAULTS[color]) {
|
|
46
|
+
if (!primaryColorProp) {
|
|
47
|
+
primaryColor = types.SEMANTIC_COLOR_DEFAULTS[color];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Resolve variant with fallback. `wireframe` always starts from `solid`
|
|
51
|
+
// (the full construction) — it is re-drawn mechanically, not authored.
|
|
52
|
+
const isCutout = variant === 'cutout' || variant === 'flat';
|
|
53
|
+
const pathRenderer = isCutout || variant === 'wireframe' ? paths.solid : ((_a = paths[variant]) !== null && _a !== void 0 ? _a : paths.solid);
|
|
54
|
+
// Build render props
|
|
55
|
+
const renderProps = {
|
|
56
|
+
primaryColor,
|
|
57
|
+
secondaryColor,
|
|
58
|
+
accentColor,
|
|
59
|
+
secondaryOpacity,
|
|
60
|
+
strokeWidth,
|
|
61
|
+
};
|
|
62
|
+
// Build transforms
|
|
63
|
+
const transforms = [];
|
|
64
|
+
if (rotate) {
|
|
65
|
+
transforms.push(`rotate(${rotate}deg)`);
|
|
66
|
+
}
|
|
67
|
+
if (flipX) {
|
|
68
|
+
transforms.push('scaleX(-1)');
|
|
69
|
+
}
|
|
70
|
+
if (flipY) {
|
|
71
|
+
transforms.push('scaleY(-1)');
|
|
72
|
+
}
|
|
73
|
+
if (scale && scale !== 1) {
|
|
74
|
+
transforms.push(`scale(${scale})`);
|
|
75
|
+
}
|
|
76
|
+
// Build gradient defs if needed
|
|
77
|
+
let gradientDefs = null;
|
|
78
|
+
let gradientId = null;
|
|
79
|
+
if (gradient && types.GRADIENT_PRESETS[gradient]) {
|
|
80
|
+
const colors = types.GRADIENT_PRESETS[gradient];
|
|
81
|
+
// Generate a deterministic ID based on displayName and gradient
|
|
82
|
+
gradientId = `${displayName}-${gradient}-grad`;
|
|
83
|
+
const x1 = Math.round(50 - 50 * Math.cos((gradientDirection * Math.PI) / 180));
|
|
84
|
+
const y1 = Math.round(50 - 50 * Math.sin((gradientDirection * Math.PI) / 180));
|
|
85
|
+
const x2 = Math.round(50 + 50 * Math.cos((gradientDirection * Math.PI) / 180));
|
|
86
|
+
const y2 = Math.round(50 + 50 * Math.sin((gradientDirection * Math.PI) / 180));
|
|
87
|
+
gradientDefs = (jsxRuntime.jsx("defs", { children: jsxRuntime.jsx("linearGradient", { id: gradientId, x1: `${x1}%`, y1: `${y1}%`, x2: `${x2}%`, y2: `${y2}%`, children: colors.map((color, i) => (jsxRuntime.jsx("stop", { offset: `${(i / (colors.length - 1)) * 100}%`, stopColor: color }, i))) }) }));
|
|
88
|
+
// Override primary color with gradient reference
|
|
89
|
+
renderProps.primaryColor = `url(#${gradientId})`;
|
|
90
|
+
}
|
|
91
|
+
// Final styles
|
|
92
|
+
const finalStyle = {
|
|
93
|
+
width: resolvedSize,
|
|
94
|
+
height: resolvedSize,
|
|
95
|
+
...style,
|
|
96
|
+
...(transforms.length > 0 && { transform: transforms.join(' ') }),
|
|
97
|
+
};
|
|
98
|
+
// Cutout mask ID (deterministic for SSR)
|
|
99
|
+
const cutoutMaskId = isCutout ? `${displayName}-cutout-mask` : null;
|
|
100
|
+
// Cutout shape renderer
|
|
101
|
+
const cutoutBg = isCutout
|
|
102
|
+
? (() => {
|
|
103
|
+
const fill = renderProps.primaryColor !== 'currentColor'
|
|
104
|
+
? renderProps.secondaryColor
|
|
105
|
+
: renderProps.primaryColor;
|
|
106
|
+
const mask = `url(#${cutoutMaskId})`;
|
|
107
|
+
switch (cutoutShape) {
|
|
108
|
+
case 'circle':
|
|
109
|
+
return jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "11", fill: fill, mask: mask });
|
|
110
|
+
case 'square':
|
|
111
|
+
return jsxRuntime.jsx("rect", { x: "1", y: "1", width: "22", height: "22", fill: fill, mask: mask });
|
|
112
|
+
case 'hexagon':
|
|
113
|
+
return (jsxRuntime.jsx("polygon", { points: "12,1 22.39,6.5 22.39,17.5 12,23 1.61,17.5 1.61,6.5", fill: fill, mask: mask }));
|
|
114
|
+
case 'diamond':
|
|
115
|
+
return jsxRuntime.jsx("polygon", { points: "12,1 23,12 12,23 1,12", fill: fill, mask: mask });
|
|
116
|
+
case 'shield':
|
|
117
|
+
return (jsxRuntime.jsx("path", { d: "M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5L12 1z", fill: fill, mask: mask }));
|
|
118
|
+
case 'squircle':
|
|
119
|
+
return (jsxRuntime.jsx("path", { d: "M12 1C5.5 1 1 5.5 1 8.5v7C1 18.5 5.5 23 12 23s11-4.5 11-7.5v-7C23 5.5 18.5 1 12 1z", fill: fill, mask: mask }));
|
|
120
|
+
default:
|
|
121
|
+
return jsxRuntime.jsx("rect", { x: "1", y: "1", width: "22", height: "22", rx: "4", fill: fill, mask: mask });
|
|
122
|
+
}
|
|
123
|
+
})()
|
|
124
|
+
: null;
|
|
125
|
+
// Mirrors the client renderer in createIcon.tsx: `sharp` / `rounded` /
|
|
126
|
+
// `wireframe` / `mini` all reuse `paths.solid`, so each needs its own pass
|
|
127
|
+
// to be distinguishable.
|
|
128
|
+
const rendered = isCutout
|
|
129
|
+
? cutoutBg
|
|
130
|
+
: variant === 'sharp'
|
|
131
|
+
? sharpen.sharpenTree(pathRenderer(renderProps))
|
|
132
|
+
: variant === 'rounded'
|
|
133
|
+
? sharpen.roundTree(pathRenderer(renderProps))
|
|
134
|
+
: variant === 'wireframe'
|
|
135
|
+
? sharpen.wireTree(pathRenderer(renderProps), renderProps.primaryColor)
|
|
136
|
+
: pathRenderer(renderProps);
|
|
137
|
+
const iconContent = variant === 'mini' ? (jsxRuntime.jsx("g", { transform: `translate(12 12) scale(${sharpen.MINI_SCALE}) translate(-12 -12)`, children: rendered })) : (rendered);
|
|
138
|
+
return (jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: viewBox, width: resolvedSize, height: resolvedSize, fill: "none", className: className, style: finalStyle, role: role, "aria-label": ariaLabel, "aria-hidden": ariaHidden !== null && ariaHidden !== void 0 ? ariaHidden : !ariaLabel, "data-testid": testId, children: [title && jsxRuntime.jsx("title", { children: title }), gradientDefs, isCutout && (jsxRuntime.jsx("defs", { children: jsxRuntime.jsxs("mask", { id: cutoutMaskId, children: [jsxRuntime.jsx("rect", { x: "0", y: "0", width: "24", height: "24", fill: "white" }), jsxRuntime.jsx("g", { transform: "translate(12,12) scale(0.8) translate(-12,-12)", children: paths.solid({
|
|
139
|
+
...renderProps,
|
|
140
|
+
primaryColor: 'black',
|
|
141
|
+
secondaryColor: 'black',
|
|
142
|
+
accentColor: 'black',
|
|
143
|
+
}) })] }) })), iconContent] }));
|
|
144
|
+
}
|
|
145
|
+
IconRSC.displayName = displayName;
|
|
146
|
+
return IconRSC;
|
|
147
|
+
}
|
|
148
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
149
|
+
STREAMING-FRIENDLY UTILITIES
|
|
150
|
+
───────────────────────────────────────────────────────────────────────────── */
|
|
151
|
+
/**
|
|
152
|
+
* Renders an icon to a static SVG string (for streaming/SSR).
|
|
153
|
+
* Useful for generating static assets or email templates.
|
|
154
|
+
*/
|
|
155
|
+
function renderIconToString(Icon, props = {}) {
|
|
156
|
+
var _a;
|
|
157
|
+
try {
|
|
158
|
+
// Import react-dom/server for SSR rendering
|
|
159
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
160
|
+
const { renderToStaticMarkup } = require('react-dom/server');
|
|
161
|
+
const element = Icon(props);
|
|
162
|
+
return renderToStaticMarkup(element);
|
|
163
|
+
}
|
|
164
|
+
catch (_b) {
|
|
165
|
+
// Fallback: construct SVG manually
|
|
166
|
+
const { size = 'md', primaryColor = 'currentColor', variant: _variant = 'solid', className = '', } = props;
|
|
167
|
+
const sizeValue = (_a = types.SIZE_MAP[size]) !== null && _a !== void 0 ? _a : (typeof size === 'number' ? size : 24);
|
|
168
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="${sizeValue}" height="${sizeValue}" class="${className}" fill="${primaryColor}"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" fill="${primaryColor}"></path></svg>`;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Type guard to check if a component is an RSC-compatible icon
|
|
173
|
+
*/
|
|
174
|
+
function isRSCIcon(component) {
|
|
175
|
+
return (typeof component === 'function' &&
|
|
176
|
+
'displayName' in component &&
|
|
177
|
+
typeof component.displayName === 'string' &&
|
|
178
|
+
component.displayName.endsWith('RSC'));
|
|
179
|
+
}
|
|
180
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
181
|
+
RSC ICON WRAPPER
|
|
182
|
+
───────────────────────────────────────────────────────────────────────────── */
|
|
183
|
+
/**
|
|
184
|
+
* Creates an RSC version from an existing icon's path config.
|
|
185
|
+
* This allows creating RSC-compatible versions from standard icons.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```tsx
|
|
189
|
+
* import { NtdHome } from '@nice2dev/icons';
|
|
190
|
+
* import { wrapAsRSC } from '@nice2dev/icons/rsc';
|
|
191
|
+
*
|
|
192
|
+
* // Note: This requires access to the original path config
|
|
193
|
+
* const NtdHomeRSC = wrapAsRSC(NtdHome);
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
function wrapAsRSC(originalPaths, displayName) {
|
|
197
|
+
return createIconRSC({
|
|
198
|
+
displayName: `${displayName}RSC`,
|
|
199
|
+
paths: originalPaths,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
exports.GRADIENT_PRESETS = types.GRADIENT_PRESETS;
|
|
204
|
+
exports.SEMANTIC_COLOR_DEFAULTS = types.SEMANTIC_COLOR_DEFAULTS;
|
|
205
|
+
exports.SIZE_MAP = types.SIZE_MAP;
|
|
206
|
+
exports.createIconRSC = createIconRSC;
|
|
207
|
+
exports.isRSCIcon = isRSCIcon;
|
|
208
|
+
exports.renderIconToString = renderIconToString;
|
|
209
|
+
exports.wrapAsRSC = wrapAsRSC;
|
package/dist/cjs/sharpen.js
CHANGED
|
@@ -181,4 +181,3 @@ exports.SmilAnimationWrapper = SmilAnimationWrapper;
|
|
|
181
181
|
exports.createSmilAnimationDef = createSmilAnimationDef;
|
|
182
182
|
exports.generateSmilAnimation = generateSmilAnimation;
|
|
183
183
|
exports.isSmilSupported = isSmilSupported;
|
|
184
|
-
//# sourceMappingURL=smilAnimations.js.map
|
package/dist/cjs/types.js
CHANGED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var React = require('react');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @file utilities.ts
|
|
9
|
+
* @description Utility hooks and functions for @nice2dev/icons
|
|
10
|
+
*
|
|
11
|
+
* Provides:
|
|
12
|
+
* - useIcon hook for dynamic icon loading
|
|
13
|
+
* - useReducedMotion hook for accessibility
|
|
14
|
+
* - preloadIcons for performance optimization
|
|
15
|
+
* - Icon type guards and helpers
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* import { useIcon, useReducedMotion, preloadIcons } from '@nice2dev/icons/utilities';
|
|
20
|
+
*
|
|
21
|
+
* // Dynamic icon loading
|
|
22
|
+
* const { Icon, isLoading } = useIcon('NtdHome');
|
|
23
|
+
*
|
|
24
|
+
* // Respect user preference for reduced motion
|
|
25
|
+
* const prefersReducedMotion = useReducedMotion();
|
|
26
|
+
*
|
|
27
|
+
* // Preload icons for better performance
|
|
28
|
+
* await preloadIcons(['NtdHome', 'NtdSearch', 'NtdUser']);
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @version 1.0.0
|
|
32
|
+
* @since 2026-03
|
|
33
|
+
* @license MIT
|
|
34
|
+
*/
|
|
35
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
36
|
+
ICON REGISTRY
|
|
37
|
+
───────────────────────────────────────────────────────────────────────────── */
|
|
38
|
+
/**
|
|
39
|
+
* Global icon cache for dynamic loading
|
|
40
|
+
*/
|
|
41
|
+
const iconRegistry = new Map();
|
|
42
|
+
/**
|
|
43
|
+
* Registers an icon in the global registry
|
|
44
|
+
*/
|
|
45
|
+
function registerIcon(name, component) {
|
|
46
|
+
iconRegistry.set(name, {
|
|
47
|
+
component,
|
|
48
|
+
loadedAt: Date.now(),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Gets an icon from the registry
|
|
53
|
+
*/
|
|
54
|
+
function getIconFromRegistry(name) {
|
|
55
|
+
var _a;
|
|
56
|
+
return (_a = iconRegistry.get(name)) === null || _a === void 0 ? void 0 : _a.component;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Clears the icon registry
|
|
60
|
+
*/
|
|
61
|
+
function clearIconRegistry() {
|
|
62
|
+
iconRegistry.clear();
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Gets all registered icon names
|
|
66
|
+
*/
|
|
67
|
+
function getRegisteredIconNames() {
|
|
68
|
+
return Array.from(iconRegistry.keys());
|
|
69
|
+
}
|
|
70
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
71
|
+
USE ICON HOOK
|
|
72
|
+
───────────────────────────────────────────────────────────────────────────── */
|
|
73
|
+
/**
|
|
74
|
+
* Hook for dynamically loading icons by name.
|
|
75
|
+
*
|
|
76
|
+
* @param iconName - Name of the icon to load (e.g., 'NtdHome')
|
|
77
|
+
* @param options - Loading options
|
|
78
|
+
* @returns Icon component, loading state, and error
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```tsx
|
|
82
|
+
* function DynamicIcon({ name }: { name: string }) {
|
|
83
|
+
* const { Icon, isLoading, error } = useIcon(name);
|
|
84
|
+
*
|
|
85
|
+
* if (isLoading) return <Spinner />;
|
|
86
|
+
* if (error || !Icon) return <FallbackIcon />;
|
|
87
|
+
*
|
|
88
|
+
* return <Icon size="md" />;
|
|
89
|
+
* }
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
function useIcon(iconName, options = {}) {
|
|
93
|
+
const { fallback, cache = true } = options;
|
|
94
|
+
const [state, setState] = React.useState({
|
|
95
|
+
Icon: iconName ? getIconFromRegistry(iconName) || null : null,
|
|
96
|
+
isLoading: false,
|
|
97
|
+
error: null,
|
|
98
|
+
});
|
|
99
|
+
React.useEffect(() => {
|
|
100
|
+
if (!iconName) {
|
|
101
|
+
setState({ Icon: null, isLoading: false, error: null });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// Check cache first
|
|
105
|
+
const cached = getIconFromRegistry(iconName);
|
|
106
|
+
if (cached) {
|
|
107
|
+
setState({ Icon: cached, isLoading: false, error: null });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// Start loading
|
|
111
|
+
setState((prev) => ({ ...prev, isLoading: true, error: null }));
|
|
112
|
+
// Dynamic import (assumes icons are exported individually)
|
|
113
|
+
const loadIcon = async () => {
|
|
114
|
+
try {
|
|
115
|
+
// Try to import from the icons module
|
|
116
|
+
const module = await Promise.resolve().then(function () { return require('./icons/index.js'); });
|
|
117
|
+
const IconComponent = module[iconName];
|
|
118
|
+
if (IconComponent) {
|
|
119
|
+
if (cache) {
|
|
120
|
+
registerIcon(iconName, IconComponent);
|
|
121
|
+
}
|
|
122
|
+
setState({ Icon: IconComponent, isLoading: false, error: null });
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
throw new Error(`Icon "${iconName}" not found`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
setState({
|
|
130
|
+
Icon: fallback || null,
|
|
131
|
+
isLoading: false,
|
|
132
|
+
error: err instanceof Error ? err : new Error('Failed to load icon'),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
loadIcon();
|
|
137
|
+
}, [iconName, cache, fallback]);
|
|
138
|
+
return state;
|
|
139
|
+
}
|
|
140
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
141
|
+
USE REDUCED MOTION
|
|
142
|
+
───────────────────────────────────────────────────────────────────────────── */
|
|
143
|
+
/**
|
|
144
|
+
* Subscribes to prefers-reduced-motion media query.
|
|
145
|
+
*/
|
|
146
|
+
function subscribeToReducedMotion(callback) {
|
|
147
|
+
if (typeof window === 'undefined') {
|
|
148
|
+
return () => { };
|
|
149
|
+
}
|
|
150
|
+
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
151
|
+
const handler = () => callback();
|
|
152
|
+
mediaQuery.addEventListener('change', handler);
|
|
153
|
+
return () => mediaQuery.removeEventListener('change', handler);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Gets current reduced motion preference.
|
|
157
|
+
*/
|
|
158
|
+
function getReducedMotionSnapshot() {
|
|
159
|
+
if (typeof window === 'undefined') {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Server snapshot for SSR (assumes no reduced motion preference).
|
|
166
|
+
*/
|
|
167
|
+
function getReducedMotionServerSnapshot() {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Hook that returns true if user prefers reduced motion.
|
|
172
|
+
* Uses useSyncExternalStore for SSR compatibility.
|
|
173
|
+
*
|
|
174
|
+
* @returns Whether the user prefers reduced motion
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```tsx
|
|
178
|
+
* function AnimatedIcon() {
|
|
179
|
+
* const prefersReducedMotion = useReducedMotion();
|
|
180
|
+
*
|
|
181
|
+
* return (
|
|
182
|
+
* <NtdHeart
|
|
183
|
+
* animation={prefersReducedMotion ? undefined : 'pulse'}
|
|
184
|
+
* />
|
|
185
|
+
* );
|
|
186
|
+
* }
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
function useReducedMotion() {
|
|
190
|
+
return React.useSyncExternalStore(subscribeToReducedMotion, getReducedMotionSnapshot, getReducedMotionServerSnapshot);
|
|
191
|
+
}
|
|
192
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
193
|
+
PRELOAD ICONS
|
|
194
|
+
───────────────────────────────────────────────────────────────────────────── */
|
|
195
|
+
/**
|
|
196
|
+
* Preloads icons into the registry for instant access.
|
|
197
|
+
*
|
|
198
|
+
* @param iconNames - Array of icon names to preload
|
|
199
|
+
* @returns Promise that resolves when all icons are loaded
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```tsx
|
|
203
|
+
* // Preload critical icons at app startup
|
|
204
|
+
* useEffect(() => {
|
|
205
|
+
* preloadIcons(['NtdHome', 'NtdSearch', 'NtdUser', 'NtdMenu']);
|
|
206
|
+
* }, []);
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
async function preloadIcons(iconNames) {
|
|
210
|
+
const results = new Map();
|
|
211
|
+
try {
|
|
212
|
+
const module = await Promise.resolve().then(function () { return require('./icons/index.js'); });
|
|
213
|
+
for (const name of iconNames) {
|
|
214
|
+
const IconComponent = module[name];
|
|
215
|
+
if (IconComponent) {
|
|
216
|
+
registerIcon(name, IconComponent);
|
|
217
|
+
results.set(name, IconComponent);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
console.warn('Failed to preload icons:', error);
|
|
223
|
+
}
|
|
224
|
+
return results;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Creates a preloader component that preloads icons on mount.
|
|
228
|
+
*
|
|
229
|
+
* @param iconNames - Icons to preload
|
|
230
|
+
* @returns React component that preloads icons
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```tsx
|
|
234
|
+
* const IconPreloader = createIconPreloader(['NtdHome', 'NtdSearch']);
|
|
235
|
+
*
|
|
236
|
+
* function App() {
|
|
237
|
+
* return (
|
|
238
|
+
* <>
|
|
239
|
+
* <IconPreloader />
|
|
240
|
+
* <MainContent />
|
|
241
|
+
* </>
|
|
242
|
+
* );
|
|
243
|
+
* }
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
function createIconPreloader(iconNames) {
|
|
247
|
+
return function IconPreloader() {
|
|
248
|
+
React.useEffect(() => {
|
|
249
|
+
preloadIcons(iconNames);
|
|
250
|
+
}, []);
|
|
251
|
+
return null;
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
255
|
+
ICON PROPS HELPERS
|
|
256
|
+
───────────────────────────────────────────────────────────────────────────── */
|
|
257
|
+
/**
|
|
258
|
+
* Creates props with animation disabled if user prefers reduced motion.
|
|
259
|
+
*
|
|
260
|
+
* @param props - Original icon props
|
|
261
|
+
* @param prefersReducedMotion - Whether user prefers reduced motion
|
|
262
|
+
* @returns Props with animation conditionally disabled
|
|
263
|
+
*/
|
|
264
|
+
function withReducedMotionProps(props, prefersReducedMotion) {
|
|
265
|
+
if (!prefersReducedMotion) {
|
|
266
|
+
return props;
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
...props,
|
|
270
|
+
animation: undefined,
|
|
271
|
+
animationDuration: undefined,
|
|
272
|
+
gradientAnimation: undefined,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Creates accessible icon props based on context.
|
|
277
|
+
*
|
|
278
|
+
* @param options - Accessibility options
|
|
279
|
+
* @returns Icon props for accessibility
|
|
280
|
+
*/
|
|
281
|
+
function createAccessibleIconProps(options) {
|
|
282
|
+
const { label, isDecorative = !label, role } = options;
|
|
283
|
+
if (isDecorative) {
|
|
284
|
+
return {
|
|
285
|
+
'aria-hidden': true,
|
|
286
|
+
role: 'presentation',
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
'aria-label': label,
|
|
291
|
+
role: role || 'img',
|
|
292
|
+
title: label,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
296
|
+
ICON TYPE GUARDS
|
|
297
|
+
───────────────────────────────────────────────────────────────────────────── */
|
|
298
|
+
/**
|
|
299
|
+
* Checks if a value is a valid icon component.
|
|
300
|
+
* Handles both regular components and forwardRef components.
|
|
301
|
+
*/
|
|
302
|
+
function isIconComponent(value) {
|
|
303
|
+
if (value == null) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
// ForwardRef components are objects with $$typeof, regular components are functions
|
|
307
|
+
const isValidComponent = typeof value === 'function' || (typeof value === 'object' && '$$typeof' in value);
|
|
308
|
+
if (!isValidComponent) {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
const displayName = value.displayName;
|
|
312
|
+
return typeof displayName === 'string' && displayName.startsWith('Ntd');
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Checks if an icon name is valid.
|
|
316
|
+
*/
|
|
317
|
+
function isValidIconName(name) {
|
|
318
|
+
return /^Ntd[A-Z][a-zA-Z0-9]*$/.test(name);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Creates a function that renders multiple icons efficiently.
|
|
322
|
+
*
|
|
323
|
+
* @param configs - Array of icon configurations
|
|
324
|
+
* @returns Function that renders all configured icons
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* ```tsx
|
|
328
|
+
* const renderNavIcons = createBatchIconRenderer([
|
|
329
|
+
* { name: 'NtdHome', props: { size: 'md' } },
|
|
330
|
+
* { name: 'NtdSearch', props: { size: 'md' } },
|
|
331
|
+
* { name: 'NtdUser', props: { size: 'md' } },
|
|
332
|
+
* ]);
|
|
333
|
+
*
|
|
334
|
+
* // In component
|
|
335
|
+
* const icons = renderNavIcons();
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
function useBatchIcons(configs) {
|
|
339
|
+
const [icons, setIcons] = React.useState(new Map());
|
|
340
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
341
|
+
React.useEffect(() => {
|
|
342
|
+
const loadIcons = async () => {
|
|
343
|
+
setIsLoading(true);
|
|
344
|
+
const names = configs.map((c) => c.name);
|
|
345
|
+
const loadedIcons = await preloadIcons(names);
|
|
346
|
+
setIcons(loadedIcons);
|
|
347
|
+
setIsLoading(false);
|
|
348
|
+
};
|
|
349
|
+
loadIcons();
|
|
350
|
+
}, [configs.map((c) => c.name).join(',')]);
|
|
351
|
+
const renderIcon = React.useCallback((name, additionalProps) => {
|
|
352
|
+
const Icon = icons.get(name);
|
|
353
|
+
const config = configs.find((c) => c.name === name);
|
|
354
|
+
if (!Icon) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
const mergedProps = {
|
|
358
|
+
...config === null || config === void 0 ? void 0 : config.props,
|
|
359
|
+
...additionalProps,
|
|
360
|
+
};
|
|
361
|
+
return React.createElement(Icon, mergedProps);
|
|
362
|
+
}, [icons, configs]);
|
|
363
|
+
return {
|
|
364
|
+
renderIcon,
|
|
365
|
+
isLoading,
|
|
366
|
+
icons,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
370
|
+
EXPORTS
|
|
371
|
+
───────────────────────────────────────────────────────────────────────────── */
|
|
372
|
+
var utilities = {
|
|
373
|
+
useIcon,
|
|
374
|
+
useReducedMotion,
|
|
375
|
+
preloadIcons,
|
|
376
|
+
createIconPreloader,
|
|
377
|
+
registerIcon,
|
|
378
|
+
getIconFromRegistry,
|
|
379
|
+
clearIconRegistry,
|
|
380
|
+
getRegisteredIconNames,
|
|
381
|
+
withReducedMotionProps,
|
|
382
|
+
createAccessibleIconProps,
|
|
383
|
+
isIconComponent,
|
|
384
|
+
isValidIconName,
|
|
385
|
+
useBatchIcons,
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
exports.clearIconRegistry = clearIconRegistry;
|
|
389
|
+
exports.createAccessibleIconProps = createAccessibleIconProps;
|
|
390
|
+
exports.createIconPreloader = createIconPreloader;
|
|
391
|
+
exports.default = utilities;
|
|
392
|
+
exports.getIconFromRegistry = getIconFromRegistry;
|
|
393
|
+
exports.getRegisteredIconNames = getRegisteredIconNames;
|
|
394
|
+
exports.isIconComponent = isIconComponent;
|
|
395
|
+
exports.isValidIconName = isValidIconName;
|
|
396
|
+
exports.preloadIcons = preloadIcons;
|
|
397
|
+
exports.registerIcon = registerIcon;
|
|
398
|
+
exports.useBatchIcons = useBatchIcons;
|
|
399
|
+
exports.useIcon = useIcon;
|
|
400
|
+
exports.useReducedMotion = useReducedMotion;
|
|
401
|
+
exports.withReducedMotionProps = withReducedMotionProps;
|
package/dist/cjs/variantProps.js
CHANGED
|
@@ -649,4 +649,3 @@ const NiceDesktopIconExporter = ({ icon, initialConfig, onExport, className = ''
|
|
|
649
649
|
};
|
|
650
650
|
|
|
651
651
|
export { DESKTOP_ICON_SIZES, LINUX_HICOLOR_DIRS, NiceDesktopIconExporter, RETINA_VARIANTS, useDesktopIconExporter };
|
|
652
|
-
//# sourceMappingURL=NiceDesktopIconExporter.js.map
|