@nice2dev/icons 1.3.5 → 1.3.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +24 -4
  3. package/dist/cjs/NiceIconStacker.js +124 -58
  4. package/dist/cjs/client.js +5 -1
  5. package/dist/cjs/createIcon.js +2 -2
  6. package/dist/cjs/iconStack.js +113 -0
  7. package/dist/cjs/icons/actions.js +11 -4
  8. package/dist/cjs/icons/business.js +27 -17
  9. package/dist/cjs/icons/ecommerce.js +14 -4
  10. package/dist/cjs/icons/education.js +18 -11
  11. package/dist/cjs/icons/files.js +20 -12
  12. package/dist/cjs/icons/health.js +31 -15
  13. package/dist/cjs/icons/travel.js +4 -4
  14. package/dist/cjs/icons/ui.js +7 -6
  15. package/dist/cjs/icons/weather.js +4 -3
  16. package/dist/cjs/index.js +5 -1
  17. package/dist/cjs/nicetodev-icons.css +1 -1
  18. package/dist/cjs/rsc.js +1 -1
  19. package/dist/cjs/stacking.js +39 -11
  20. package/dist/esm/NiceIconStacker.js +125 -59
  21. package/dist/esm/client.js +2 -1
  22. package/dist/esm/createIcon.js +3 -3
  23. package/dist/esm/iconParts.js +1 -1
  24. package/dist/esm/iconStack.js +106 -0
  25. package/dist/esm/icons/actions.js +11 -4
  26. package/dist/esm/icons/business.js +27 -17
  27. package/dist/esm/icons/ecommerce.js +14 -4
  28. package/dist/esm/icons/education.js +18 -11
  29. package/dist/esm/icons/files.js +22 -14
  30. package/dist/esm/icons/health.js +31 -15
  31. package/dist/esm/icons/media.js +1 -1
  32. package/dist/esm/icons/travel.js +4 -4
  33. package/dist/esm/icons/ui.js +7 -6
  34. package/dist/esm/icons/weather.js +5 -4
  35. package/dist/esm/index.js +2 -1
  36. package/dist/esm/nicetodev-icons.css +1 -1
  37. package/dist/esm/rsc.js +1 -1
  38. package/dist/esm/smilAnimations.js +1 -1
  39. package/dist/esm/stacking.js +38 -12
  40. package/dist/types/NiceIconStacker.d.ts +18 -28
  41. package/dist/types/client.d.ts +4 -2
  42. package/dist/types/iconStack.d.ts +83 -0
  43. package/dist/types/index.d.ts +4 -2
  44. package/dist/types/stacking.d.ts +16 -3
  45. package/dist/types/types.d.ts +24 -5
  46. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,83 @@
1
1
  # @nice2dev/icons — Changelog
2
2
 
3
+ ## 1.3.7
4
+
5
+ ### Patch Changes
6
+
7
+ - f527e94: Icon fixes from showcase feedback round 37:
8
+
9
+ - **NtdHamburger:** the wavy sauce is gone, leaving three even layers. Duotone uses fills only.
10
+ - **NtdBook / NtdBookBookmark:** the front cover is 20% lower and carries a line of title.
11
+ - **NtdMountain:** solid is all primary, with the snow line cut in the accent colour.
12
+ - **NtdBriefcase:**
13
+ - the band and clasp sit higher;
14
+ - the outline's band ends flush with the sides and follows `strokeWidth`;
15
+ - vivid's clasp is all accent.
16
+ - **NtdPercent:** the rings are 20% bigger and the slash is twice the stroke width.
17
+ - **NtdBus:** the wheels are lower.
18
+ - **NtdMonitor:** the stand no longer reaches over the screen.
19
+ - **NtdPieChart:** turned 45° clockwise.
20
+ - **NtdPromoTag:** gains a percent sign turned 45° clockwise.
21
+
22
+ - d4d6211: Icon fixes from showcase feedback round 38:
23
+
24
+ - **NtdBook:** the title sits higher and the block of pages is 50% thicker. NtdBookBookmark keeps
25
+ its title below the ribbon.
26
+ - **NtdAdmin:** the tie is twice as wide.
27
+ - **NtdTag:** the hole sits closer to the point.
28
+ - **NtdTrash:** the lid and the handle are smaller. Vivid uses the theme colours instead of a fixed
29
+ blue.
30
+ - **NtdDna:** redrawn as a double helix that stays inside the box, with eight thin base pairs
31
+ running strand to strand.
32
+
33
+ - b321834: Stacked icons can be moved precisely:
34
+
35
+ - **`stackOffsetX` / `stackOffsetY`:** move a stacked icon from where `stackAlign` puts it. The
36
+ value is a percent of the parent icon's width or height, so the move scales with the icon:
37
+ positive X is right, positive Y is down. A turn (`stackRotation`) follows the moved icon's
38
+ centre.
39
+ - **NiceIconStacker:** "Shift right" and "Shift down" sliders (−50…50%) under "More", written to
40
+ the code. The layers keep them as `offsetX` / `offsetY`, which `NiceIconStack` draws.
41
+ - **Translations:** new keys `iconStacker.offsetX` and `iconStacker.offsetY`.
42
+
43
+ - 9f58a4e: The default `strokeWidth` of the icons is now 1 (it was 2, and 1.5 in the server-component
44
+ icons), so the icons draw thinner lines by default. Glyph strokes on filled variants follow it at
45
+ twice the width. Pass `strokeWidth={2}` to keep the old weight.
46
+
47
+ ## 1.3.6
48
+
49
+ ### Patch Changes
50
+
51
+ - 4bdd01c: NiceIconStacker builds stacks nested any number of levels deep:
52
+
53
+ - **Nesting:** a layer sits in its `parent` (a new optional field). A layer without one sits in
54
+ the base, as in 1.3.5.
55
+ - **Where icons go:** "Add" puts the current icon into the selected layer and then selects it, so
56
+ adding again nests one level deeper (`<NtdBitcoin><NtdStar><NtdSparkle>1</NtdSparkle></NtdStar></NtdBitcoin>`).
57
+ Select an outer layer to put icons side by side.
58
+ - **Display:**
59
+ - layers are indented by depth;
60
+ - text is the innermost level, offered under the deepest icons;
61
+ - the colours flip at every level.
62
+ - **Removing:** removing a layer moves what sat in it up into its parent.
63
+ - **Handle:** a ref handle (`NiceIconStackerHandle`: `add`, `clear`) lets a parent run "add" from a
64
+ button of its own.
65
+ - **Translations:** new keys `iconStacker.addInto` and `iconStacker.target`.
66
+ - **Smaller text:** the default size of stacked text is three quarters of the 1.3.5 size (9, 7.5,
67
+ 6, 5 by length).
68
+ - **`stackRotation`:** turns a stacked icon about its own centre.
69
+ - **NiceIconStacker:** every layer can turn and take its own primary, secondary and accent colours
70
+ (with "auto" to follow its parent), and its text a size and colour of its own.
71
+ - **Bound text:**
72
+ - in the stacker, text can be bound to a value ("Bind to"): the code writes `{count}`, and the
73
+ text stays a sample;
74
+ - the new `NiceIconStack` draws a stack kept as data with live values (`values={{ count }}`).
75
+ - **`renderIconStack`** draws a stack as one icon tree.
76
+ - **`half-` positions:** `stackAlign` takes every position again with `half-` (`half-top`,
77
+ `half-top-right`, `half-bottom`, …). The icon sits halfway between the centre and that edge or
78
+ corner, inside the parent, so it is cut out like centred content (only full corners are
79
+ badges).
80
+
3
81
  ## 1.3.5
4
82
 
5
83
  ### Patch Changes
package/README.md CHANGED
@@ -105,7 +105,7 @@ Every icon component accepts these props (extends `SVGProps<SVGSVGElement>`):
105
105
  | `primaryOpacity` | `number` | `1` | Opacity of primary elements (0–1) |
106
106
  | `secondaryOpacity` | `number` | `0.4` | Opacity of secondary elements (0–1) |
107
107
  | `accentOpacity` | `number` | `1` | Opacity of accent elements (0–1) |
108
- | `strokeWidth` | `number` | `2` | Base stroke width for outline/duotone |
108
+ | `strokeWidth` | `number` | `1` | Base stroke width for outline/duotone |
109
109
  | `strokeLinecap` | `'round' \| 'square' \| 'butt'` | `'round'` | Stroke line cap style |
110
110
  | `strokeLinejoin` | `'round' \| 'miter' \| 'bevel'` | `'round'` | Stroke line join style |
111
111
  | `animation` | `IconAnimation` | `undefined` | CSS animation name |
@@ -220,19 +220,39 @@ centred on the icon:
220
220
  </NtdFolder>
221
221
 
222
222
  <NtdCircle>42</NtdCircle>
223
+
224
+ <NtdBitcoin>
225
+ <NtdStar>
226
+ <NtdSparkle>1</NtdSparkle>
227
+ </NtdStar>
228
+ </NtdBitcoin>
223
229
  ```
224
230
 
231
+ Stacks nest any number of levels deep, with the colours flipping at every level.
232
+
225
233
  - **`stackAlign`:** `center` (default), `top`, `bottom`, `left`, `right`, `top-left`,
226
- `top-right`, `bottom-left`, `bottom-right`.
234
+ `top-right`, `bottom-left`, `bottom-right`, plus each of them with `half-` (`half-top`,
235
+ `half-bottom-right`, …), halfway from the centre to that edge or corner.
227
236
  - **`stackSize`:** the child's share of its parent. Default `0.6` centred, `0.5` elsewhere.
228
237
  - **Colours** follow the parent unless the child sets its own:
229
238
  - In the centre or at an edge, a filled parent cuts the content out. The child takes the
230
239
  parent's accent and its text the parent's primary. On `outline` / `wireframe` everything stays
231
240
  in primary.
232
241
  - A corner badge keeps the parent's colours and sits on an accent-coloured gap.
233
- - **Text:** `textColor` and `textSize` override the text's colour and size.
242
+ - **`stackRotation`:** turns a stacked icon about its own centre, in degrees clockwise. Its
243
+ contents and text turn with it.
244
+ - **`stackOffsetX` / `stackOffsetY`:** move a stacked icon from where `stackAlign` puts it, in
245
+ percent of the parent's width or height (positive is right or down). Being relative, the nudge
246
+ scales with the icon: `<NtdStar stackAlign="top-right" stackOffsetX={-5} stackOffsetY={5} />`.
247
+ - **Text:** the default size is 9, 7.5, 6 or 5 in the 24-unit box (by length). `textColor` and
248
+ `textSize` override the text's colour and size.
249
+ - **Data:** `NiceIconStack` draws a stack kept as data (NiceIconStacker's layers), its bound text
250
+ filled from `values`. This works with live numbers, for example
251
+ `<NiceIconStack layers={stack} icons={icons} values={{ count }} />`.
234
252
  - **Builder:** `NiceIconStacker` builds a stack interactively and writes the JSX
235
- (`niceIconStackCode`).
253
+ (`niceIconStackCode`). Each icon you add goes into the selected layer, which is the newest one,
254
+ so adding nests deeper. Select an outer layer to put icons side by side. Text is the innermost
255
+ level.
236
256
 
237
257
  ---
238
258
 
@@ -4,6 +4,8 @@ var jsxRuntime = require('react/jsx-runtime');
4
4
  var uiCore = require('@nice2dev/ui-core');
5
5
  var React = require('react');
6
6
  var actions = require('./icons/actions.js');
7
+ var navigation = require('./icons/navigation.js');
8
+ var iconStack = require('./iconStack.js');
7
9
  var stacking = require('./stacking.js');
8
10
 
9
11
  const ALIGNS = [
@@ -16,46 +18,84 @@ const ALIGNS = [
16
18
  'top-right',
17
19
  'bottom-left',
18
20
  'bottom-right',
21
+ 'half-top',
22
+ 'half-bottom',
23
+ 'half-left',
24
+ 'half-right',
25
+ 'half-top-left',
26
+ 'half-top-right',
27
+ 'half-bottom-left',
28
+ 'half-bottom-right',
19
29
  ];
20
30
  const PREVIEW_SIZES = [96, 48, 24];
21
31
  const NO_LAYERS = [];
22
- /** The stack as JSX, the base wrapping its layers. */
23
- function niceIconStackCode(layers) {
24
- const [base, ...rest] = layers;
32
+ /** What a colour field shows while the colour is unset (follows the parent). */
33
+ const AUTO_SWATCH = '#808080';
34
+ /** The layers in tree order (each followed by what sits in it), with their depth. */
35
+ function walk(layers) {
36
+ const out = [];
37
+ const visit = (layer, depth) => {
38
+ out.push({ layer, depth });
39
+ for (const child of iconStack.stackChildrenOf(layers, layer.id)) {
40
+ visit(child, depth + 1);
41
+ }
42
+ };
43
+ if (layers[0]) {
44
+ visit(layers[0], 0);
45
+ }
46
+ return out;
47
+ }
48
+ /** Removes a layer; what sat in it moves up into its parent (a removed base hands over to its first child). */
49
+ function removeLayer(layers, id) {
50
+ const base = layers[0];
25
51
  if (!base) {
26
- return '';
52
+ return [];
27
53
  }
28
- const baseAttrs = base.variant ? ` variant="${base.variant}"` : '';
29
- if (rest.length === 0) {
30
- return base.text
31
- ? `<${base.icon}${baseAttrs}>${base.text}</${base.icon}>`
32
- : `<${base.icon}${baseAttrs} />`;
54
+ if (id !== base.id) {
55
+ const gone = layers.find((l) => l.id === id);
56
+ const up = gone ? iconStack.stackParentId(layers, gone) : undefined;
57
+ return layers
58
+ .filter((l) => l.id !== id)
59
+ .map((l) => (l.parent === id ? { ...l, parent: up === base.id ? undefined : up } : l));
33
60
  }
34
- const lines = rest.map((l) => {
35
- var _a;
36
- const attrs = [
37
- `stackAlign="${(_a = l.align) !== null && _a !== void 0 ? _a : 'center'}"`,
38
- l.size !== undefined ? `stackSize={${l.size}}` : '',
39
- l.variant && l.variant !== base.variant ? `variant="${l.variant}"` : '',
40
- ]
41
- .filter(Boolean)
42
- .join(' ');
43
- return l.text ? ` <${l.icon} ${attrs}>${l.text}</${l.icon}>` : ` <${l.icon} ${attrs} />`;
44
- });
61
+ const [heir, ...siblings] = iconStack.stackChildrenOf(layers, base.id);
62
+ if (!heir) {
63
+ return [];
64
+ }
65
+ const newBase = {
66
+ ...heir,
67
+ parent: undefined,
68
+ align: undefined,
69
+ size: undefined,
70
+ rotation: undefined,
71
+ offsetX: undefined,
72
+ offsetY: undefined,
73
+ };
45
74
  return [
46
- `<${base.icon}${baseAttrs}>`,
47
- ...(base.text ? [` ${base.text}`] : []),
48
- ...lines,
49
- `</${base.icon}>`,
50
- ].join('\n');
75
+ newBase,
76
+ ...layers
77
+ .filter((l) => l.id !== base.id && l.id !== heir.id)
78
+ .map((l) => siblings.includes(l)
79
+ ? { ...l, parent: heir.id }
80
+ : l.parent === heir.id
81
+ ? { ...l, parent: undefined }
82
+ : l),
83
+ ];
84
+ }
85
+ /** A colour of the layer's own, or "auto" — following its parent — until one is picked. */
86
+ function ColorField({ label, value, onChange, autoLabel, resetLabel }) {
87
+ return (jsxRuntime.jsxs("span", { className: "nice-icon-stacker__color", children: [jsxRuntime.jsxs("label", { className: "nice-icon-stacker__field", children: [jsxRuntime.jsx("span", { children: label }), jsxRuntime.jsx("input", { type: "color", value: value !== null && value !== void 0 ? value : AUTO_SWATCH, onChange: (e) => onChange(e.target.value) })] }), value ? (jsxRuntime.jsx("button", { type: "button", className: "nice-icon-stacker__reset", onClick: () => onChange(undefined), "aria-label": resetLabel, title: resetLabel, children: jsxRuntime.jsx(navigation.NtdClose, { size: 12 }) })) : (jsxRuntime.jsx("small", { className: "nice-icon-stacker__auto", children: autoLabel }))] }));
51
88
  }
52
89
  /**
53
90
  * NiceIconStacker (1.3.5) — builds an icon stack: the base icon and the icons stacked in it, each
54
- * with its position, size and text, shown live at three sizes and as the JSX that draws it. Icons
55
- * come in through `current` (the "add" button) and leave one by one or all at once.
91
+ * with its position, size, turn and colours, nested any number of levels deep (1.3.6) with text
92
+ * innermost — its size, colour and the value it is bound to — shown live at three sizes and as the
93
+ * JSX that draws it. The "add" button puts `current` into the selected layer and selects it, so
94
+ * adding again goes one level deeper; select an outer layer to add side by side. Layers leave one
95
+ * by one or all at once. `NiceIconStack` draws a stack built here from data.
56
96
  */
57
- function NiceIconStacker({ icons, layers: layersProp, defaultLayers, onLayersChange, current, previewProps, className, }) {
58
- var _a;
97
+ const NiceIconStacker = React.forwardRef(function NiceIconStacker({ icons, layers: layersProp, defaultLayers, onLayersChange, current, previewProps, className }, ref) {
98
+ var _a, _b, _c, _d;
59
99
  const { t } = uiCore.useNiceTranslation();
60
100
  const [layers, setLayers] = uiCore.useControllableState({
61
101
  value: layersProp,
@@ -65,25 +105,45 @@ function NiceIconStacker({ icons, layers: layersProp, defaultLayers, onLayersCha
65
105
  componentName: 'NiceIconStacker',
66
106
  });
67
107
  const nextId = React.useRef(0);
108
+ const targetGroup = React.useId();
68
109
  const [copied, setCopied] = React.useState(false);
110
+ // The layer the next icon goes into — the newest by default, so adding nests deeper.
111
+ const [selected, setSelected] = React.useState(undefined);
112
+ const base = layers[0];
113
+ const target = (_b = (_a = layers.find((l) => l.id === selected)) !== null && _a !== void 0 ? _a : layers[layers.length - 1]) !== null && _b !== void 0 ? _b : undefined;
69
114
  const resolve = React.useCallback((name) => (typeof icons === 'function' ? icons(name) : icons[name]), [icons]);
70
- const [base, ...rest] = layers;
71
- const code = React.useMemo(() => niceIconStackCode(layers), [layers]);
115
+ const tree = React.useMemo(() => walk(layers), [layers]);
116
+ const code = React.useMemo(() => iconStack.niceIconStackCode(layers), [layers]);
72
117
  const update = (id, patch) => setLayers((prev) => prev.map((l) => (l.id === id ? { ...l, ...patch } : l)));
73
118
  const add = () => {
74
119
  if (!current) {
75
120
  return;
76
121
  }
77
122
  nextId.current += 1;
78
- const layer = {
79
- id: `${current.icon}-${nextId.current}`,
80
- icon: current.icon,
81
- variant: current.variant,
82
- };
83
- setLayers((prev) => [...prev, layer]);
123
+ const id = `${current.icon}-${Date.now().toString(36)}-${nextId.current}`;
124
+ setLayers((prev) => {
125
+ const into = prev.find((l) => l.id === (target === null || target === void 0 ? void 0 : target.id));
126
+ const layer = {
127
+ id,
128
+ icon: current.icon,
129
+ variant: current.variant,
130
+ ...(into && into !== prev[0] ? { parent: into.id } : {}),
131
+ };
132
+ return [...prev, layer];
133
+ });
134
+ setSelected(id);
135
+ };
136
+ const remove = (id) => {
137
+ setLayers((prev) => removeLayer(prev, id));
138
+ if (selected === id) {
139
+ setSelected(undefined);
140
+ }
141
+ };
142
+ const clear = () => {
143
+ setLayers(NO_LAYERS);
144
+ setSelected(undefined);
84
145
  };
85
- const remove = (id) => setLayers((prev) => prev.filter((l) => l.id !== id));
86
- const clear = () => setLayers(NO_LAYERS);
146
+ React.useImperativeHandle(ref, () => ({ add, clear }));
87
147
  const copy = () => {
88
148
  var _a;
89
149
  void ((_a = navigator.clipboard) === null || _a === void 0 ? void 0 : _a.writeText(code).then(() => {
@@ -91,30 +151,36 @@ function NiceIconStacker({ icons, layers: layersProp, defaultLayers, onLayersCha
91
151
  window.setTimeout(() => setCopied(false), 1500);
92
152
  }));
93
153
  };
94
- const renderStack = (size) => {
95
- var _a;
96
- const Base = base ? resolve(base.icon) : undefined;
97
- if (!base || !Base) {
98
- return null;
99
- }
100
- return (jsxRuntime.jsxs(Base, { ...previewProps, variant: (_a = base.variant) !== null && _a !== void 0 ? _a : previewProps === null || previewProps === void 0 ? void 0 : previewProps.variant, size: size, title: base.icon, children: [base.text, rest.map((l) => {
101
- var _a;
102
- const Layer = resolve(l.icon);
103
- return Layer ? (jsxRuntime.jsx(Layer, { stackAlign: (_a = l.align) !== null && _a !== void 0 ? _a : 'center', stackSize: l.size, variant: l.variant !== base.variant ? l.variant : undefined, children: l.text }, l.id)) : null;
104
- })] }));
105
- };
106
- return (jsxRuntime.jsxs("div", { className: ['nice-icon-stacker', className].filter(Boolean).join(' '), children: [jsxRuntime.jsxs("div", { className: "nice-icon-stacker__toolbar", children: [jsxRuntime.jsxs("button", { type: "button", onClick: add, disabled: !current, children: [jsxRuntime.jsx(actions.NtdPlus, { size: 14 }), ' ', t('iconStacker.add', 'Add {icon} to the stack').replace('{icon}', (_a = current === null || current === void 0 ? void 0 : current.icon) !== null && _a !== void 0 ? _a : '')] }), jsxRuntime.jsxs("button", { type: "button", onClick: clear, disabled: layers.length === 0, children: [jsxRuntime.jsx(actions.NtdTrash, { size: 14 }), " ", t('iconStacker.clear', 'Clear the stack')] })] }), layers.length === 0 ? (jsxRuntime.jsx("p", { className: "nice-icon-stacker__empty", children: t('iconStacker.empty', 'The stack is empty. Add an icon to start one.') })) : (jsxRuntime.jsxs("div", { className: "nice-icon-stacker__body", children: [jsxRuntime.jsx("div", { className: "nice-icon-stacker__preview", "aria-label": t('iconStacker.preview', 'Preview'), role: "group", children: PREVIEW_SIZES.map((s) => (jsxRuntime.jsxs("span", { className: "nice-icon-stacker__preview-cell", children: [renderStack(s), jsxRuntime.jsxs("small", { children: [s, "px"] })] }, s))) }), jsxRuntime.jsx("ol", { className: "nice-icon-stacker__layers", "aria-label": t('iconStacker.layers', 'Layers'), children: layers.map((l, i) => {
107
- var _a, _b, _c;
154
+ const autoLabel = t('iconStacker.auto', 'auto');
155
+ const resetLabel = t('iconStacker.reset', 'Back to auto');
156
+ const addLabel = base && target
157
+ ? t('iconStacker.addInto', 'Add {icon} into {target}')
158
+ .replace('{icon}', (_c = current === null || current === void 0 ? void 0 : current.icon) !== null && _c !== void 0 ? _c : '')
159
+ .replace('{target}', target.icon)
160
+ : t('iconStacker.add', 'Add {icon} to the stack').replace('{icon}', (_d = current === null || current === void 0 ? void 0 : current.icon) !== null && _d !== void 0 ? _d : '');
161
+ return (jsxRuntime.jsxs("div", { className: ['nice-icon-stacker', className].filter(Boolean).join(' '), children: [jsxRuntime.jsxs("div", { className: "nice-icon-stacker__toolbar", children: [jsxRuntime.jsxs("button", { type: "button", onClick: add, disabled: !current, children: [jsxRuntime.jsx(actions.NtdPlus, { size: 14 }), " ", addLabel] }), jsxRuntime.jsxs("button", { type: "button", onClick: clear, disabled: layers.length === 0, children: [jsxRuntime.jsx(actions.NtdTrash, { size: 14 }), " ", t('iconStacker.clear', 'Clear the stack')] })] }), !base ? (jsxRuntime.jsx("p", { className: "nice-icon-stacker__empty", children: t('iconStacker.empty', 'The stack is empty. Add an icon to start one.') })) : (jsxRuntime.jsxs("div", { className: "nice-icon-stacker__body", children: [jsxRuntime.jsx("div", { className: "nice-icon-stacker__preview", "aria-label": t('iconStacker.preview', 'Preview'), role: "group", children: PREVIEW_SIZES.map((s) => (jsxRuntime.jsxs("span", { className: "nice-icon-stacker__preview-cell", children: [iconStack.renderIconStack(layers, icons, {
162
+ baseProps: { ...previewProps, size: s, title: base.icon },
163
+ }), jsxRuntime.jsxs("small", { children: [s, "px"] })] }, s))) }), jsxRuntime.jsx("ol", { className: "nice-icon-stacker__layers", "aria-label": t('iconStacker.layers', 'Layers'), children: tree.map(({ layer: l, depth }) => {
164
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
108
165
  const Icon = resolve(l.icon);
109
- const isBase = i === 0;
166
+ const isBase = l === base;
110
167
  const align = (_a = l.align) !== null && _a !== void 0 ? _a : 'center';
111
168
  const share = (_b = l.size) !== null && _b !== void 0 ? _b : stacking.defaultStackSize(align);
112
- return (jsxRuntime.jsxs("li", { className: "nice-icon-stacker__layer", children: [jsxRuntime.jsx("span", { className: "nice-icon-stacker__layer-icon", children: Icon ? jsxRuntime.jsx(Icon, { size: 24, variant: l.variant }) : null }), jsxRuntime.jsxs("span", { className: "nice-icon-stacker__layer-name", children: [l.icon, isBase && (jsxRuntime.jsx("span", { className: "nice-icon-stacker__badge", children: t('iconStacker.base', 'Base') }))] }), !isBase && (jsxRuntime.jsxs("label", { className: "nice-icon-stacker__field", children: [jsxRuntime.jsx("span", { children: t('iconStacker.align', 'Position') }), jsxRuntime.jsx("select", { value: align, onChange: (e) => update(l.id, { align: e.target.value }), children: ALIGNS.map((a) => (jsxRuntime.jsx("option", { value: a, children: a }, a))) })] })), !isBase && (jsxRuntime.jsxs("label", { className: "nice-icon-stacker__field", children: [jsxRuntime.jsx("span", { children: t('iconStacker.size', 'Size') }), jsxRuntime.jsx("input", { type: "range", min: 0.2, max: 1, step: 0.05, value: share, onChange: (e) => update(l.id, { size: Number(e.target.value) }) }), jsxRuntime.jsxs("output", { children: [Math.round(share * 100), "%"] })] })), jsxRuntime.jsxs("label", { className: "nice-icon-stacker__field", children: [jsxRuntime.jsx("span", { children: t('iconStacker.text', 'Text') }), jsxRuntime.jsx("input", { type: "text", value: (_c = l.text) !== null && _c !== void 0 ? _c : '', maxLength: 4, onChange: (e) => update(l.id, { text: e.target.value || undefined }) })] }), jsxRuntime.jsx("button", { type: "button", className: "nice-icon-stacker__remove", onClick: () => remove(l.id), "aria-label": t('iconStacker.remove', 'Remove {icon}').replace('{icon}', l.icon), title: t('iconStacker.remove', 'Remove {icon}').replace('{icon}', l.icon), children: jsxRuntime.jsx(actions.NtdTrash, { size: 14 }) })] }, l.id));
113
- }) }), jsxRuntime.jsxs("div", { className: "nice-icon-stacker__code", children: [jsxRuntime.jsx("pre", { children: code }), jsxRuntime.jsxs("button", { type: "button", onClick: copy, children: [copied ? jsxRuntime.jsx(actions.NtdCheck, { size: 14 }) : jsxRuntime.jsx(actions.NtdCopy, { size: 14 }), ' ', copied ? t('iconStacker.copied', 'Copied') : t('iconStacker.copy', 'Copy the code')] })] })] }))] }));
114
- }
169
+ const innermost = iconStack.stackChildrenOf(layers, l.id).length === 0;
170
+ const textSize = (_c = l.textSize) !== null && _c !== void 0 ? _c : stacking.defaultTextSize(((_d = l.text) !== null && _d !== void 0 ? _d : '').length || 1);
171
+ const bindingInvalid = !!l.textBinding && !iconStack.isValidTextBinding(l.textBinding);
172
+ const depthStyle = {
173
+ ['--nice-icon-stacker-depth']: String(depth),
174
+ };
175
+ const label = (key, fallback) => t(key, fallback).replace('{icon}', l.icon);
176
+ return (jsxRuntime.jsxs("li", { className: "nice-icon-stacker__layer", style: depthStyle, children: [jsxRuntime.jsxs("div", { className: "nice-icon-stacker__layer-row", children: [jsxRuntime.jsx("input", { type: "radio", name: targetGroup, checked: (target === null || target === void 0 ? void 0 : target.id) === l.id, onChange: () => setSelected(l.id), "aria-label": label('iconStacker.target', 'Add the next icon into {icon}'), title: label('iconStacker.target', 'Add the next icon into {icon}') }), jsxRuntime.jsx("span", { className: "nice-icon-stacker__layer-icon", children: Icon ? jsxRuntime.jsx(Icon, { size: 24, variant: l.variant }) : null }), jsxRuntime.jsxs("span", { className: "nice-icon-stacker__layer-name", children: [l.icon, isBase && (jsxRuntime.jsx("span", { className: "nice-icon-stacker__badge", children: t('iconStacker.base', 'Base') }))] }), !isBase && (jsxRuntime.jsxs("label", { className: "nice-icon-stacker__field", children: [jsxRuntime.jsx("span", { children: t('iconStacker.align', 'Position') }), jsxRuntime.jsx("select", { value: align, onChange: (e) => update(l.id, { align: e.target.value }), children: ALIGNS.map((a) => (jsxRuntime.jsx("option", { value: a, children: a }, a))) })] })), !isBase && (jsxRuntime.jsxs("label", { className: "nice-icon-stacker__field", children: [jsxRuntime.jsx("span", { children: t('iconStacker.size', 'Size') }), jsxRuntime.jsx("input", { type: "range", min: 0.2, max: 1, step: 0.05, value: share, onChange: (e) => update(l.id, { size: Number(e.target.value) }) }), jsxRuntime.jsxs("output", { children: [Math.round(share * 100), "%"] })] })), jsxRuntime.jsx("button", { type: "button", className: "nice-icon-stacker__remove", onClick: () => remove(l.id), "aria-label": label('iconStacker.remove', 'Remove {icon}'), title: label('iconStacker.remove', 'Remove {icon}'), children: jsxRuntime.jsx(actions.NtdTrash, { size: 14 }) })] }), jsxRuntime.jsxs("details", { className: "nice-icon-stacker__more", children: [jsxRuntime.jsx("summary", { children: t('iconStacker.more', 'More') }), jsxRuntime.jsxs("div", { className: "nice-icon-stacker__more-body", children: [!isBase && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("label", { className: "nice-icon-stacker__field", children: [jsxRuntime.jsx("span", { children: t('iconStacker.rotation', 'Turn') }), jsxRuntime.jsx("input", { type: "range", min: -180, max: 180, step: 15, value: (_e = l.rotation) !== null && _e !== void 0 ? _e : 0, onChange: (e) => update(l.id, { rotation: Number(e.target.value) || undefined }) }), jsxRuntime.jsxs("output", { children: [(_f = l.rotation) !== null && _f !== void 0 ? _f : 0, "\u00B0"] })] }), jsxRuntime.jsxs("label", { className: "nice-icon-stacker__field", children: [jsxRuntime.jsx("span", { children: t('iconStacker.offsetX', 'Shift right') }), jsxRuntime.jsx("input", { type: "range", min: -50, max: 50, step: 1, value: (_g = l.offsetX) !== null && _g !== void 0 ? _g : 0, onChange: (e) => update(l.id, { offsetX: Number(e.target.value) || undefined }) }), jsxRuntime.jsxs("output", { children: [(_h = l.offsetX) !== null && _h !== void 0 ? _h : 0, "%"] })] }), jsxRuntime.jsxs("label", { className: "nice-icon-stacker__field", children: [jsxRuntime.jsx("span", { children: t('iconStacker.offsetY', 'Shift down') }), jsxRuntime.jsx("input", { type: "range", min: -50, max: 50, step: 1, value: (_j = l.offsetY) !== null && _j !== void 0 ? _j : 0, onChange: (e) => update(l.id, { offsetY: Number(e.target.value) || undefined }) }), jsxRuntime.jsxs("output", { children: [(_k = l.offsetY) !== null && _k !== void 0 ? _k : 0, "%"] })] })] })), jsxRuntime.jsx(ColorField, { label: t('iconStacker.primary', 'Primary'), value: l.primaryColor, onChange: (v) => update(l.id, { primaryColor: v }), autoLabel: autoLabel, resetLabel: resetLabel }), jsxRuntime.jsx(ColorField, { label: t('iconStacker.secondary', 'Secondary'), value: l.secondaryColor, onChange: (v) => update(l.id, { secondaryColor: v }), autoLabel: autoLabel, resetLabel: resetLabel }), jsxRuntime.jsx(ColorField, { label: t('iconStacker.accent', 'Accent'), value: l.accentColor, onChange: (v) => update(l.id, { accentColor: v }), autoLabel: autoLabel, resetLabel: resetLabel })] })] }), (innermost || l.text || l.textBinding) && (jsxRuntime.jsxs("div", { className: "nice-icon-stacker__text", children: [jsxRuntime.jsxs("label", { className: "nice-icon-stacker__field", children: [jsxRuntime.jsx("span", { children: t('iconStacker.text', 'Text') }), jsxRuntime.jsx("input", { type: "text", value: (_l = l.text) !== null && _l !== void 0 ? _l : '', maxLength: 4, onChange: (e) => update(l.id, { text: e.target.value || undefined }) })] }), jsxRuntime.jsxs("label", { className: "nice-icon-stacker__field", children: [jsxRuntime.jsx("span", { children: t('iconStacker.textSize', 'Text size') }), jsxRuntime.jsx("input", { type: "range", min: 3, max: 14, step: 0.5, value: textSize, onChange: (e) => update(l.id, { textSize: Number(e.target.value) }) }), jsxRuntime.jsx("output", { children: textSize }), l.textSize !== undefined && (jsxRuntime.jsx("button", { type: "button", className: "nice-icon-stacker__reset", onClick: () => update(l.id, { textSize: undefined }), "aria-label": resetLabel, title: resetLabel, children: jsxRuntime.jsx(navigation.NtdClose, { size: 12 }) }))] }), jsxRuntime.jsx(ColorField, { label: t('iconStacker.textColor', 'Text colour'), value: l.textColor, onChange: (v) => update(l.id, { textColor: v }), autoLabel: autoLabel, resetLabel: resetLabel }), jsxRuntime.jsxs("label", { className: "nice-icon-stacker__field", children: [jsxRuntime.jsx("span", { children: t('iconStacker.bind', 'Bind to') }), jsxRuntime.jsx("input", { type: "text", value: (_m = l.textBinding) !== null && _m !== void 0 ? _m : '', "aria-invalid": bindingInvalid || undefined, title: t('iconStacker.bindHint', 'The name of the value the text shows, e.g. count — the text above is a sample'), onChange: (e) => update(l.id, { textBinding: e.target.value.trim() || undefined }) })] })] }))] }, l.id));
177
+ }) }), jsxRuntime.jsxs("div", { className: "nice-icon-stacker__code", children: [jsxRuntime.jsx("pre", { children: code }), jsxRuntime.jsxs("button", { type: "button", onClick: copy, children: [copied ? jsxRuntime.jsx(actions.NtdCheck, { size: 14 }) : jsxRuntime.jsx(actions.NtdCopy, { size: 14 }), ' ', copied
178
+ ? t('iconStacker.copied', 'Copied')
179
+ : t('iconStacker.copy', 'Copy the code')] })] })] }))] }));
180
+ });
115
181
  if (process.env.NODE_ENV !== 'production') {
116
182
  NiceIconStacker.displayName = 'NiceIconStacker';
117
183
  }
118
184
 
185
+ exports.niceIconStackCode = iconStack.niceIconStackCode;
119
186
  exports.NiceIconStacker = NiceIconStacker;
120
- exports.niceIconStackCode = niceIconStackCode;
@@ -3,6 +3,7 @@
3
3
  var animationControls = require('./animationControls.js');
4
4
  var NiceIconPicker = require('./NiceIconPicker.js');
5
5
  var NiceIconStacker = require('./NiceIconStacker.js');
6
+ var iconStack = require('./iconStack.js');
6
7
  var NtdIconMorph = require('./NtdIconMorph.js');
7
8
  var NtdIconBadge = require('./NtdIconBadge.js');
8
9
  var smilAnimations = require('./smilAnimations.js');
@@ -20,7 +21,10 @@ exports.createVisibilityAnimation = animationControls.createVisibilityAnimation;
20
21
  exports.useAnimationSequence = animationControls.useAnimationSequence;
21
22
  exports.NiceIconPicker = NiceIconPicker.NiceIconPicker;
22
23
  exports.NiceIconStacker = NiceIconStacker.NiceIconStacker;
23
- exports.niceIconStackCode = NiceIconStacker.niceIconStackCode;
24
+ exports.NiceIconStack = iconStack.NiceIconStack;
25
+ exports.isValidTextBinding = iconStack.isValidTextBinding;
26
+ exports.niceIconStackCode = iconStack.niceIconStackCode;
27
+ exports.renderIconStack = iconStack.renderIconStack;
24
28
  exports.NtdIconMorph = NtdIconMorph.NtdIconMorph;
25
29
  exports.NtdMorphPair = NtdIconMorph.NtdMorphPair;
26
30
  exports.NtdPathMorph = NtdIconMorph.NtdPathMorph;
@@ -116,11 +116,11 @@ function renderCutoutShape(shape, fill, maskId) {
116
116
  }
117
117
  function createIcon(options) {
118
118
  const { displayName, viewBox = '0 0 24 24', paths, textCenter = BOX_CENTER } = options;
119
- const Icon = React.forwardRef(({ size = 'md', variant = 'solid', cutoutShape = 'rounded-square', color, lightColor, darkColor, primaryColor: propPrimaryColor = 'currentColor', secondaryColor, accentColor, primaryOpacity = 1, secondaryOpacity = 0.4, accentOpacity = 1, strokeWidth = 2, strokeLinecap: propLinecap, strokeLinejoin: propLinejoin, animation, animationDuration, smilAnimation, smilDuration = 1, smilRepeatCount = 'indefinite', smilEasing = 'ease-in-out', smilDelay = 0, gradient, gradientType = 'linear', gradientAngle = 135, gradientAnimation,
119
+ const Icon = React.forwardRef(({ size = 'md', variant = 'solid', cutoutShape = 'rounded-square', color, lightColor, darkColor, primaryColor: propPrimaryColor = 'currentColor', secondaryColor, accentColor, primaryOpacity = 1, secondaryOpacity = 0.4, accentOpacity = 1, strokeWidth = 1, strokeLinecap: propLinecap, strokeLinejoin: propLinejoin, animation, animationDuration, smilAnimation, smilDuration = 1, smilRepeatCount = 'indefinite', smilEasing = 'ease-in-out', smilDelay = 0, gradient, gradientType = 'linear', gradientAngle = 135, gradientAnimation,
120
120
  // Transform props
121
121
  rotate, flipX, flipY, scale, transition, transformOrigin = 'center center',
122
122
  // stacking (1.3.5): the parent icon reads a child's own align / size from its props
123
- children, stackAlign: _stackAlign, stackSize: _stackSize, textColor, textSize, title, className, style, ...rest }, ref) => {
123
+ children, stackAlign: _stackAlign, stackSize: _stackSize, stackRotation: _stackRotation, stackOffsetX: _stackOffsetX, stackOffsetY: _stackOffsetY, textColor, textSize, title, className, style, ...rest }, ref) => {
124
124
  var _a, _b, _c, _d, _e, _f, _g;
125
125
  const resolvedSize = typeof size === 'number' ? size : ((_a = types.SIZE_MAP[size]) !== null && _a !== void 0 ? _a : 24);
126
126
  const gradId = React.useId();
@@ -0,0 +1,113 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var React = require('react');
5
+
6
+ /** A name `textBinding` can take: a JavaScript identifier. */
7
+ function isValidTextBinding(name) {
8
+ return !!name && /^[A-Za-z_$][\w$]*$/.test(name);
9
+ }
10
+ /** The id of the layer `layer` sits in, `undefined` for the base. */
11
+ function stackParentId(layers, layer) {
12
+ var _a, _b;
13
+ return layer === layers[0] ? undefined : ((_a = layer.parent) !== null && _a !== void 0 ? _a : (_b = layers[0]) === null || _b === void 0 ? void 0 : _b.id);
14
+ }
15
+ /** The layers that sit directly in layer `id`, in order. */
16
+ function stackChildrenOf(layers, id) {
17
+ return layers.filter((l) => l !== layers[0] && stackParentId(layers, l) === id);
18
+ }
19
+ const COLOR_PROPS = ['primaryColor', 'secondaryColor', 'accentColor', 'textColor'];
20
+ /** The stack as JSX: every layer wrapping the layers that sit in it, text innermost. */
21
+ function niceIconStackCode(layers) {
22
+ const base = layers[0];
23
+ if (!base) {
24
+ return '';
25
+ }
26
+ const bound = layers.filter((l) => isValidTextBinding(l.textBinding));
27
+ const write = (layer, parent, indent) => {
28
+ var _a;
29
+ const attrs = [
30
+ parent ? `stackAlign="${(_a = layer.align) !== null && _a !== void 0 ? _a : 'center'}"` : '',
31
+ parent && layer.size !== undefined ? `stackSize={${layer.size}}` : '',
32
+ parent && layer.rotation ? `stackRotation={${layer.rotation}}` : '',
33
+ parent && layer.offsetX ? `stackOffsetX={${layer.offsetX}}` : '',
34
+ parent && layer.offsetY ? `stackOffsetY={${layer.offsetY}}` : '',
35
+ layer.variant && layer.variant !== (parent === null || parent === void 0 ? void 0 : parent.variant) ? `variant="${layer.variant}"` : '',
36
+ ...COLOR_PROPS.map((p) => (layer[p] ? `${p}="${layer[p]}"` : '')),
37
+ layer.textSize !== undefined ? `textSize={${layer.textSize}}` : '',
38
+ ]
39
+ .filter(Boolean)
40
+ .join(' ');
41
+ const open = `${layer.icon}${attrs ? ` ${attrs}` : ''}`;
42
+ const text = isValidTextBinding(layer.textBinding) ? `{${layer.textBinding}}` : layer.text;
43
+ const inner = stackChildrenOf(layers, layer.id);
44
+ if (inner.length === 0) {
45
+ return text ? `${indent}<${open}>${text}</${layer.icon}>` : `${indent}<${open} />`;
46
+ }
47
+ return [
48
+ `${indent}<${open}>`,
49
+ ...(text ? [`${indent} ${text}`] : []),
50
+ ...inner.map((child) => write(child, layer, `${indent} `)),
51
+ `${indent}</${layer.icon}>`,
52
+ ].join('\n');
53
+ };
54
+ const notes = bound.map((l) => `// {${l.textBinding}}: bound text${l.text ? `, e.g. "${l.text}"` : ''}`);
55
+ return [...notes, write(base, undefined, '')].join('\n');
56
+ }
57
+ /**
58
+ * The stack drawn as icons — the base wrapping the layers that sit in it, text innermost (1.3.6).
59
+ * One SVG, as nesting the icons by hand would give.
60
+ */
61
+ function renderIconStack(layers, icons, { baseProps, values } = {}) {
62
+ const resolve = (name) => (typeof icons === 'function' ? icons(name) : icons[name]);
63
+ const draw = (layer, parent) => {
64
+ var _a, _b;
65
+ const Icon = resolve(layer.icon);
66
+ if (!Icon) {
67
+ return null;
68
+ }
69
+ const bound = isValidTextBinding(layer.textBinding) ? values === null || values === void 0 ? void 0 : values[layer.textBinding] : undefined;
70
+ const text = bound !== null && bound !== void 0 ? bound : layer.text;
71
+ const own = {
72
+ primaryColor: layer.primaryColor,
73
+ secondaryColor: layer.secondaryColor,
74
+ accentColor: layer.accentColor,
75
+ textColor: layer.textColor,
76
+ textSize: layer.textSize,
77
+ };
78
+ const inner = stackChildrenOf(layers, layer.id).map((child) => draw(child, layer));
79
+ if (!parent) {
80
+ const props = { ...baseProps };
81
+ for (const [k, v] of Object.entries(own)) {
82
+ if (v !== undefined) {
83
+ props[k] = v;
84
+ }
85
+ }
86
+ return (React.createElement(Icon, { ...props, key: layer.id, variant: (_a = layer.variant) !== null && _a !== void 0 ? _a : baseProps === null || baseProps === void 0 ? void 0 : baseProps.variant },
87
+ inner,
88
+ text));
89
+ }
90
+ return (React.createElement(Icon, { ...own, key: layer.id, stackAlign: (_b = layer.align) !== null && _b !== void 0 ? _b : 'center', stackSize: layer.size, stackRotation: layer.rotation, stackOffsetX: layer.offsetX, stackOffsetY: layer.offsetY, variant: layer.variant !== parent.variant ? layer.variant : undefined },
91
+ inner,
92
+ text));
93
+ };
94
+ return layers[0] ? draw(layers[0], undefined) : null;
95
+ }
96
+ /**
97
+ * NiceIconStack (1.3.6) — draws a stack kept as data (NiceIconStacker's layers) as one icon, its
98
+ * bound text filled from `values`: `<NiceIconStack layers={stack} icons={icons} values={{ count }} />`.
99
+ * The rest of the props go to the base icon.
100
+ */
101
+ function NiceIconStack({ layers, icons, values, ...baseProps }) {
102
+ return jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderIconStack(layers, icons, { baseProps, values }) });
103
+ }
104
+ if (process.env.NODE_ENV !== 'production') {
105
+ NiceIconStack.displayName = 'NiceIconStack';
106
+ }
107
+
108
+ exports.NiceIconStack = NiceIconStack;
109
+ exports.isValidTextBinding = isValidTextBinding;
110
+ exports.niceIconStackCode = niceIconStackCode;
111
+ exports.renderIconStack = renderIconStack;
112
+ exports.stackChildrenOf = stackChildrenOf;
113
+ exports.stackParentId = stackParentId;
@@ -51,13 +51,20 @@ const NtdEditDetail = /* @__PURE__ */ createIcon.createIcon({
51
51
  vivid: ({ primaryColor, secondaryColor, strokeWidth, uid }) => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("path", { d: EDIT_DETAIL_FRAME, stroke: secondaryColor, strokeWidth: strokeWidth * 2, fill: "none" }), jsxRuntime.jsx("path", { d: EDIT_DETAIL_PENCIL, fill: primaryColor }), iconParts.within(uid, 'band', jsxRuntime.jsx("path", { d: EDIT_DETAIL_PENCIL }), jsxRuntime.jsx("path", { d: EDIT_DETAIL_BAND, stroke: secondaryColor, strokeWidth: "1.3" }))] })),
52
52
  },
53
53
  });
54
+ // NtdTrash: the lid overhangs the bin by 1 a side (16 across; it was 18) and the handle is 6 by 3
55
+ // (it was 8 by 4). Vivid: the lid and the handle secondary, the grooves accent (they were a fixed
56
+ // #3B82F6).
57
+ const TRASH_LID = 'M4 6h16';
58
+ const TRASH_HANDLE = 'M9 6V4.5a1.5 1.5 0 011.5-1.5h3a1.5 1.5 0 011.5 1.5V6';
59
+ const TRASH_BIN = 'M5 6l1 14a2 2 0 002 2h8a2 2 0 002-2l1-14H5z';
60
+ const TRASH_GROOVES = 'M10 10.5v7M14 10.5v7';
54
61
  const NtdTrash = /* @__PURE__ */ createIcon.createIcon({
55
62
  displayName: 'NtdTrash',
56
63
  paths: {
57
- solid: ({ primaryColor, accentColor, accentOpacity, strokeWidth }) => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("path", { d: "M3 6h18", stroke: primaryColor, strokeWidth: strokeWidth }), jsxRuntime.jsx("path", { d: "M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2", stroke: primaryColor, strokeWidth: strokeWidth }), jsxRuntime.jsx("path", { d: "M5 6l1 14a2 2 0 002 2h8a2 2 0 002-2l1-14H5z", fill: primaryColor }), jsxRuntime.jsx("line", { x1: "10", y1: "10.5", x2: "10", y2: "17.5", stroke: accentColor, strokeWidth: strokeWidth * 0.7, opacity: accentOpacity }), jsxRuntime.jsx("line", { x1: "14", y1: "10.5", x2: "14", y2: "17.5", stroke: accentColor, strokeWidth: strokeWidth * 0.7, opacity: accentOpacity })] })),
58
- outline: ({ primaryColor, strokeWidth }) => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("path", { d: "M3 6h18", stroke: primaryColor, strokeWidth: strokeWidth }), jsxRuntime.jsx("path", { d: "M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2", stroke: primaryColor, strokeWidth: strokeWidth }), jsxRuntime.jsx("path", { d: "M5 6l1 14a2 2 0 002 2h8a2 2 0 002-2l1-14", stroke: primaryColor, strokeWidth: strokeWidth, fill: "none" }), jsxRuntime.jsx("line", { x1: "10", y1: "10.5", x2: "10", y2: "17.5", stroke: primaryColor, strokeWidth: strokeWidth }), jsxRuntime.jsx("line", { x1: "14", y1: "10.5", x2: "14", y2: "17.5", stroke: primaryColor, strokeWidth: strokeWidth })] })),
59
- duotone: ({ primaryColor, secondaryColor, secondaryOpacity, strokeWidth }) => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("path", { d: "M5 6l1 14a2 2 0 002 2h8a2 2 0 002-2l1-14H5z", fill: secondaryColor, opacity: secondaryOpacity }), jsxRuntime.jsx("path", { d: "M3 6h18", stroke: primaryColor, strokeWidth: strokeWidth }), jsxRuntime.jsx("path", { d: "M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2", stroke: primaryColor, strokeWidth: strokeWidth }), jsxRuntime.jsx("path", { d: "M5 6l1 14a2 2 0 002 2h8a2 2 0 002-2l1-14", stroke: primaryColor, strokeWidth: strokeWidth, fill: "none" }), jsxRuntime.jsx("line", { x1: "10", y1: "10.5", x2: "10", y2: "17.5", stroke: primaryColor, strokeWidth: strokeWidth }), jsxRuntime.jsx("line", { x1: "14", y1: "10.5", x2: "14", y2: "17.5", stroke: primaryColor, strokeWidth: strokeWidth })] })),
60
- vivid: ({ primaryColor, secondaryColor }) => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("path", { d: "M5 6l1 14a2 2 0 002 2h8a2 2 0 002-2l1-14H5z", fill: primaryColor }), jsxRuntime.jsx("path", { d: "M5 6l1 14a2 2 0 002 2h8a2 2 0 002-2l1-14H5z", fill: "white", opacity: "0.1" }), jsxRuntime.jsx("path", { d: "M3 6h18", stroke: secondaryColor, strokeWidth: "1.8" }), jsxRuntime.jsx("path", { d: "M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2", stroke: "#3B82F6", strokeWidth: "1.5" }), jsxRuntime.jsx("line", { x1: "10", y1: "10.5", x2: "10", y2: "17.5", stroke: "#3B82F6", strokeWidth: "1.5" }), jsxRuntime.jsx("line", { x1: "14", y1: "10.5", x2: "14", y2: "17.5", stroke: "#3B82F6", strokeWidth: "1.5" })] })),
64
+ solid: ({ primaryColor, accentColor, accentOpacity, strokeWidth }) => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("path", { d: TRASH_LID, stroke: primaryColor, strokeWidth: strokeWidth }), jsxRuntime.jsx("path", { d: TRASH_HANDLE, stroke: primaryColor, strokeWidth: strokeWidth, fill: "none" }), jsxRuntime.jsx("path", { d: TRASH_BIN, fill: primaryColor }), jsxRuntime.jsx("line", { x1: "10", y1: "10.5", x2: "10", y2: "17.5", stroke: accentColor, strokeWidth: strokeWidth * 0.7, opacity: accentOpacity }), jsxRuntime.jsx("line", { x1: "14", y1: "10.5", x2: "14", y2: "17.5", stroke: accentColor, strokeWidth: strokeWidth * 0.7, opacity: accentOpacity })] })),
65
+ outline: ({ primaryColor, strokeWidth }) => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("path", { d: TRASH_LID, stroke: primaryColor, strokeWidth: strokeWidth }), jsxRuntime.jsx("path", { d: TRASH_HANDLE, stroke: primaryColor, strokeWidth: strokeWidth, fill: "none" }), jsxRuntime.jsx("path", { d: "M5 6l1 14a2 2 0 002 2h8a2 2 0 002-2l1-14", stroke: primaryColor, strokeWidth: strokeWidth, fill: "none" }), jsxRuntime.jsx("path", { d: TRASH_GROOVES, stroke: primaryColor, strokeWidth: strokeWidth })] })),
66
+ duotone: ({ primaryColor, secondaryColor, secondaryOpacity, strokeWidth }) => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("path", { d: TRASH_BIN, fill: secondaryColor, opacity: secondaryOpacity }), jsxRuntime.jsx("path", { d: TRASH_LID, stroke: primaryColor, strokeWidth: strokeWidth }), jsxRuntime.jsx("path", { d: TRASH_HANDLE, stroke: primaryColor, strokeWidth: strokeWidth, fill: "none" }), jsxRuntime.jsx("path", { d: "M5 6l1 14a2 2 0 002 2h8a2 2 0 002-2l1-14", stroke: primaryColor, strokeWidth: strokeWidth, fill: "none" }), jsxRuntime.jsx("path", { d: TRASH_GROOVES, stroke: primaryColor, strokeWidth: strokeWidth })] })),
67
+ vivid: ({ primaryColor, secondaryColor, accentColor, accentOpacity, strokeWidth }) => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("path", { d: TRASH_BIN, fill: primaryColor }), jsxRuntime.jsx("path", { d: TRASH_LID, stroke: secondaryColor, strokeWidth: strokeWidth }), jsxRuntime.jsx("path", { d: TRASH_HANDLE, stroke: secondaryColor, strokeWidth: strokeWidth, fill: "none" }), jsxRuntime.jsx("path", { d: TRASH_GROOVES, stroke: accentColor, strokeWidth: strokeWidth * 0.7, opacity: accentOpacity })] })),
61
68
  },
62
69
  });
63
70
  // NtdSave: a 3.5" floppy — the clipped corner, a wide metal shutter at the top edge with its