@nice2dev/icons 1.3.15 → 1.3.16

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.
Files changed (140) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/cjs/NiceDesktopIconExporter.js +688 -599
  3. package/dist/cjs/NiceFaviconGenerator.js +603 -417
  4. package/dist/cjs/NiceIconPicker.js +788 -508
  5. package/dist/cjs/NiceIconPreview.js +246 -188
  6. package/dist/cjs/NiceIconSetCreator.js +683 -552
  7. package/dist/cjs/NiceIconStacker.js +547 -171
  8. package/dist/cjs/NiceIconValue.js +27 -35
  9. package/dist/cjs/NtdIconBadge.js +384 -360
  10. package/dist/cjs/NtdIconMorph.js +419 -354
  11. package/dist/cjs/NtdStateMorph.js +150 -147
  12. package/dist/cjs/advancedIconSearch.js +373 -510
  13. package/dist/cjs/animationControls.js +518 -642
  14. package/dist/cjs/client.js +12 -15
  15. package/dist/cjs/createIcon.js +349 -251
  16. package/dist/cjs/design-tokens.js +324 -390
  17. package/dist/cjs/figmaExport.js +162 -257
  18. package/dist/cjs/figmaSync.js +234 -316
  19. package/dist/cjs/headless-ui.js +240 -368
  20. package/dist/cjs/iconAnalytics.js +337 -393
  21. package/dist/cjs/iconHtml.js +64 -86
  22. package/dist/cjs/iconMetadata.js +392 -472
  23. package/dist/cjs/iconParts.js +120 -90
  24. package/dist/cjs/iconStack.js +136 -155
  25. package/dist/cjs/iconStyles.js +1609 -504
  26. package/dist/cjs/iconValue.js +359 -430
  27. package/dist/cjs/icons/accessibility.js +1119 -134
  28. package/dist/cjs/icons/actions.js +1290 -330
  29. package/dist/cjs/icons/ai.js +1424 -144
  30. package/dist/cjs/icons/brand.js +1344 -172
  31. package/dist/cjs/icons/business.js +903 -193
  32. package/dist/cjs/icons/devtech.js +1656 -227
  33. package/dist/cjs/icons/ecommerce.js +802 -111
  34. package/dist/cjs/icons/education.js +1345 -199
  35. package/dist/cjs/icons/files.js +1597 -328
  36. package/dist/cjs/icons/fintech.js +793 -104
  37. package/dist/cjs/icons/food.js +1507 -153
  38. package/dist/cjs/icons/fun.js +786 -109
  39. package/dist/cjs/icons/gaming.js +916 -170
  40. package/dist/cjs/icons/health.js +623 -112
  41. package/dist/cjs/icons/index.js +32 -35
  42. package/dist/cjs/icons/input.js +282 -83
  43. package/dist/cjs/icons/legal.js +1065 -122
  44. package/dist/cjs/icons/media.js +1061 -267
  45. package/dist/cjs/icons/navigation.js +387 -122
  46. package/dist/cjs/icons/omniverk.js +747 -90
  47. package/dist/cjs/icons/realestate.js +1520 -193
  48. package/dist/cjs/icons/saas.js +867 -98
  49. package/dist/cjs/icons/science.js +1451 -202
  50. package/dist/cjs/icons/shapes.js +1316 -308
  51. package/dist/cjs/icons/smarthome.js +786 -90
  52. package/dist/cjs/icons/social.js +591 -59
  53. package/dist/cjs/icons/sports.js +1225 -133
  54. package/dist/cjs/icons/sustainability.js +1211 -139
  55. package/dist/cjs/icons/travel.js +1470 -148
  56. package/dist/cjs/icons/ui.js +1508 -315
  57. package/dist/cjs/icons/weather.js +1160 -173
  58. package/dist/cjs/index.js +566 -568
  59. package/dist/cjs/lottieIntegration.js +261 -266
  60. package/dist/cjs/nicetodev-icons.css +1 -1
  61. package/dist/cjs/particleEffects.js +270 -251
  62. package/dist/cjs/resolver.js +229 -284
  63. package/dist/cjs/rsc.js +175 -193
  64. package/dist/cjs/sharpen.js +73 -130
  65. package/dist/cjs/smilAnimations.js +371 -165
  66. package/dist/cjs/stacking.js +87 -122
  67. package/dist/cjs/tailwind-plugin.js +280 -320
  68. package/dist/cjs/types.js +46 -72
  69. package/dist/cjs/utilities.js +163 -324
  70. package/dist/cjs/variantProps.js +12 -52
  71. package/dist/esm/NiceDesktopIconExporter.js +671 -598
  72. package/dist/esm/NiceFaviconGenerator.js +583 -416
  73. package/dist/esm/NiceIconPicker.js +789 -507
  74. package/dist/esm/NiceIconPreview.js +247 -187
  75. package/dist/esm/NiceIconSetCreator.js +685 -551
  76. package/dist/esm/NiceIconStacker.js +549 -170
  77. package/dist/esm/NiceIconValue.js +29 -34
  78. package/dist/esm/NtdIconBadge.js +388 -359
  79. package/dist/esm/NtdIconMorph.js +423 -353
  80. package/dist/esm/NtdStateMorph.js +152 -146
  81. package/dist/esm/advancedIconSearch.js +391 -509
  82. package/dist/esm/animationControls.js +527 -641
  83. package/dist/esm/client.js +48 -10
  84. package/dist/esm/createIcon.js +350 -250
  85. package/dist/esm/design-tokens.js +334 -387
  86. package/dist/esm/figmaExport.js +170 -256
  87. package/dist/esm/figmaSync.js +237 -315
  88. package/dist/esm/headless-ui.js +254 -365
  89. package/dist/esm/iconAnalytics.js +342 -392
  90. package/dist/esm/iconHtml.js +67 -85
  91. package/dist/esm/iconMetadata.js +404 -471
  92. package/dist/esm/iconParts.js +125 -89
  93. package/dist/esm/iconStack.js +145 -154
  94. package/dist/esm/iconStyles.js +1620 -503
  95. package/dist/esm/iconValue.js +361 -429
  96. package/dist/esm/icons/accessibility.js +1133 -133
  97. package/dist/esm/icons/actions.js +1320 -329
  98. package/dist/esm/icons/ai.js +1441 -143
  99. package/dist/esm/icons/brand.js +1351 -171
  100. package/dist/esm/icons/business.js +931 -201
  101. package/dist/esm/icons/devtech.js +1687 -226
  102. package/dist/esm/icons/ecommerce.js +816 -110
  103. package/dist/esm/icons/education.js +1364 -198
  104. package/dist/esm/icons/files.js +1628 -327
  105. package/dist/esm/icons/fintech.js +807 -103
  106. package/dist/esm/icons/food.js +1524 -152
  107. package/dist/esm/icons/fun.js +801 -108
  108. package/dist/esm/icons/gaming.js +933 -169
  109. package/dist/esm/icons/health.js +636 -111
  110. package/dist/esm/icons/index.js +596 -32
  111. package/dist/esm/icons/input.js +326 -82
  112. package/dist/esm/icons/legal.js +1079 -121
  113. package/dist/esm/icons/media.js +1086 -266
  114. package/dist/esm/icons/navigation.js +401 -121
  115. package/dist/esm/icons/omniverk.js +759 -89
  116. package/dist/esm/icons/realestate.js +1544 -198
  117. package/dist/esm/icons/saas.js +880 -97
  118. package/dist/esm/icons/science.js +1477 -208
  119. package/dist/esm/icons/shapes.js +1343 -307
  120. package/dist/esm/icons/smarthome.js +799 -89
  121. package/dist/esm/icons/social.js +599 -58
  122. package/dist/esm/icons/sports.js +1240 -132
  123. package/dist/esm/icons/sustainability.js +1226 -138
  124. package/dist/esm/icons/travel.js +1486 -147
  125. package/dist/esm/icons/ui.js +1540 -314
  126. package/dist/esm/icons/weather.js +1181 -172
  127. package/dist/esm/index.js +756 -58
  128. package/dist/esm/lottieIntegration.js +264 -265
  129. package/dist/esm/nicetodev-icons.css +1 -1
  130. package/dist/esm/particleEffects.js +271 -250
  131. package/dist/esm/resolver.js +231 -283
  132. package/dist/esm/rsc.js +182 -192
  133. package/dist/esm/sharpen.js +78 -129
  134. package/dist/esm/smilAnimations.js +376 -164
  135. package/dist/esm/stacking.js +99 -121
  136. package/dist/esm/tailwind-plugin.js +282 -319
  137. package/dist/esm/types.js +50 -71
  138. package/dist/esm/utilities.js +177 -321
  139. package/dist/esm/variantProps.js +23 -52
  140. package/package.json +15 -22
@@ -1,655 +1,744 @@
1
- 'use strict';
2
-
3
- var jsxRuntime = require('react/jsx-runtime');
4
- var uiCore = require('@nice2dev/ui-core');
5
- var React = require('react');
6
-
7
- /* ══════════════════════════════════════════════════════════════════════════════
8
- Constants
9
- ══════════════════════════════════════════════════════════════════════════════ */
10
- /** Standard desktop icon sizes used across platforms */
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
25
+ const jsxRuntime = require("react/jsx-runtime");
26
+ const uiCore = require("@nice2dev/ui-core");
27
+ const React = require("react");
28
+ ;/* empty css */
11
29
  const DESKTOP_ICON_SIZES = [
12
- { size: 16, label: '16×16', platforms: ['windows', 'linux'] },
13
- { size: 24, label: '24×24', platforms: ['linux'] },
14
- { size: 32, label: '32×32', platforms: ['windows', 'linux'] },
15
- { size: 48, label: '48×48', platforms: ['windows', 'linux'] },
16
- { size: 64, label: '64×64', platforms: ['windows', 'linux'] },
17
- { size: 128, label: '128×128', platforms: ['windows', 'macos', 'linux'] },
18
- { size: 256, label: '256×256', platforms: ['windows', 'macos', 'linux'] },
19
- { size: 512, label: '512×512', platforms: ['macos', 'linux'] },
20
- { size: 1024, label: '1024×1024', platforms: ['macos'] },
30
+ { size: 16, label: "16×16", platforms: ["windows", "linux"] },
31
+ { size: 24, label: "24×24", platforms: ["linux"] },
32
+ { size: 32, label: "32×32", platforms: ["windows", "linux"] },
33
+ { size: 48, label: "48×48", platforms: ["windows", "linux"] },
34
+ { size: 64, label: "64×64", platforms: ["windows", "linux"] },
35
+ { size: 128, label: "128×128", platforms: ["windows", "macos", "linux"] },
36
+ { size: 256, label: "256×256", platforms: ["windows", "macos", "linux"] },
37
+ { size: 512, label: "512×512", platforms: ["macos", "linux"] },
38
+ { size: 1024, label: "1024×1024", platforms: ["macos"] }
21
39
  ];
22
- /** Retina/HiDPI scale factors */
23
40
  const RETINA_VARIANTS = [
24
- { scale: 1, suffix: '' },
25
- { scale: 2, suffix: '@2x' },
26
- { scale: 3, suffix: '@3x' },
41
+ { scale: 1, suffix: "" },
42
+ { scale: 2, suffix: "@2x" },
43
+ { scale: 3, suffix: "@3x" }
27
44
  ];
28
- /** Default export configuration */
29
45
  const DEFAULT_CONFIG = {
30
- name: 'icon',
31
- includeWindows: true,
32
- includeMacOS: true,
33
- includeLinux: true,
34
- includeRetina: true,
35
- backgroundColor: undefined, // transparent
36
- padding: 0.1,
37
- fillColor: '#1e1e1e',
38
- sizes: [16, 32, 48, 64, 128, 256, 512],
46
+ name: "icon",
47
+ includeWindows: true,
48
+ includeMacOS: true,
49
+ includeLinux: true,
50
+ includeRetina: true,
51
+ backgroundColor: void 0,
52
+ // transparent
53
+ padding: 0.1,
54
+ fillColor: "#1e1e1e",
55
+ sizes: [16, 32, 48, 64, 128, 256, 512]
39
56
  };
40
- /** Linux hicolor theme icon directories */
41
57
  const LINUX_HICOLOR_DIRS = [
42
- { size: 16, dir: 'hicolor/16x16/apps' },
43
- { size: 24, dir: 'hicolor/24x24/apps' },
44
- { size: 32, dir: 'hicolor/32x32/apps' },
45
- { size: 48, dir: 'hicolor/48x48/apps' },
46
- { size: 64, dir: 'hicolor/64x64/apps' },
47
- { size: 128, dir: 'hicolor/128x128/apps' },
48
- { size: 256, dir: 'hicolor/256x256/apps' },
49
- { size: 512, dir: 'hicolor/512x512/apps' },
58
+ { size: 16, dir: "hicolor/16x16/apps" },
59
+ { size: 24, dir: "hicolor/24x24/apps" },
60
+ { size: 32, dir: "hicolor/32x32/apps" },
61
+ { size: 48, dir: "hicolor/48x48/apps" },
62
+ { size: 64, dir: "hicolor/64x64/apps" },
63
+ { size: 128, dir: "hicolor/128x128/apps" },
64
+ { size: 256, dir: "hicolor/256x256/apps" },
65
+ { size: 512, dir: "hicolor/512x512/apps" }
50
66
  ];
51
- /* ══════════════════════════════════════════════════════════════════════════════
52
- Utility Functions
53
- ══════════════════════════════════════════════════════════════════════════════ */
54
- /**
55
- * Render an icon component to a canvas at the specified size
56
- */
57
67
  async function renderIconToCanvas(IconComponent, size, fillColor, backgroundColor, padding = 0.1) {
58
- const canvas = document.createElement('canvas');
59
- canvas.width = size;
60
- canvas.height = size;
61
- const ctx = canvas.getContext('2d');
62
- // Fill background (or make transparent)
63
- if (backgroundColor) {
64
- ctx.fillStyle = backgroundColor;
65
- ctx.fillRect(0, 0, size, size);
66
- }
67
- else {
68
- ctx.clearRect(0, 0, size, size);
69
- }
70
- // Render icon to temporary container
71
- const container = document.createElement('div');
72
- const iconSize = Math.round(size * (1 - padding * 2));
73
- const offset = Math.round(size * padding);
74
- const { createRoot } = await import('react-dom/client');
75
- const root = createRoot(container);
76
- return new Promise((resolve) => {
77
- root.render(jsxRuntime.jsx(IconComponent, { size: iconSize, fill: fillColor }));
78
- // Wait for render
79
- setTimeout(() => {
80
- const svg = container.querySelector('svg');
81
- if (svg) {
82
- const svgData = new XMLSerializer().serializeToString(svg);
83
- const img = new Image();
84
- img.onload = () => {
85
- ctx.drawImage(img, offset, offset, iconSize, iconSize);
86
- root.unmount();
87
- resolve(canvas);
88
- };
89
- img.onerror = () => {
90
- root.unmount();
91
- resolve(canvas);
92
- };
93
- img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgData)));
94
- }
95
- else {
96
- root.unmount();
97
- resolve(canvas);
98
- }
99
- }, 50);
100
- });
68
+ const canvas = document.createElement("canvas");
69
+ canvas.width = size;
70
+ canvas.height = size;
71
+ const ctx = canvas.getContext("2d");
72
+ if (backgroundColor) {
73
+ ctx.fillStyle = backgroundColor;
74
+ ctx.fillRect(0, 0, size, size);
75
+ } else {
76
+ ctx.clearRect(0, 0, size, size);
77
+ }
78
+ const container = document.createElement("div");
79
+ const iconSize = Math.round(size * (1 - padding * 2));
80
+ const offset = Math.round(size * padding);
81
+ const { createRoot } = await import("react-dom/client");
82
+ const root = createRoot(container);
83
+ return new Promise((resolve) => {
84
+ root.render(/* @__PURE__ */ jsxRuntime.jsx(IconComponent, { size: iconSize, fill: fillColor }));
85
+ setTimeout(() => {
86
+ const svg = container.querySelector("svg");
87
+ if (svg) {
88
+ const svgData = new XMLSerializer().serializeToString(svg);
89
+ const img = new Image();
90
+ img.onload = () => {
91
+ ctx.drawImage(img, offset, offset, iconSize, iconSize);
92
+ root.unmount();
93
+ resolve(canvas);
94
+ };
95
+ img.onerror = () => {
96
+ root.unmount();
97
+ resolve(canvas);
98
+ };
99
+ img.src = "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svgData)));
100
+ } else {
101
+ root.unmount();
102
+ resolve(canvas);
103
+ }
104
+ }, 50);
105
+ });
101
106
  }
102
- /**
103
- * Convert canvas to PNG blob
104
- */
105
107
  function canvasToBlob(canvas) {
106
- return new Promise((resolve, reject) => {
107
- canvas.toBlob((blob) => {
108
- if (blob) {
109
- resolve(blob);
110
- }
111
- else {
112
- reject(new Error('Failed to convert canvas to blob'));
113
- }
114
- }, 'image/png');
115
- });
108
+ return new Promise((resolve, reject) => {
109
+ canvas.toBlob((blob) => {
110
+ if (blob) {
111
+ resolve(blob);
112
+ } else {
113
+ reject(new Error("Failed to convert canvas to blob"));
114
+ }
115
+ }, "image/png");
116
+ });
116
117
  }
117
- /**
118
- * Create a Windows ICO file from multiple PNG images.
119
- * ICO format: https://en.wikipedia.org/wiki/ICO_(file_format)
120
- */
121
118
  async function createIcoFile(pngBuffers) {
122
- // Sort by size descending
123
- const sorted = [...pngBuffers].sort((a, b) => b.size - a.size);
124
- // ICO header: 6 bytes
125
- // Image entry: 16 bytes each
126
- const headerSize = 6;
127
- const entrySize = 16;
128
- const directorySize = headerSize + entrySize * sorted.length;
129
- // Calculate total size and offsets
130
- let currentOffset = directorySize;
131
- const entries = [];
132
- for (const png of sorted) {
133
- entries.push({ size: png.size, offset: currentOffset, data: png.data });
134
- currentOffset += png.data.byteLength;
135
- }
136
- // Create the ICO buffer
137
- const totalSize = currentOffset;
138
- const buffer = new ArrayBuffer(totalSize);
139
- const view = new DataView(buffer);
140
- const uint8 = new Uint8Array(buffer);
141
- // Write ICO header
142
- view.setUint16(0, 0, true); // Reserved (must be 0)
143
- view.setUint16(2, 1, true); // Type (1 = ICO)
144
- view.setUint16(4, sorted.length, true); // Number of images
145
- // Write image directory entries
146
- let entryOffset = headerSize;
147
- for (const entry of entries) {
148
- const size = entry.size > 255 ? 0 : entry.size; // 0 means 256
149
- view.setUint8(entryOffset + 0, size); // Width
150
- view.setUint8(entryOffset + 1, size); // Height
151
- view.setUint8(entryOffset + 2, 0); // Color palette (0 = no palette)
152
- view.setUint8(entryOffset + 3, 0); // Reserved
153
- view.setUint16(entryOffset + 4, 1, true); // Color planes
154
- view.setUint16(entryOffset + 6, 32, true); // Bits per pixel
155
- view.setUint32(entryOffset + 8, entry.data.byteLength, true); // Image data size
156
- view.setUint32(entryOffset + 12, entry.offset, true); // Image data offset
157
- entryOffset += entrySize;
158
- }
159
- // Write image data
160
- for (const entry of entries) {
161
- uint8.set(new Uint8Array(entry.data), entry.offset);
162
- }
163
- return new Blob([buffer], { type: 'image/x-icon' });
119
+ const sorted = [...pngBuffers].sort((a, b) => b.size - a.size);
120
+ const headerSize = 6;
121
+ const entrySize = 16;
122
+ const directorySize = headerSize + entrySize * sorted.length;
123
+ let currentOffset = directorySize;
124
+ const entries = [];
125
+ for (const png of sorted) {
126
+ entries.push({ size: png.size, offset: currentOffset, data: png.data });
127
+ currentOffset += png.data.byteLength;
128
+ }
129
+ const totalSize = currentOffset;
130
+ const buffer = new ArrayBuffer(totalSize);
131
+ const view = new DataView(buffer);
132
+ const uint8 = new Uint8Array(buffer);
133
+ view.setUint16(0, 0, true);
134
+ view.setUint16(2, 1, true);
135
+ view.setUint16(4, sorted.length, true);
136
+ let entryOffset = headerSize;
137
+ for (const entry of entries) {
138
+ const size = entry.size > 255 ? 0 : entry.size;
139
+ view.setUint8(entryOffset + 0, size);
140
+ view.setUint8(entryOffset + 1, size);
141
+ view.setUint8(entryOffset + 2, 0);
142
+ view.setUint8(entryOffset + 3, 0);
143
+ view.setUint16(entryOffset + 4, 1, true);
144
+ view.setUint16(entryOffset + 6, 32, true);
145
+ view.setUint32(entryOffset + 8, entry.data.byteLength, true);
146
+ view.setUint32(entryOffset + 12, entry.offset, true);
147
+ entryOffset += entrySize;
148
+ }
149
+ for (const entry of entries) {
150
+ uint8.set(new Uint8Array(entry.data), entry.offset);
151
+ }
152
+ return new Blob([buffer], { type: "image/x-icon" });
164
153
  }
165
- /**
166
- * Create a ZIP file containing multiple files
167
- */
168
154
  async function createZipFile(files) {
169
- // Simple ZIP implementation without external libraries
170
- // Uses store (no compression) method for simplicity
171
- const encoder = new TextEncoder();
172
- const fileEntries = [];
173
- let offset = 0;
174
- // Prepare file entries
175
- for (const file of files) {
176
- const nameBytes = encoder.encode(file.name);
177
- let dataBytes;
178
- if (typeof file.data === 'string') {
179
- dataBytes = encoder.encode(file.data);
180
- }
181
- else if (file.data instanceof Blob) {
182
- dataBytes = new Uint8Array(await file.data.arrayBuffer());
183
- }
184
- else {
185
- dataBytes = new Uint8Array(file.data);
186
- }
187
- const crc32 = calculateCRC32(dataBytes);
188
- fileEntries.push({ name: nameBytes, data: dataBytes, crc32, offset });
189
- // Local file header size: 30 + name length + data length
190
- offset += 30 + nameBytes.length + dataBytes.length;
155
+ const encoder = new TextEncoder();
156
+ const fileEntries = [];
157
+ let offset = 0;
158
+ for (const file of files) {
159
+ const nameBytes = encoder.encode(file.name);
160
+ let dataBytes;
161
+ if (typeof file.data === "string") {
162
+ dataBytes = encoder.encode(file.data);
163
+ } else if (file.data instanceof Blob) {
164
+ dataBytes = new Uint8Array(await file.data.arrayBuffer());
165
+ } else {
166
+ dataBytes = new Uint8Array(file.data);
191
167
  }
192
- const centralDirectoryOffset = offset;
193
- // Calculate central directory size
194
- let centralDirSize = 0;
195
- for (const entry of fileEntries) {
196
- centralDirSize += 46 + entry.name.length;
197
- }
198
- // Total size: local headers + data + central directory + end of central directory
199
- const totalSize = offset + centralDirSize + 22;
200
- const buffer = new ArrayBuffer(totalSize);
201
- const view = new DataView(buffer);
202
- const bytes = new Uint8Array(buffer);
203
- let pos = 0;
204
- // Write local file headers and data
205
- for (const entry of fileEntries) {
206
- // Local file header signature
207
- view.setUint32(pos, 0x04034b50, true);
208
- pos += 4;
209
- // Version needed
210
- view.setUint16(pos, 20, true);
211
- pos += 2;
212
- // General purpose bit flag
213
- view.setUint16(pos, 0, true);
214
- pos += 2;
215
- // Compression method (0 = store)
216
- view.setUint16(pos, 0, true);
217
- pos += 2;
218
- // File time & date (use current)
219
- const now = new Date();
220
- const dosTime = ((now.getHours() << 11) | (now.getMinutes() << 5) | (now.getSeconds() >> 1)) & 0xffff;
221
- const dosDate = (((now.getFullYear() - 1980) << 9) | ((now.getMonth() + 1) << 5) | now.getDate()) & 0xffff;
222
- view.setUint16(pos, dosTime, true);
223
- pos += 2;
224
- view.setUint16(pos, dosDate, true);
225
- pos += 2;
226
- // CRC-32
227
- view.setUint32(pos, entry.crc32, true);
228
- pos += 4;
229
- // Compressed size
230
- view.setUint32(pos, entry.data.length, true);
231
- pos += 4;
232
- // Uncompressed size
233
- view.setUint32(pos, entry.data.length, true);
234
- pos += 4;
235
- // File name length
236
- view.setUint16(pos, entry.name.length, true);
237
- pos += 2;
238
- // Extra field length
239
- view.setUint16(pos, 0, true);
240
- pos += 2;
241
- // File name
242
- bytes.set(entry.name, pos);
243
- pos += entry.name.length;
244
- // File data
245
- bytes.set(entry.data, pos);
246
- pos += entry.data.length;
247
- }
248
- // Write central directory
249
- for (const entry of fileEntries) {
250
- // Central directory file header signature
251
- view.setUint32(pos, 0x02014b50, true);
252
- pos += 4;
253
- // Version made by
254
- view.setUint16(pos, 20, true);
255
- pos += 2;
256
- // Version needed
257
- view.setUint16(pos, 20, true);
258
- pos += 2;
259
- // General purpose bit flag
260
- view.setUint16(pos, 0, true);
261
- pos += 2;
262
- // Compression method
263
- view.setUint16(pos, 0, true);
264
- pos += 2;
265
- // File time & date
266
- const now = new Date();
267
- const dosTime = ((now.getHours() << 11) | (now.getMinutes() << 5) | (now.getSeconds() >> 1)) & 0xffff;
268
- const dosDate = (((now.getFullYear() - 1980) << 9) | ((now.getMonth() + 1) << 5) | now.getDate()) & 0xffff;
269
- view.setUint16(pos, dosTime, true);
270
- pos += 2;
271
- view.setUint16(pos, dosDate, true);
272
- pos += 2;
273
- // CRC-32
274
- view.setUint32(pos, entry.crc32, true);
275
- pos += 4;
276
- // Compressed size
277
- view.setUint32(pos, entry.data.length, true);
278
- pos += 4;
279
- // Uncompressed size
280
- view.setUint32(pos, entry.data.length, true);
281
- pos += 4;
282
- // File name length
283
- view.setUint16(pos, entry.name.length, true);
284
- pos += 2;
285
- // Extra field length
286
- view.setUint16(pos, 0, true);
287
- pos += 2;
288
- // File comment length
289
- view.setUint16(pos, 0, true);
290
- pos += 2;
291
- // Disk number start
292
- view.setUint16(pos, 0, true);
293
- pos += 2;
294
- // Internal file attributes
295
- view.setUint16(pos, 0, true);
296
- pos += 2;
297
- // External file attributes
298
- view.setUint32(pos, 0, true);
299
- pos += 4;
300
- // Relative offset of local header
301
- view.setUint32(pos, entry.offset, true);
302
- pos += 4;
303
- // File name
304
- bytes.set(entry.name, pos);
305
- pos += entry.name.length;
306
- }
307
- // End of central directory record
308
- view.setUint32(pos, 0x06054b50, true);
168
+ const crc32 = calculateCRC32(dataBytes);
169
+ fileEntries.push({ name: nameBytes, data: dataBytes, crc32, offset });
170
+ offset += 30 + nameBytes.length + dataBytes.length;
171
+ }
172
+ const centralDirectoryOffset = offset;
173
+ let centralDirSize = 0;
174
+ for (const entry of fileEntries) {
175
+ centralDirSize += 46 + entry.name.length;
176
+ }
177
+ const totalSize = offset + centralDirSize + 22;
178
+ const buffer = new ArrayBuffer(totalSize);
179
+ const view = new DataView(buffer);
180
+ const bytes = new Uint8Array(buffer);
181
+ let pos = 0;
182
+ for (const entry of fileEntries) {
183
+ view.setUint32(pos, 67324752, true);
309
184
  pos += 4;
310
- // Disk number
185
+ view.setUint16(pos, 20, true);
186
+ pos += 2;
311
187
  view.setUint16(pos, 0, true);
312
188
  pos += 2;
313
- // Disk number with CD
314
189
  view.setUint16(pos, 0, true);
315
190
  pos += 2;
316
- // Number of entries on this disk
317
- view.setUint16(pos, fileEntries.length, true);
191
+ const now = /* @__PURE__ */ new Date();
192
+ const dosTime = (now.getHours() << 11 | now.getMinutes() << 5 | now.getSeconds() >> 1) & 65535;
193
+ const dosDate = (now.getFullYear() - 1980 << 9 | now.getMonth() + 1 << 5 | now.getDate()) & 65535;
194
+ view.setUint16(pos, dosTime, true);
318
195
  pos += 2;
319
- // Total number of entries
320
- view.setUint16(pos, fileEntries.length, true);
196
+ view.setUint16(pos, dosDate, true);
321
197
  pos += 2;
322
- // Size of central directory
323
- view.setUint32(pos, centralDirSize, true);
198
+ view.setUint32(pos, entry.crc32, true);
199
+ pos += 4;
200
+ view.setUint32(pos, entry.data.length, true);
324
201
  pos += 4;
325
- // Offset of central directory
326
- view.setUint32(pos, centralDirectoryOffset, true);
202
+ view.setUint32(pos, entry.data.length, true);
327
203
  pos += 4;
328
- // Comment length
204
+ view.setUint16(pos, entry.name.length, true);
205
+ pos += 2;
206
+ view.setUint16(pos, 0, true);
207
+ pos += 2;
208
+ bytes.set(entry.name, pos);
209
+ pos += entry.name.length;
210
+ bytes.set(entry.data, pos);
211
+ pos += entry.data.length;
212
+ }
213
+ for (const entry of fileEntries) {
214
+ view.setUint32(pos, 33639248, true);
215
+ pos += 4;
216
+ view.setUint16(pos, 20, true);
217
+ pos += 2;
218
+ view.setUint16(pos, 20, true);
219
+ pos += 2;
329
220
  view.setUint16(pos, 0, true);
330
- return new Blob([buffer], { type: 'application/zip' });
221
+ pos += 2;
222
+ view.setUint16(pos, 0, true);
223
+ pos += 2;
224
+ const now = /* @__PURE__ */ new Date();
225
+ const dosTime = (now.getHours() << 11 | now.getMinutes() << 5 | now.getSeconds() >> 1) & 65535;
226
+ const dosDate = (now.getFullYear() - 1980 << 9 | now.getMonth() + 1 << 5 | now.getDate()) & 65535;
227
+ view.setUint16(pos, dosTime, true);
228
+ pos += 2;
229
+ view.setUint16(pos, dosDate, true);
230
+ pos += 2;
231
+ view.setUint32(pos, entry.crc32, true);
232
+ pos += 4;
233
+ view.setUint32(pos, entry.data.length, true);
234
+ pos += 4;
235
+ view.setUint32(pos, entry.data.length, true);
236
+ pos += 4;
237
+ view.setUint16(pos, entry.name.length, true);
238
+ pos += 2;
239
+ view.setUint16(pos, 0, true);
240
+ pos += 2;
241
+ view.setUint16(pos, 0, true);
242
+ pos += 2;
243
+ view.setUint16(pos, 0, true);
244
+ pos += 2;
245
+ view.setUint16(pos, 0, true);
246
+ pos += 2;
247
+ view.setUint32(pos, 0, true);
248
+ pos += 4;
249
+ view.setUint32(pos, entry.offset, true);
250
+ pos += 4;
251
+ bytes.set(entry.name, pos);
252
+ pos += entry.name.length;
253
+ }
254
+ view.setUint32(pos, 101010256, true);
255
+ pos += 4;
256
+ view.setUint16(pos, 0, true);
257
+ pos += 2;
258
+ view.setUint16(pos, 0, true);
259
+ pos += 2;
260
+ view.setUint16(pos, fileEntries.length, true);
261
+ pos += 2;
262
+ view.setUint16(pos, fileEntries.length, true);
263
+ pos += 2;
264
+ view.setUint32(pos, centralDirSize, true);
265
+ pos += 4;
266
+ view.setUint32(pos, centralDirectoryOffset, true);
267
+ pos += 4;
268
+ view.setUint16(pos, 0, true);
269
+ return new Blob([buffer], { type: "application/zip" });
331
270
  }
332
- /**
333
- * Calculate CRC-32 for ZIP files
334
- */
335
271
  function calculateCRC32(data) {
336
- let crc = 0xffffffff;
337
- const table = getCRC32Table();
338
- for (let i = 0; i < data.length; i++) {
339
- crc = (crc >>> 8) ^ table[(crc ^ data[i]) & 0xff];
340
- }
341
- return (crc ^ 0xffffffff) >>> 0;
272
+ let crc = 4294967295;
273
+ const table = getCRC32Table();
274
+ for (let i = 0; i < data.length; i++) {
275
+ crc = crc >>> 8 ^ table[(crc ^ data[i]) & 255];
276
+ }
277
+ return (crc ^ 4294967295) >>> 0;
342
278
  }
343
279
  let crc32Table = null;
344
280
  function getCRC32Table() {
345
- if (crc32Table) {
346
- return crc32Table;
347
- }
348
- crc32Table = new Uint32Array(256);
349
- for (let i = 0; i < 256; i++) {
350
- let c = i;
351
- for (let j = 0; j < 8; j++) {
352
- c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
353
- }
354
- crc32Table[i] = c;
355
- }
281
+ if (crc32Table) {
356
282
  return crc32Table;
283
+ }
284
+ crc32Table = new Uint32Array(256);
285
+ for (let i = 0; i < 256; i++) {
286
+ let c = i;
287
+ for (let j = 0; j < 8; j++) {
288
+ c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
289
+ }
290
+ crc32Table[i] = c;
291
+ }
292
+ return crc32Table;
357
293
  }
358
- /* ══════════════════════════════════════════════════════════════════════════════
359
- Hook
360
- ══════════════════════════════════════════════════════════════════════════════ */
361
- /**
362
- * Hook for exporting desktop icons in multiple formats and sizes.
363
- *
364
- * @example
365
- * ```tsx
366
- * const { exportAll, downloadAll, icons, isExporting } = useDesktopIconExporter(MyIcon);
367
- *
368
- * // Export all icons
369
- * await exportAll();
370
- *
371
- * // Download as ZIP
372
- * await downloadAll();
373
- * ```
374
- */
375
294
  function useDesktopIconExporter(icon, initialConfig) {
376
- const [config, setConfig] = React.useState({
377
- ...DEFAULT_CONFIG,
378
- ...initialConfig,
379
- });
380
- const [icons, setIcons] = React.useState([]);
381
- const [isExporting, setIsExporting] = React.useState(false);
382
- const [progress, setProgress] = React.useState(0);
383
- // Calculate total number of icons to export
384
- const totalIcons = React.useMemo(() => {
385
- let count = 0;
386
- const scales = config.includeRetina ? RETINA_VARIANTS.length : 1;
387
- for (const size of config.sizes) {
388
- if (config.includeWindows || config.includeLinux) {
389
- count += scales;
390
- }
391
- if (config.includeMacOS && size >= 128) {
392
- count += scales;
393
- }
394
- }
395
- return count;
396
- }, [config]);
397
- // Export all icons as PNGs
398
- const exportAll = React.useCallback(async () => {
399
- setIsExporting(true);
400
- setProgress(0);
401
- const exported = [];
402
- let completed = 0;
403
- try {
404
- const scales = config.includeRetina ? RETINA_VARIANTS : [RETINA_VARIANTS[0]];
405
- for (const size of config.sizes) {
406
- for (const variant of scales) {
407
- const actualSize = size * variant.scale;
408
- const canvas = await renderIconToCanvas(icon, actualSize, config.fillColor, config.backgroundColor, config.padding);
409
- const dataUrl = canvas.toDataURL('image/png');
410
- const blob = await canvasToBlob(canvas);
411
- // Determine platforms
412
- const platforms = [];
413
- if (config.includeWindows && size <= 256) {
414
- platforms.push('windows');
415
- }
416
- if (config.includeMacOS && size >= 128) {
417
- platforms.push('macos');
418
- }
419
- if (config.includeLinux) {
420
- platforms.push('linux');
421
- }
422
- exported.push({
423
- filename: `${config.name}-${size}x${size}${variant.suffix}.png`,
424
- size,
425
- scale: variant.scale,
426
- platform: platforms.join(','),
427
- dataUrl,
428
- blob,
429
- });
430
- completed++;
431
- setProgress((completed / totalIcons) * 100);
432
- }
433
- }
434
- setIcons(exported);
435
- }
436
- catch (error) {
437
- console.error('Failed to export icons:', error);
295
+ const [config, setConfig] = React.useState({
296
+ ...DEFAULT_CONFIG,
297
+ ...initialConfig
298
+ });
299
+ const [icons, setIcons] = React.useState([]);
300
+ const [isExporting, setIsExporting] = React.useState(false);
301
+ const [progress, setProgress] = React.useState(0);
302
+ const totalIcons = React.useMemo(() => {
303
+ let count = 0;
304
+ const scales = config.includeRetina ? RETINA_VARIANTS.length : 1;
305
+ for (const size of config.sizes) {
306
+ if (config.includeWindows || config.includeLinux) {
307
+ count += scales;
308
+ }
309
+ if (config.includeMacOS && size >= 128) {
310
+ count += scales;
311
+ }
312
+ }
313
+ return count;
314
+ }, [config]);
315
+ const exportAll = React.useCallback(async () => {
316
+ setIsExporting(true);
317
+ setProgress(0);
318
+ const exported = [];
319
+ let completed = 0;
320
+ try {
321
+ const scales = config.includeRetina ? RETINA_VARIANTS : [RETINA_VARIANTS[0]];
322
+ for (const size of config.sizes) {
323
+ for (const variant of scales) {
324
+ const actualSize = size * variant.scale;
325
+ const canvas = await renderIconToCanvas(
326
+ icon,
327
+ actualSize,
328
+ config.fillColor,
329
+ config.backgroundColor,
330
+ config.padding
331
+ );
332
+ const dataUrl = canvas.toDataURL("image/png");
333
+ const blob = await canvasToBlob(canvas);
334
+ const platforms = [];
335
+ if (config.includeWindows && size <= 256) {
336
+ platforms.push("windows");
337
+ }
338
+ if (config.includeMacOS && size >= 128) {
339
+ platforms.push("macos");
340
+ }
341
+ if (config.includeLinux) {
342
+ platforms.push("linux");
343
+ }
344
+ exported.push({
345
+ filename: `${config.name}-${size}x${size}${variant.suffix}.png`,
346
+ size,
347
+ scale: variant.scale,
348
+ platform: platforms.join(","),
349
+ dataUrl,
350
+ blob
351
+ });
352
+ completed++;
353
+ setProgress(completed / totalIcons * 100);
438
354
  }
439
- finally {
440
- setIsExporting(false);
355
+ }
356
+ setIcons(exported);
357
+ } catch (error) {
358
+ console.error("Failed to export icons:", error);
359
+ } finally {
360
+ setIsExporting(false);
361
+ }
362
+ }, [icon, config, totalIcons]);
363
+ const exportWindows = React.useCallback(async () => {
364
+ const windowsSizes = config.sizes.filter((s) => s <= 256);
365
+ const pngBuffers = [];
366
+ for (const size of windowsSizes) {
367
+ const canvas = await renderIconToCanvas(
368
+ icon,
369
+ size,
370
+ config.fillColor,
371
+ config.backgroundColor,
372
+ config.padding
373
+ );
374
+ const blob = await canvasToBlob(canvas);
375
+ const buffer = await blob.arrayBuffer();
376
+ pngBuffers.push({ size, data: buffer });
377
+ }
378
+ const icoBlob = await createIcoFile(pngBuffers);
379
+ return URL.createObjectURL(icoBlob);
380
+ }, [icon, config]);
381
+ const exportMacOS = React.useCallback(async () => {
382
+ const macSizes = config.sizes.filter((s) => s >= 128);
383
+ const files = [];
384
+ for (const size of macSizes) {
385
+ const scales = config.includeRetina ? RETINA_VARIANTS : [RETINA_VARIANTS[0]];
386
+ for (const variant of scales) {
387
+ const actualSize = size * variant.scale;
388
+ const canvas = await renderIconToCanvas(
389
+ icon,
390
+ actualSize,
391
+ config.fillColor,
392
+ config.backgroundColor,
393
+ config.padding
394
+ );
395
+ const blob = await canvasToBlob(canvas);
396
+ files.push({
397
+ name: `${config.name}.iconset/icon_${size}x${size}${variant.suffix}.png`,
398
+ data: blob
399
+ });
400
+ }
401
+ }
402
+ files.push({
403
+ name: "README.txt",
404
+ data: new Blob(
405
+ [
406
+ `macOS Icon Set
407
+ =============
408
+
409
+ To convert to .icns format, use the following command in Terminal:
410
+
411
+ iconutil -c icns ${config.name}.iconset
412
+
413
+ This will create ${config.name}.icns
414
+
415
+ Note: iconutil is only available on macOS.
416
+ `
417
+ ],
418
+ { type: "text/plain" }
419
+ )
420
+ });
421
+ const zipBlob = await createZipFile(files);
422
+ return URL.createObjectURL(zipBlob);
423
+ }, [icon, config]);
424
+ const exportLinux = React.useCallback(async () => {
425
+ const linuxIcons = [];
426
+ for (const { size, dir } of LINUX_HICOLOR_DIRS) {
427
+ if (!config.sizes.includes(size)) {
428
+ continue;
429
+ }
430
+ const canvas = await renderIconToCanvas(
431
+ icon,
432
+ size,
433
+ config.fillColor,
434
+ config.backgroundColor,
435
+ config.padding
436
+ );
437
+ const dataUrl = canvas.toDataURL("image/png");
438
+ const blob = await canvasToBlob(canvas);
439
+ linuxIcons.push({
440
+ filename: `${dir}/${config.name}.png`,
441
+ size,
442
+ scale: 1,
443
+ platform: "linux",
444
+ dataUrl,
445
+ blob
446
+ });
447
+ }
448
+ return linuxIcons;
449
+ }, [icon, config]);
450
+ const downloadSingle = React.useCallback((exportedIcon) => {
451
+ const link = document.createElement("a");
452
+ link.href = exportedIcon.dataUrl;
453
+ link.download = exportedIcon.filename;
454
+ link.click();
455
+ }, []);
456
+ const downloadAll = React.useCallback(async () => {
457
+ setIsExporting(true);
458
+ setProgress(0);
459
+ try {
460
+ const files = [];
461
+ await exportAll();
462
+ for (const exportedIcon of icons) {
463
+ if (exportedIcon.blob) {
464
+ files.push({ name: `png/${exportedIcon.filename}`, data: exportedIcon.blob });
441
465
  }
442
- }, [icon, config, totalIcons]);
443
- // Export Windows ICO file
444
- const exportWindows = React.useCallback(async () => {
466
+ }
467
+ if (config.includeWindows) {
445
468
  const windowsSizes = config.sizes.filter((s) => s <= 256);
446
469
  const pngBuffers = [];
447
470
  for (const size of windowsSizes) {
448
- const canvas = await renderIconToCanvas(icon, size, config.fillColor, config.backgroundColor, config.padding);
449
- const blob = await canvasToBlob(canvas);
450
- const buffer = await blob.arrayBuffer();
451
- pngBuffers.push({ size, data: buffer });
471
+ const canvas = await renderIconToCanvas(
472
+ icon,
473
+ size,
474
+ config.fillColor,
475
+ config.backgroundColor,
476
+ config.padding
477
+ );
478
+ const blob = await canvasToBlob(canvas);
479
+ pngBuffers.push({ size, data: await blob.arrayBuffer() });
452
480
  }
453
481
  const icoBlob = await createIcoFile(pngBuffers);
454
- return URL.createObjectURL(icoBlob);
455
- }, [icon, config]);
456
- // Export macOS icons (as ZIP with PNG set - ICNS requires native tools)
457
- const exportMacOS = React.useCallback(async () => {
482
+ files.push({ name: `windows/${config.name}.ico`, data: icoBlob });
483
+ }
484
+ if (config.includeMacOS) {
458
485
  const macSizes = config.sizes.filter((s) => s >= 128);
459
- const files = [];
486
+ const scales = config.includeRetina ? RETINA_VARIANTS : [RETINA_VARIANTS[0]];
460
487
  for (const size of macSizes) {
461
- const scales = config.includeRetina ? RETINA_VARIANTS : [RETINA_VARIANTS[0]];
462
- for (const variant of scales) {
463
- const actualSize = size * variant.scale;
464
- const canvas = await renderIconToCanvas(icon, actualSize, config.fillColor, config.backgroundColor, config.padding);
465
- const blob = await canvasToBlob(canvas);
466
- // macOS naming convention: icon_128x128@2x.png
467
- files.push({
468
- name: `${config.name}.iconset/icon_${size}x${size}${variant.suffix}.png`,
469
- data: blob,
470
- });
471
- }
488
+ for (const variant of scales) {
489
+ const actualSize = size * variant.scale;
490
+ const canvas = await renderIconToCanvas(
491
+ icon,
492
+ actualSize,
493
+ config.fillColor,
494
+ config.backgroundColor,
495
+ config.padding
496
+ );
497
+ const blob = await canvasToBlob(canvas);
498
+ files.push({
499
+ name: `macos/${config.name}.iconset/icon_${size}x${size}${variant.suffix}.png`,
500
+ data: blob
501
+ });
502
+ }
472
503
  }
473
- // Add README with iconutil instructions
474
504
  files.push({
475
- name: 'README.txt',
476
- data: new Blob([
477
- `macOS Icon Set
478
- =============
479
-
480
- To convert to .icns format, use the following command in Terminal:
481
-
482
- iconutil -c icns ${config.name}.iconset
483
-
484
- This will create ${config.name}.icns
485
-
486
- Note: iconutil is only available on macOS.
487
- `,
488
- ], { type: 'text/plain' }),
505
+ name: "macos/README.txt",
506
+ data: `To convert to .icns: iconutil -c icns ${config.name}.iconset`
489
507
  });
490
- const zipBlob = await createZipFile(files);
491
- return URL.createObjectURL(zipBlob);
492
- }, [icon, config]);
493
- // Export Linux PNG set
494
- const exportLinux = React.useCallback(async () => {
495
- const linuxIcons = [];
508
+ }
509
+ if (config.includeLinux) {
496
510
  for (const { size, dir } of LINUX_HICOLOR_DIRS) {
497
- if (!config.sizes.includes(size)) {
498
- continue;
499
- }
500
- const canvas = await renderIconToCanvas(icon, size, config.fillColor, config.backgroundColor, config.padding);
501
- const dataUrl = canvas.toDataURL('image/png');
502
- const blob = await canvasToBlob(canvas);
503
- linuxIcons.push({
504
- filename: `${dir}/${config.name}.png`,
505
- size,
506
- scale: 1,
507
- platform: 'linux',
508
- dataUrl,
509
- blob,
510
- });
511
+ if (!config.sizes.includes(size)) {
512
+ continue;
513
+ }
514
+ const canvas = await renderIconToCanvas(
515
+ icon,
516
+ size,
517
+ config.fillColor,
518
+ config.backgroundColor,
519
+ config.padding
520
+ );
521
+ const blob = await canvasToBlob(canvas);
522
+ files.push({ name: `linux/${dir}/${config.name}.png`, data: blob });
511
523
  }
512
- return linuxIcons;
513
- }, [icon, config]);
514
- // Download single icon
515
- const downloadSingle = React.useCallback((exportedIcon) => {
516
- const link = document.createElement('a');
517
- link.href = exportedIcon.dataUrl;
518
- link.download = exportedIcon.filename;
519
- link.click();
520
- }, []);
521
- // Download all as ZIP
522
- const downloadAll = React.useCallback(async () => {
523
- setIsExporting(true);
524
- setProgress(0);
525
- try {
526
- const files = [];
527
- // Export all PNG icons first
528
- await exportAll();
529
- // Add PNG icons
530
- for (const exportedIcon of icons) {
531
- if (exportedIcon.blob) {
532
- files.push({ name: `png/${exportedIcon.filename}`, data: exportedIcon.blob });
533
- }
524
+ }
525
+ const zipBlob = await createZipFile(files);
526
+ const url = URL.createObjectURL(zipBlob);
527
+ const link = document.createElement("a");
528
+ link.href = url;
529
+ link.download = `${config.name}-icons.zip`;
530
+ link.click();
531
+ URL.revokeObjectURL(url);
532
+ } catch (error) {
533
+ console.error("Failed to download icons:", error);
534
+ } finally {
535
+ setIsExporting(false);
536
+ }
537
+ }, [icon, icons, config, exportAll]);
538
+ return {
539
+ config,
540
+ setConfig,
541
+ icons,
542
+ isExporting,
543
+ progress,
544
+ exportAll,
545
+ exportWindows,
546
+ exportMacOS,
547
+ exportLinux,
548
+ downloadAll,
549
+ downloadSingle
550
+ };
551
+ }
552
+ const NiceDesktopIconExporter = ({
553
+ icon,
554
+ initialConfig,
555
+ onExport,
556
+ className = ""
557
+ }) => {
558
+ const { t } = uiCore.useNiceTranslation();
559
+ const {
560
+ config,
561
+ setConfig,
562
+ icons,
563
+ isExporting,
564
+ progress,
565
+ exportAll,
566
+ downloadAll,
567
+ downloadSingle
568
+ } = useDesktopIconExporter(icon, initialConfig);
569
+ const handleExport = async () => {
570
+ await exportAll();
571
+ onExport?.(icons);
572
+ };
573
+ const IconComponent = icon;
574
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `nice-desktop-icon-exporter ${className}`, children: [
575
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nice-desktop-icon-exporter__preview", children: [
576
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: t("desktopIconExporter.preview", "Preview") }),
577
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nice-desktop-icon-exporter__preview-grid", children: [16, 32, 48, 64, 128, 256].map((size) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nice-desktop-icon-exporter__preview-item", children: [
578
+ /* @__PURE__ */ jsxRuntime.jsx(
579
+ "div",
580
+ {
581
+ className: "nice-desktop-icon-exporter__preview-box",
582
+ style: {
583
+ width: Math.min(size, 128),
584
+ height: Math.min(size, 128),
585
+ backgroundColor: config.backgroundColor || "#f0f0f0"
586
+ },
587
+ children: /* @__PURE__ */ jsxRuntime.jsx(
588
+ IconComponent,
589
+ {
590
+ size: Math.min(size, 128) * (1 - config.padding * 2),
591
+ fill: config.fillColor
592
+ }
593
+ )
594
+ }
595
+ ),
596
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
597
+ size,
598
+ "×",
599
+ size
600
+ ] })
601
+ ] }, size)) })
602
+ ] }),
603
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nice-desktop-icon-exporter__config", children: [
604
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: t("desktopIconExporter.configuration", "Configuration") }),
605
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
606
+ t("desktopIconExporter.iconName", "Icon Name:"),
607
+ /* @__PURE__ */ jsxRuntime.jsx(
608
+ "input",
609
+ {
610
+ type: "text",
611
+ value: config.name,
612
+ onChange: (e) => setConfig((c) => ({ ...c, name: e.target.value }))
613
+ }
614
+ )
615
+ ] }),
616
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
617
+ t("desktopIconExporter.fillColor", "Fill Color:"),
618
+ /* @__PURE__ */ jsxRuntime.jsx(
619
+ "input",
620
+ {
621
+ type: "color",
622
+ value: config.fillColor,
623
+ onChange: (e) => setConfig((c) => ({ ...c, fillColor: e.target.value }))
624
+ }
625
+ )
626
+ ] }),
627
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
628
+ t("desktopIconExporter.background", "Background:"),
629
+ /* @__PURE__ */ jsxRuntime.jsx(
630
+ "input",
631
+ {
632
+ type: "color",
633
+ value: config.backgroundColor || "#ffffff",
634
+ onChange: (e) => setConfig((c) => ({ ...c, backgroundColor: e.target.value }))
635
+ }
636
+ ),
637
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setConfig((c) => ({ ...c, backgroundColor: void 0 })), children: t("desktopIconExporter.transparent", "Transparent") })
638
+ ] }),
639
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
640
+ t("desktopIconExporter.padding", "Padding: {padding}%").replace(
641
+ "{padding}",
642
+ String(Math.round(config.padding * 100))
643
+ ),
644
+ /* @__PURE__ */ jsxRuntime.jsx(
645
+ "input",
646
+ {
647
+ type: "range",
648
+ min: "0",
649
+ max: "0.3",
650
+ step: "0.01",
651
+ value: config.padding,
652
+ onChange: (e) => setConfig((c) => ({ ...c, padding: parseFloat(e.target.value) }))
653
+ }
654
+ )
655
+ ] }),
656
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nice-desktop-icon-exporter__platforms", children: [
657
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
658
+ /* @__PURE__ */ jsxRuntime.jsx(
659
+ "input",
660
+ {
661
+ type: "checkbox",
662
+ checked: config.includeWindows,
663
+ onChange: (e) => setConfig((c) => ({ ...c, includeWindows: e.target.checked }))
534
664
  }
535
- // Add Windows ICO
536
- if (config.includeWindows) {
537
- const windowsSizes = config.sizes.filter((s) => s <= 256);
538
- const pngBuffers = [];
539
- for (const size of windowsSizes) {
540
- const canvas = await renderIconToCanvas(icon, size, config.fillColor, config.backgroundColor, config.padding);
541
- const blob = await canvasToBlob(canvas);
542
- pngBuffers.push({ size, data: await blob.arrayBuffer() });
543
- }
544
- const icoBlob = await createIcoFile(pngBuffers);
545
- files.push({ name: `windows/${config.name}.ico`, data: icoBlob });
665
+ ),
666
+ t("desktopIconExporter.windowsIco", "Windows (.ico)")
667
+ ] }),
668
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
669
+ /* @__PURE__ */ jsxRuntime.jsx(
670
+ "input",
671
+ {
672
+ type: "checkbox",
673
+ checked: config.includeMacOS,
674
+ onChange: (e) => setConfig((c) => ({ ...c, includeMacOS: e.target.checked }))
546
675
  }
547
- // Add macOS iconset
548
- if (config.includeMacOS) {
549
- const macSizes = config.sizes.filter((s) => s >= 128);
550
- const scales = config.includeRetina ? RETINA_VARIANTS : [RETINA_VARIANTS[0]];
551
- for (const size of macSizes) {
552
- for (const variant of scales) {
553
- const actualSize = size * variant.scale;
554
- const canvas = await renderIconToCanvas(icon, actualSize, config.fillColor, config.backgroundColor, config.padding);
555
- const blob = await canvasToBlob(canvas);
556
- files.push({
557
- name: `macos/${config.name}.iconset/icon_${size}x${size}${variant.suffix}.png`,
558
- data: blob,
559
- });
560
- }
561
- }
562
- files.push({
563
- name: 'macos/README.txt',
564
- data: `To convert to .icns: iconutil -c icns ${config.name}.iconset`,
565
- });
676
+ ),
677
+ t("desktopIconExporter.macosIconset", "macOS (.iconset)")
678
+ ] }),
679
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
680
+ /* @__PURE__ */ jsxRuntime.jsx(
681
+ "input",
682
+ {
683
+ type: "checkbox",
684
+ checked: config.includeLinux,
685
+ onChange: (e) => setConfig((c) => ({ ...c, includeLinux: e.target.checked }))
566
686
  }
567
- // Add Linux icons
568
- if (config.includeLinux) {
569
- for (const { size, dir } of LINUX_HICOLOR_DIRS) {
570
- if (!config.sizes.includes(size)) {
571
- continue;
572
- }
573
- const canvas = await renderIconToCanvas(icon, size, config.fillColor, config.backgroundColor, config.padding);
574
- const blob = await canvasToBlob(canvas);
575
- files.push({ name: `linux/${dir}/${config.name}.png`, data: blob });
576
- }
687
+ ),
688
+ t("desktopIconExporter.linuxHicolor", "Linux (hicolor)")
689
+ ] }),
690
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
691
+ /* @__PURE__ */ jsxRuntime.jsx(
692
+ "input",
693
+ {
694
+ type: "checkbox",
695
+ checked: config.includeRetina,
696
+ onChange: (e) => setConfig((c) => ({ ...c, includeRetina: e.target.checked }))
577
697
  }
578
- // Create and download ZIP
579
- const zipBlob = await createZipFile(files);
580
- const url = URL.createObjectURL(zipBlob);
581
- const link = document.createElement('a');
582
- link.href = url;
583
- link.download = `${config.name}-icons.zip`;
584
- link.click();
585
- URL.revokeObjectURL(url);
586
- }
587
- catch (error) {
588
- console.error('Failed to download icons:', error);
589
- }
590
- finally {
591
- setIsExporting(false);
592
- }
593
- }, [icon, icons, config, exportAll]);
594
- return {
595
- config,
596
- setConfig,
597
- icons,
598
- isExporting,
599
- progress,
600
- exportAll,
601
- exportWindows,
602
- exportMacOS,
603
- exportLinux,
604
- downloadAll,
605
- downloadSingle,
606
- };
607
- }
608
- /* ══════════════════════════════════════════════════════════════════════════════
609
- Component
610
- ══════════════════════════════════════════════════════════════════════════════ */
611
- /**
612
- * Desktop icon exporter component with UI for configuring and downloading icons.
613
- *
614
- * @example
615
- * ```tsx
616
- * import { NiceDesktopIconExporter } from '@nice2dev/icons';
617
- * import { NtdRocket } from '@nice2dev/icons/paths';
618
- *
619
- * function App() {
620
- * return (
621
- * <NiceDesktopIconExporter
622
- * icon={NtdRocket}
623
- * initialConfig={{ name: 'my-app', fillColor: '#ff6b35' }}
624
- * />
625
- * );
626
- * }
627
- * ```
628
- */
629
- const NiceDesktopIconExporter = ({ icon, initialConfig, onExport, className = '', }) => {
630
- const { t } = uiCore.useNiceTranslation();
631
- const { config, setConfig, icons, isExporting, progress, exportAll, downloadAll, downloadSingle, } = useDesktopIconExporter(icon, initialConfig);
632
- const handleExport = async () => {
633
- await exportAll();
634
- onExport === null || onExport === void 0 ? void 0 : onExport(icons);
635
- };
636
- const IconComponent = icon;
637
- return (jsxRuntime.jsxs("div", { className: `nice-desktop-icon-exporter ${className}`, children: [jsxRuntime.jsxs("div", { className: "nice-desktop-icon-exporter__preview", children: [jsxRuntime.jsx("h3", { children: t('desktopIconExporter.preview', 'Preview') }), jsxRuntime.jsx("div", { className: "nice-desktop-icon-exporter__preview-grid", children: [16, 32, 48, 64, 128, 256].map((size) => (jsxRuntime.jsxs("div", { className: "nice-desktop-icon-exporter__preview-item", children: [jsxRuntime.jsx("div", { className: "nice-desktop-icon-exporter__preview-box", style: {
638
- width: Math.min(size, 128),
639
- height: Math.min(size, 128),
640
- backgroundColor: config.backgroundColor || '#f0f0f0',
641
- }, children: jsxRuntime.jsx(IconComponent, { size: Math.min(size, 128) * (1 - config.padding * 2), fill: config.fillColor }) }), jsxRuntime.jsxs("span", { children: [size, "\u00D7", size] })] }, size))) })] }), jsxRuntime.jsxs("div", { className: "nice-desktop-icon-exporter__config", children: [jsxRuntime.jsx("h3", { children: t('desktopIconExporter.configuration', 'Configuration') }), jsxRuntime.jsxs("label", { children: [t('desktopIconExporter.iconName', 'Icon Name:'), jsxRuntime.jsx("input", { type: "text", value: config.name, onChange: (e) => setConfig((c) => ({ ...c, name: e.target.value })) })] }), jsxRuntime.jsxs("label", { children: [t('desktopIconExporter.fillColor', 'Fill Color:'), jsxRuntime.jsx("input", { type: "color", value: config.fillColor, onChange: (e) => setConfig((c) => ({ ...c, fillColor: e.target.value })) })] }), jsxRuntime.jsxs("label", { children: [t('desktopIconExporter.background', 'Background:'), jsxRuntime.jsx("input", { type: "color", value: config.backgroundColor || '#ffffff', onChange: (e) => setConfig((c) => ({ ...c, backgroundColor: e.target.value })) }), jsxRuntime.jsx("button", { onClick: () => setConfig((c) => ({ ...c, backgroundColor: undefined })), children: t('desktopIconExporter.transparent', 'Transparent') })] }), jsxRuntime.jsxs("label", { children: [t('desktopIconExporter.padding', 'Padding: {padding}%').replace('{padding}', String(Math.round(config.padding * 100))), jsxRuntime.jsx("input", { type: "range", min: "0", max: "0.3", step: "0.01", value: config.padding, onChange: (e) => setConfig((c) => ({ ...c, padding: parseFloat(e.target.value) })) })] }), jsxRuntime.jsxs("div", { className: "nice-desktop-icon-exporter__platforms", children: [jsxRuntime.jsxs("label", { children: [jsxRuntime.jsx("input", { type: "checkbox", checked: config.includeWindows, onChange: (e) => setConfig((c) => ({ ...c, includeWindows: e.target.checked })) }), t('desktopIconExporter.windowsIco', 'Windows (.ico)')] }), jsxRuntime.jsxs("label", { children: [jsxRuntime.jsx("input", { type: "checkbox", checked: config.includeMacOS, onChange: (e) => setConfig((c) => ({ ...c, includeMacOS: e.target.checked })) }), t('desktopIconExporter.macosIconset', 'macOS (.iconset)')] }), jsxRuntime.jsxs("label", { children: [jsxRuntime.jsx("input", { type: "checkbox", checked: config.includeLinux, onChange: (e) => setConfig((c) => ({ ...c, includeLinux: e.target.checked })) }), t('desktopIconExporter.linuxHicolor', 'Linux (hicolor)')] }), jsxRuntime.jsxs("label", { children: [jsxRuntime.jsx("input", { type: "checkbox", checked: config.includeRetina, onChange: (e) => setConfig((c) => ({ ...c, includeRetina: e.target.checked })) }), t('desktopIconExporter.hidpiRetina2x3x', 'HiDPI/Retina (@2x, @3x)')] })] }), jsxRuntime.jsxs("div", { className: "nice-desktop-icon-exporter__sizes", children: [jsxRuntime.jsx("h4", { children: t('desktopIconExporter.sizes', 'Sizes:') }), DESKTOP_ICON_SIZES.map(({ size, label }) => (jsxRuntime.jsxs("label", { children: [jsxRuntime.jsx("input", { type: "checkbox", checked: config.sizes.includes(size), onChange: (e) => {
642
- setConfig((c) => ({
643
- ...c,
644
- sizes: e.target.checked
645
- ? [...c.sizes, size].sort((a, b) => a - b)
646
- : c.sizes.filter((s) => s !== size),
647
- }));
648
- } }), label] }, size)))] })] }), jsxRuntime.jsxs("div", { className: "nice-desktop-icon-exporter__actions", children: [jsxRuntime.jsx("button", { onClick: handleExport, disabled: isExporting, children: isExporting
649
- ? t('desktopIconExporter.exporting', 'Exporting... {progress}%').replace('{progress}', String(Math.round(progress)))
650
- : t('desktopIconExporter.generateIcons', 'Generate Icons') }), jsxRuntime.jsx("button", { onClick: downloadAll, disabled: isExporting || icons.length === 0, children: t('desktopIconExporter.downloadAllZip', 'Download All (ZIP)') })] }), icons.length > 0 && (jsxRuntime.jsxs("div", { className: "nice-desktop-icon-exporter__results", children: [jsxRuntime.jsx("h3", { children: t('desktopIconExporter.generatedIcons', 'Generated Icons ({icons})').replace('{icons}', String(icons.length)) }), jsxRuntime.jsx("div", { className: "nice-desktop-icon-exporter__results-grid", children: icons.map((exportedIcon) => (jsxRuntime.jsxs("div", { className: "nice-desktop-icon-exporter__result-item", children: [jsxRuntime.jsx("img", { src: exportedIcon.dataUrl, alt: exportedIcon.filename }), jsxRuntime.jsx("span", { children: exportedIcon.filename }), jsxRuntime.jsx("button", { onClick: () => downloadSingle(exportedIcon), children: t('desktopIconExporter.download', 'Download') })] }, exportedIcon.filename))) })] }))] }));
698
+ ),
699
+ t("desktopIconExporter.hidpiRetina2x3x", "HiDPI/Retina (@2x, @3x)")
700
+ ] })
701
+ ] }),
702
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nice-desktop-icon-exporter__sizes", children: [
703
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { children: t("desktopIconExporter.sizes", "Sizes:") }),
704
+ DESKTOP_ICON_SIZES.map(({ size, label }) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
705
+ /* @__PURE__ */ jsxRuntime.jsx(
706
+ "input",
707
+ {
708
+ type: "checkbox",
709
+ checked: config.sizes.includes(size),
710
+ onChange: (e) => {
711
+ setConfig((c) => ({
712
+ ...c,
713
+ sizes: e.target.checked ? [...c.sizes, size].sort((a, b) => a - b) : c.sizes.filter((s) => s !== size)
714
+ }));
715
+ }
716
+ }
717
+ ),
718
+ label
719
+ ] }, size))
720
+ ] })
721
+ ] }),
722
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nice-desktop-icon-exporter__actions", children: [
723
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handleExport, disabled: isExporting, children: isExporting ? t("desktopIconExporter.exporting", "Exporting... {progress}%").replace(
724
+ "{progress}",
725
+ String(Math.round(progress))
726
+ ) : t("desktopIconExporter.generateIcons", "Generate Icons") }),
727
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: downloadAll, disabled: isExporting || icons.length === 0, children: t("desktopIconExporter.downloadAllZip", "Download All (ZIP)") })
728
+ ] }),
729
+ icons.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nice-desktop-icon-exporter__results", children: [
730
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: t("desktopIconExporter.generatedIcons", "Generated Icons ({icons})").replace(
731
+ "{icons}",
732
+ String(icons.length)
733
+ ) }),
734
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nice-desktop-icon-exporter__results-grid", children: icons.map((exportedIcon) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nice-desktop-icon-exporter__result-item", children: [
735
+ /* @__PURE__ */ jsxRuntime.jsx("img", { src: exportedIcon.dataUrl, alt: exportedIcon.filename }),
736
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: exportedIcon.filename }),
737
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => downloadSingle(exportedIcon), children: t("desktopIconExporter.download", "Download") })
738
+ ] }, exportedIcon.filename)) })
739
+ ] })
740
+ ] });
651
741
  };
652
-
653
742
  exports.DESKTOP_ICON_SIZES = DESKTOP_ICON_SIZES;
654
743
  exports.LINUX_HICOLOR_DIRS = LINUX_HICOLOR_DIRS;
655
744
  exports.NiceDesktopIconExporter = NiceDesktopIconExporter;