@nice2dev/icons 1.3.13 → 1.3.15

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.
@@ -1,457 +0,0 @@
1
- 'use strict';
2
-
3
- var jsxRuntime = require('react/jsx-runtime');
4
- var React = require('react');
5
-
6
- /* ══════════════════════════════════════════════════════════════════════════════
7
- Constants
8
- ══════════════════════════════════════════════════════════════════════════════ */
9
- const DEFAULT_COLORS = [
10
- '#000000',
11
- '#ffffff',
12
- '#ef4444',
13
- '#f97316',
14
- '#eab308',
15
- '#22c55e',
16
- '#14b8a6',
17
- '#3b82f6',
18
- '#8b5cf6',
19
- '#ec4899',
20
- '#6b7280',
21
- '#0ea5e9',
22
- ];
23
- const DEFAULT_SIZES = [16, 20, 24, 32, 48, 64, 96, 128, 256];
24
- const BADGE_POSITIONS = [
25
- { value: 'top-right', label: 'Top Right' },
26
- { value: 'top-left', label: 'Top Left' },
27
- { value: 'bottom-right', label: 'Bottom Right' },
28
- { value: 'bottom-left', label: 'Bottom Left' },
29
- ];
30
- const DEFAULT_STATE = {
31
- fill: 'currentColor',
32
- stroke: 'none',
33
- strokeWidth: 0,
34
- size: 64,
35
- rotation: 0,
36
- flipX: false,
37
- flipY: false,
38
- opacity: 1,
39
- };
40
- /* ══════════════════════════════════════════════════════════════════════════════
41
- Utility Functions
42
- ══════════════════════════════════════════════════════════════════════════════ */
43
- /**
44
- * Gets badge position coordinates.
45
- */
46
- function getBadgeOffset(position, containerSize, badgeSize) {
47
- const offset = badgeSize * 0.25;
48
- switch (position) {
49
- case 'top-right':
50
- return { x: containerSize - badgeSize + offset, y: -offset };
51
- case 'top-left':
52
- return { x: -offset, y: -offset };
53
- case 'bottom-right':
54
- return { x: containerSize - badgeSize + offset, y: containerSize - badgeSize + offset };
55
- case 'bottom-left':
56
- return { x: -offset, y: containerSize - badgeSize + offset };
57
- }
58
- }
59
- /**
60
- * Converts SVG to PNG data URL.
61
- */
62
- async function svgToPng(svgElement, size, backgroundColor) {
63
- return new Promise((resolve, reject) => {
64
- const canvas = document.createElement('canvas');
65
- canvas.width = size;
66
- canvas.height = size;
67
- const ctx = canvas.getContext('2d');
68
- if (!ctx) {
69
- reject(new Error('Failed to get canvas context'));
70
- return;
71
- }
72
- // Background
73
- if (backgroundColor) {
74
- ctx.fillStyle = backgroundColor;
75
- ctx.fillRect(0, 0, size, size);
76
- }
77
- // SVG to image
78
- const svgData = new XMLSerializer().serializeToString(svgElement);
79
- const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
80
- const url = URL.createObjectURL(svgBlob);
81
- const img = new Image();
82
- img.onload = () => {
83
- ctx.drawImage(img, 0, 0, size, size);
84
- URL.revokeObjectURL(url);
85
- resolve(canvas.toDataURL('image/png'));
86
- };
87
- img.onerror = () => {
88
- URL.revokeObjectURL(url);
89
- reject(new Error('Failed to load SVG'));
90
- };
91
- img.src = url;
92
- });
93
- }
94
- /**
95
- * Converts SVG to ICO format (simplified - creates PNG with .ico header).
96
- */
97
- async function svgToIco(svgElement, sizes = [16, 32, 48]) {
98
- // For a proper ICO, we would need to create the ICO binary format
99
- // For simplicity, we'll create a PNG at the largest size
100
- const largestSize = Math.max(...sizes);
101
- const pngData = await svgToPng(svgElement, largestSize);
102
- const response = await fetch(pngData);
103
- return response.blob();
104
- }
105
- /**
106
- * Generates React component code from state.
107
- */
108
- function generateReactCode(iconName, state) {
109
- const props = [];
110
- if (state.fill !== 'currentColor') {
111
- props.push(`fill="${state.fill}"`);
112
- }
113
- if (state.stroke !== 'none') {
114
- props.push(`stroke="${state.stroke}"`);
115
- }
116
- if (state.strokeWidth > 0) {
117
- props.push(`strokeWidth={${state.strokeWidth}}`);
118
- }
119
- if (state.size !== 24) {
120
- props.push(`size={${state.size}}`);
121
- }
122
- if (state.rotation !== 0) {
123
- props.push(`rotate={${state.rotation}}`);
124
- }
125
- if (state.flipX) {
126
- props.push(`flipX`);
127
- }
128
- if (state.flipY) {
129
- props.push(`flipY`);
130
- }
131
- if (state.opacity !== 1) {
132
- props.push(`opacity={${state.opacity}}`);
133
- }
134
- const propsStr = props.length > 0 ? ` ${props.join(' ')}` : '';
135
- return `<${iconName}${propsStr} />`;
136
- }
137
- /**
138
- * Hook for managing icon editor state.
139
- */
140
- function useIconEditor(options = {}) {
141
- const [state, setState] = React.useState({
142
- ...DEFAULT_STATE,
143
- ...options.defaultState,
144
- });
145
- const updateState = React.useCallback((updates) => {
146
- setState((prev) => {
147
- var _a;
148
- const next = { ...prev, ...updates };
149
- (_a = options.onChange) === null || _a === void 0 ? void 0 : _a.call(options, next);
150
- return next;
151
- });
152
- }, [options.onChange]);
153
- return {
154
- state,
155
- setFill: React.useCallback((fill) => updateState({ fill }), [updateState]),
156
- setStroke: React.useCallback((stroke) => updateState({ stroke }), [updateState]),
157
- setStrokeWidth: React.useCallback((strokeWidth) => updateState({ strokeWidth }), [updateState]),
158
- setSize: React.useCallback((size) => updateState({ size }), [updateState]),
159
- setRotation: React.useCallback((rotation) => updateState({ rotation }), [updateState]),
160
- setFlipX: React.useCallback((flipX) => updateState({ flipX }), [updateState]),
161
- setFlipY: React.useCallback((flipY) => updateState({ flipY }), [updateState]),
162
- setOpacity: React.useCallback((opacity) => updateState({ opacity }), [updateState]),
163
- setBadge: React.useCallback((badge) => updateState({ badge }), [updateState]),
164
- reset: React.useCallback(() => setState({ ...DEFAULT_STATE, ...options.defaultState }), [options.defaultState]),
165
- };
166
- }
167
- /* ══════════════════════════════════════════════════════════════════════════════
168
- Component
169
- ══════════════════════════════════════════════════════════════════════════════ */
170
- /**
171
- * Icon editor component for modifying and exporting icons.
172
- */
173
- function NiceIconEditor({ icon: Icon, iconName = 'Icon', defaultState, onChange, onExport, badges = [], colorPresets = DEFAULT_COLORS, sizePresets = DEFAULT_SIZES, showAdvanced = true, className = '', }) {
174
- const editor = useIconEditor({ defaultState, onChange });
175
- const { state } = editor;
176
- const previewRef = React.useRef(null);
177
- // Transform style
178
- const transformStyle = React.useMemo(() => {
179
- const transforms = [];
180
- if (state.rotation !== 0) {
181
- transforms.push(`rotate(${state.rotation}deg)`);
182
- }
183
- if (state.flipX) {
184
- transforms.push('scaleX(-1)');
185
- }
186
- if (state.flipY) {
187
- transforms.push('scaleY(-1)');
188
- }
189
- return transforms.length > 0 ? transforms.join(' ') : undefined;
190
- }, [state.rotation, state.flipX, state.flipY]);
191
- // Export handler
192
- const handleExport = React.useCallback(async (format, options = {}) => {
193
- var _a;
194
- const svgElement = (_a = previewRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('svg');
195
- if (!svgElement) {
196
- return;
197
- }
198
- let data;
199
- switch (format) {
200
- case 'svg':
201
- data = new XMLSerializer().serializeToString(svgElement);
202
- break;
203
- case 'png':
204
- data = await svgToPng(svgElement, options.size || state.size, options.backgroundColor);
205
- break;
206
- case 'base64':
207
- data = await svgToPng(svgElement, options.size || state.size, options.backgroundColor);
208
- break;
209
- case 'react':
210
- data = generateReactCode(iconName, state);
211
- break;
212
- case 'ico': {
213
- const blob = await svgToIco(svgElement);
214
- data = URL.createObjectURL(blob);
215
- break;
216
- }
217
- default:
218
- data = new XMLSerializer().serializeToString(svgElement);
219
- }
220
- onExport === null || onExport === void 0 ? void 0 : onExport(data, format);
221
- }, [state, iconName, onExport]);
222
- // Download handler
223
- const handleDownload = React.useCallback(async (format) => {
224
- var _a;
225
- const svgElement = (_a = previewRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('svg');
226
- if (!svgElement) {
227
- return;
228
- }
229
- let blob;
230
- let filename;
231
- switch (format) {
232
- case 'svg':
233
- blob = new Blob([new XMLSerializer().serializeToString(svgElement)], {
234
- type: 'image/svg+xml',
235
- });
236
- filename = `${iconName}.svg`;
237
- break;
238
- case 'png': {
239
- const pngData = await svgToPng(svgElement, state.size);
240
- const response = await fetch(pngData);
241
- blob = await response.blob();
242
- filename = `${iconName}.png`;
243
- break;
244
- }
245
- default:
246
- return;
247
- }
248
- const url = URL.createObjectURL(blob);
249
- const a = document.createElement('a');
250
- a.href = url;
251
- a.download = filename;
252
- document.body.appendChild(a);
253
- a.click();
254
- document.body.removeChild(a);
255
- URL.revokeObjectURL(url);
256
- }, [state.size, iconName]);
257
- return (jsxRuntime.jsxs("div", { className: `nice-icon-editor ${className}`, style: styles.container, children: [jsxRuntime.jsxs("div", { style: styles.previewSection, children: [jsxRuntime.jsx("div", { ref: previewRef, style: {
258
- ...styles.preview,
259
- backgroundColor: state.fill === '#ffffff' ? '#1e1e1e' : '#f5f5f5',
260
- }, children: jsxRuntime.jsxs("div", { style: { position: 'relative', display: 'inline-block' }, children: [jsxRuntime.jsx(Icon, { size: state.size, fill: state.fill, stroke: state.stroke, strokeWidth: state.strokeWidth, style: {
261
- transform: transformStyle,
262
- opacity: state.opacity,
263
- transition: 'all 0.2s ease',
264
- } }), state.badge && (jsxRuntime.jsx("div", { style: {
265
- position: 'absolute',
266
- ...getBadgeOffset(state.badge.position, state.size, state.badge.size),
267
- }, children: jsxRuntime.jsx(state.badge.icon, { size: state.badge.size, fill: state.badge.fill }) }))] }) }), jsxRuntime.jsxs("div", { style: styles.sizeInfo, children: [state.size, "\u00D7", state.size, "px"] })] }), jsxRuntime.jsxs("div", { style: styles.controls, children: [jsxRuntime.jsx(ControlGroup, { label: "Fill Color", children: jsxRuntime.jsx(ColorPicker, { value: state.fill, onChange: editor.setFill, presets: colorPresets }) }), jsxRuntime.jsx(ControlGroup, { label: "Stroke", children: jsxRuntime.jsxs("div", { style: styles.row, children: [jsxRuntime.jsx(ColorPicker, { value: state.stroke, onChange: editor.setStroke, presets: ['none', ...colorPresets] }), jsxRuntime.jsx(RangeInput, { min: 0, max: 5, step: 0.5, value: state.strokeWidth, onChange: editor.setStrokeWidth, label: "Width" })] }) }), jsxRuntime.jsx(ControlGroup, { label: "Size", children: jsxRuntime.jsxs("div", { style: styles.row, children: [jsxRuntime.jsx("select", { value: state.size, onChange: (e) => editor.setSize(Number(e.target.value)), style: styles.select, children: sizePresets.map((s) => (jsxRuntime.jsxs("option", { value: s, children: [s, "px"] }, s))) }), jsxRuntime.jsx("input", { type: "number", value: state.size, onChange: (e) => editor.setSize(Number(e.target.value)), min: 8, max: 512, style: styles.numberInput })] }) }), showAdvanced && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs(ControlGroup, { label: "Transform", children: [jsxRuntime.jsx(RangeInput, { min: 0, max: 360, value: state.rotation, onChange: editor.setRotation, label: `Rotate: ${state.rotation}°` }), jsxRuntime.jsxs("div", { style: styles.row, children: [jsxRuntime.jsx(ToggleButton, { active: state.flipX, onClick: () => editor.setFlipX(!state.flipX), label: "Flip X" }), jsxRuntime.jsx(ToggleButton, { active: state.flipY, onClick: () => editor.setFlipY(!state.flipY), label: "Flip Y" })] })] }), jsxRuntime.jsx(ControlGroup, { label: "Opacity", children: jsxRuntime.jsx(RangeInput, { min: 0, max: 1, step: 0.1, value: state.opacity, onChange: editor.setOpacity, label: `${Math.round(state.opacity * 100)}%` }) }), badges.length > 0 && (jsxRuntime.jsxs(ControlGroup, { label: "Badge", children: [jsxRuntime.jsxs("select", { value: state.badge ? 'badge' : 'none', onChange: (e) => {
268
- if (e.target.value === 'none') {
269
- editor.setBadge(undefined);
270
- }
271
- else {
272
- const badgeIcon = badges.find((b) => b.name === e.target.value);
273
- if (badgeIcon) {
274
- editor.setBadge({
275
- icon: badgeIcon.icon,
276
- position: 'top-right',
277
- size: Math.max(16, state.size * 0.4),
278
- fill: '#ef4444',
279
- });
280
- }
281
- }
282
- }, style: styles.select, children: [jsxRuntime.jsx("option", { value: "none", children: "None" }), badges.map((b) => (jsxRuntime.jsx("option", { value: b.name, children: b.name }, b.name)))] }), state.badge && (jsxRuntime.jsx("select", { value: state.badge.position, onChange: (e) => {
283
- if (state.badge) {
284
- editor.setBadge({
285
- ...state.badge,
286
- position: e.target.value,
287
- });
288
- }
289
- }, style: styles.select, children: BADGE_POSITIONS.map((p) => (jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))) }))] }))] })), jsxRuntime.jsx(ControlGroup, { label: "Export", children: jsxRuntime.jsxs("div", { style: styles.exportButtons, children: [jsxRuntime.jsx("button", { onClick: () => handleDownload('svg'), style: styles.exportButton, children: "SVG" }), jsxRuntime.jsx("button", { onClick: () => handleDownload('png'), style: styles.exportButton, children: "PNG" }), jsxRuntime.jsx("button", { onClick: () => handleExport('react'), style: styles.exportButton, children: "React" })] }) }), jsxRuntime.jsx("button", { onClick: editor.reset, style: styles.resetButton, children: "Reset" })] })] }));
290
- }
291
- /* ══════════════════════════════════════════════════════════════════════════════
292
- Sub-components
293
- ══════════════════════════════════════════════════════════════════════════════ */
294
- function ControlGroup({ label, children }) {
295
- return (jsxRuntime.jsxs("div", { style: styles.controlGroup, children: [jsxRuntime.jsx("label", { style: styles.label, children: label }), children] }));
296
- }
297
- function ColorPicker({ value, onChange, presets, }) {
298
- return (jsxRuntime.jsxs("div", { style: styles.colorPicker, children: [jsxRuntime.jsx("input", { type: "color", value: value === 'none' || value === 'currentColor' ? '#000000' : value, onChange: (e) => onChange(e.target.value), style: styles.colorInput }), jsxRuntime.jsx("div", { style: styles.colorPresets, children: presets.map((color) => (jsxRuntime.jsx("button", { onClick: () => onChange(color), style: {
299
- ...styles.colorSwatch,
300
- backgroundColor: color === 'none' ? 'transparent' : color,
301
- border: value === color ? '2px solid #3b82f6' : '1px solid #ccc',
302
- }, title: color, children: color === 'none' && '×' }, color))) })] }));
303
- }
304
- function RangeInput({ min, max, step = 1, value, onChange, label, }) {
305
- return (jsxRuntime.jsxs("div", { style: styles.rangeContainer, children: [label && jsxRuntime.jsx("span", { style: styles.rangeLabel, children: label }), jsxRuntime.jsx("input", { type: "range", min: min, max: max, step: step, value: value, onChange: (e) => onChange(Number(e.target.value)), style: styles.rangeInput })] }));
306
- }
307
- function ToggleButton({ active, onClick, label, }) {
308
- return (jsxRuntime.jsx("button", { onClick: onClick, style: {
309
- ...styles.toggleButton,
310
- backgroundColor: active ? '#3b82f6' : '#e5e7eb',
311
- color: active ? '#fff' : '#374151',
312
- }, children: label }));
313
- }
314
- /* ══════════════════════════════════════════════════════════════════════════════
315
- Styles
316
- ══════════════════════════════════════════════════════════════════════════════ */
317
- const styles = {
318
- container: {
319
- display: 'flex',
320
- gap: '24px',
321
- padding: '16px',
322
- fontFamily: 'system-ui, -apple-system, sans-serif',
323
- fontSize: '14px',
324
- },
325
- previewSection: {
326
- display: 'flex',
327
- flexDirection: 'column',
328
- alignItems: 'center',
329
- gap: '8px',
330
- },
331
- preview: {
332
- display: 'flex',
333
- alignItems: 'center',
334
- justifyContent: 'center',
335
- padding: '32px',
336
- borderRadius: '8px',
337
- border: '1px solid #e5e7eb',
338
- minWidth: '200px',
339
- minHeight: '200px',
340
- },
341
- sizeInfo: {
342
- fontSize: '12px',
343
- color: '#6b7280',
344
- },
345
- controls: {
346
- display: 'flex',
347
- flexDirection: 'column',
348
- gap: '16px',
349
- minWidth: '280px',
350
- },
351
- controlGroup: {
352
- display: 'flex',
353
- flexDirection: 'column',
354
- gap: '8px',
355
- },
356
- label: {
357
- fontWeight: 600,
358
- color: '#374151',
359
- fontSize: '12px',
360
- textTransform: 'uppercase',
361
- letterSpacing: '0.5px',
362
- },
363
- row: {
364
- display: 'flex',
365
- gap: '8px',
366
- alignItems: 'center',
367
- },
368
- colorPicker: {
369
- display: 'flex',
370
- flexDirection: 'column',
371
- gap: '8px',
372
- },
373
- colorInput: {
374
- width: '100%',
375
- height: '32px',
376
- border: 'none',
377
- borderRadius: '4px',
378
- cursor: 'pointer',
379
- },
380
- colorPresets: {
381
- display: 'flex',
382
- flexWrap: 'wrap',
383
- gap: '4px',
384
- },
385
- colorSwatch: {
386
- width: '24px',
387
- height: '24px',
388
- borderRadius: '4px',
389
- cursor: 'pointer',
390
- fontSize: '14px',
391
- display: 'flex',
392
- alignItems: 'center',
393
- justifyContent: 'center',
394
- color: '#999',
395
- },
396
- select: {
397
- flex: 1,
398
- padding: '6px 8px',
399
- borderRadius: '4px',
400
- border: '1px solid #d1d5db',
401
- fontSize: '14px',
402
- },
403
- numberInput: {
404
- width: '70px',
405
- padding: '6px 8px',
406
- borderRadius: '4px',
407
- border: '1px solid #d1d5db',
408
- fontSize: '14px',
409
- },
410
- rangeContainer: {
411
- display: 'flex',
412
- flexDirection: 'column',
413
- gap: '4px',
414
- },
415
- rangeLabel: {
416
- fontSize: '12px',
417
- color: '#6b7280',
418
- },
419
- rangeInput: {
420
- width: '100%',
421
- },
422
- toggleButton: {
423
- flex: 1,
424
- padding: '8px 12px',
425
- borderRadius: '4px',
426
- border: 'none',
427
- cursor: 'pointer',
428
- fontWeight: 500,
429
- transition: 'all 0.2s',
430
- },
431
- exportButtons: {
432
- display: 'flex',
433
- gap: '8px',
434
- },
435
- exportButton: {
436
- flex: 1,
437
- padding: '8px 12px',
438
- borderRadius: '4px',
439
- border: '1px solid #d1d5db',
440
- backgroundColor: '#fff',
441
- cursor: 'pointer',
442
- fontWeight: 500,
443
- transition: 'all 0.2s',
444
- },
445
- resetButton: {
446
- padding: '8px 16px',
447
- borderRadius: '4px',
448
- border: '1px solid #ef4444',
449
- backgroundColor: '#fff',
450
- color: '#ef4444',
451
- cursor: 'pointer',
452
- fontWeight: 500,
453
- },
454
- };
455
-
456
- exports.NiceIconEditor = NiceIconEditor;
457
- exports.useIconEditor = useIconEditor;