@nice2dev/icons 1.3.16 → 1.3.18

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 (55) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +21 -1
  3. package/dist/cjs/animationControls.js +10 -5
  4. package/dist/cjs/animationSpeed.js +102 -0
  5. package/dist/cjs/createIcon.js +68 -24
  6. package/dist/cjs/iconStyles.js +1171 -1081
  7. package/dist/cjs/icons/actions.js +2 -2
  8. package/dist/cjs/icons/brand.js +4 -747
  9. package/dist/cjs/icons/business.js +59 -23
  10. package/dist/cjs/icons/files.js +54 -54
  11. package/dist/cjs/icons/food.js +75 -557
  12. package/dist/cjs/icons/fun.js +51 -33
  13. package/dist/cjs/icons/index.js +0 -7
  14. package/dist/cjs/icons/navigation.js +26 -7
  15. package/dist/cjs/icons/omniverk.js +17 -244
  16. package/dist/cjs/icons/science.js +63 -170
  17. package/dist/cjs/icons/sports.js +45 -137
  18. package/dist/cjs/index.js +0 -7
  19. package/dist/cjs/reducedMotion.js +30 -0
  20. package/dist/cjs/smilAnimations.js +16 -3
  21. package/dist/cjs/utilities.js +4 -25
  22. package/dist/esm/animationControls.js +10 -5
  23. package/dist/esm/animationSpeed.js +102 -0
  24. package/dist/esm/createIcon.js +69 -25
  25. package/dist/esm/iconStyles.js +1171 -1081
  26. package/dist/esm/icons/actions.js +2 -2
  27. package/dist/esm/icons/brand.js +5 -748
  28. package/dist/esm/icons/business.js +59 -23
  29. package/dist/esm/icons/files.js +54 -54
  30. package/dist/esm/icons/food.js +74 -556
  31. package/dist/esm/icons/fun.js +51 -33
  32. package/dist/esm/icons/index.js +3 -10
  33. package/dist/esm/icons/navigation.js +26 -7
  34. package/dist/esm/icons/omniverk.js +17 -244
  35. package/dist/esm/icons/science.js +63 -170
  36. package/dist/esm/icons/sports.js +45 -137
  37. package/dist/esm/index.js +3 -10
  38. package/dist/esm/nicetodev-icons.css +1 -1
  39. package/dist/esm/reducedMotion.js +30 -0
  40. package/dist/esm/smilAnimations.js +16 -3
  41. package/dist/esm/utilities.js +5 -26
  42. package/dist/types/animationSpeed.d.ts +28 -0
  43. package/dist/types/createIcon.d.ts +6 -0
  44. package/dist/types/iconStyles.d.ts +12 -1
  45. package/dist/types/icons/brand.d.ts +0 -4
  46. package/dist/types/icons/food.d.ts +0 -3
  47. package/dist/types/icons/index.d.ts +3 -3
  48. package/dist/types/icons/omniverk.d.ts +0 -3
  49. package/dist/types/icons/science.d.ts +1 -1
  50. package/dist/types/reducedMotion.d.ts +6 -0
  51. package/dist/types/smilAnimations.d.ts +10 -2
  52. package/dist/types/types.d.ts +26 -0
  53. package/dist/types/utilities.d.ts +2 -2
  54. package/package.json +2 -2
  55. package/dist/cjs/nicetodev-icons.css +0 -1
@@ -186,7 +186,7 @@ const AnimationController = /* @__PURE__ */ forwardRef(function AnimationControl
186
186
  const [singleState, setSingleState] = useState(
187
187
  autoPlay ? "playing" : "idle"
188
188
  );
189
- const singleIterationRef = useRef(0);
189
+ const [singleIteration, setSingleIteration] = useState(0);
190
190
  const sequenceConfig = isSequence ? animation : { steps: [{ animation, duration: 1e3 }] };
191
191
  const sequence = useAnimationSequence(sequenceConfig);
192
192
  const state = isSequence ? sequence.state : singleState;
@@ -245,7 +245,7 @@ const AnimationController = /* @__PURE__ */ forwardRef(function AnimationControl
245
245
  sequence.play();
246
246
  } else {
247
247
  setSingleState("playing");
248
- singleIterationRef.current++;
248
+ setSingleIteration((n) => n + 1);
249
249
  }
250
250
  onStart?.();
251
251
  },
@@ -262,7 +262,7 @@ const AnimationController = /* @__PURE__ */ forwardRef(function AnimationControl
262
262
  sequence.reset();
263
263
  } else {
264
264
  setSingleState("idle");
265
- singleIterationRef.current++;
265
+ setSingleIteration((n) => n + 1);
266
266
  }
267
267
  },
268
268
  goToStep: (index) => {
@@ -308,7 +308,7 @@ const AnimationController = /* @__PURE__ */ forwardRef(function AnimationControl
308
308
  ...children.props.style,
309
309
  ...animationStyles
310
310
  },
311
- key: isSequence ? `seq-${currentStepIndex}-${sequence.loopIteration}` : `single-${singleIterationRef.current}`
311
+ key: isSequence ? `seq-${currentStepIndex}-${sequence.loopIteration}` : `single-${singleIteration}`
312
312
  });
313
313
  return /* @__PURE__ */ jsx(
314
314
  "span",
@@ -333,6 +333,7 @@ const InteractiveIcon = /* @__PURE__ */ forwardRef(
333
333
  const [isActive, setIsActive] = useState(false);
334
334
  const [isLeaving, setIsLeaving] = useState(false);
335
335
  const [hasPlayed, setHasPlayed] = useState(false);
336
+ const [plays, setPlays] = useState(0);
336
337
  const wrapperRef = useRef(null);
337
338
  const timeoutRef = useRef(null);
338
339
  const setRefs = useCallback(
@@ -366,11 +367,13 @@ const InteractiveIcon = /* @__PURE__ */ forwardRef(
366
367
  timeoutRef.current = setTimeout(() => {
367
368
  setIsActive(true);
368
369
  setHasPlayed(true);
370
+ setPlays((n) => n + 1);
369
371
  onInteractionStart?.();
370
372
  }, config.delay);
371
373
  } else {
372
374
  setIsActive(true);
373
375
  setHasPlayed(true);
376
+ setPlays((n) => n + 1);
374
377
  onInteractionStart?.();
375
378
  }
376
379
  }, [disabled, config.once, config.delay, hasPlayed, clearTimer, onInteractionStart]);
@@ -423,7 +426,9 @@ const InteractiveIcon = /* @__PURE__ */ forwardRef(
423
426
  style: {
424
427
  ...children.props.style,
425
428
  ...animationStyles
426
- }
429
+ },
430
+ // a click plays the animation again: a new key restarts it
431
+ ...config.trigger === "click" ? { key: `play-${plays}` } : {}
427
432
  });
428
433
  return /* @__PURE__ */ jsx(
429
434
  "span",
@@ -0,0 +1,102 @@
1
+ import React from "react";
2
+ const ANIMATION_SLOWDOWN = 2;
3
+ function animationScale(speed) {
4
+ const s = typeof speed === "number" && speed > 0 && Number.isFinite(speed) ? speed : 1;
5
+ return ANIMATION_SLOWDOWN / s;
6
+ }
7
+ function durationScale(durationGiven, speed) {
8
+ return durationGiven ? animationScale(speed) / ANIMATION_SLOWDOWN : animationScale(speed);
9
+ }
10
+ const SMIL_ELEMENTS = /* @__PURE__ */ new Set([
11
+ "animate",
12
+ "animateTransform",
13
+ "animateMotion",
14
+ "animateColor",
15
+ "set"
16
+ ]);
17
+ const TIME_PROPS = ["dur", "begin", "end"];
18
+ const CLOCK = /^(\d*\.?\d+)(ms|s|min|h)?$/;
19
+ function scaleTime(value, scale) {
20
+ if (typeof value === "number") {
21
+ return Number((value * scale).toFixed(4));
22
+ }
23
+ return value.split(";").map((part) => {
24
+ const m = CLOCK.exec(part.trim());
25
+ if (!m) {
26
+ return part;
27
+ }
28
+ return `${Number((Number(m[1]) * scale).toFixed(4))}${m[2] ?? "s"}`;
29
+ }).join(";");
30
+ }
31
+ function retimeTree(node, scale) {
32
+ if (scale === 1) {
33
+ return node;
34
+ }
35
+ const walk = (n) => {
36
+ if (Array.isArray(n)) {
37
+ return n.map(walk);
38
+ }
39
+ if (!React.isValidElement(n)) {
40
+ return n;
41
+ }
42
+ const props = n.props;
43
+ const isHost = typeof n.type === "string";
44
+ const isFragment = n.type === React.Fragment;
45
+ if (!isHost && !isFragment) {
46
+ return n;
47
+ }
48
+ const next = {};
49
+ if (isHost && SMIL_ELEMENTS.has(n.type)) {
50
+ for (const key of TIME_PROPS) {
51
+ const v = props[key];
52
+ if (typeof v === "string" || typeof v === "number") {
53
+ next[key] = scaleTime(v, scale);
54
+ }
55
+ }
56
+ }
57
+ if (props.children !== void 0) {
58
+ next.children = React.Children.map(props.children, walk);
59
+ }
60
+ return Object.keys(next).length > 0 ? React.cloneElement(n, next) : n;
61
+ };
62
+ return walk(node);
63
+ }
64
+ function stillTree(node) {
65
+ const walk = (n) => {
66
+ if (Array.isArray(n)) {
67
+ return n.map(walk);
68
+ }
69
+ if (!React.isValidElement(n)) {
70
+ return n;
71
+ }
72
+ const props = n.props;
73
+ const isHost = typeof n.type === "string";
74
+ if (!isHost && n.type !== React.Fragment) {
75
+ return n;
76
+ }
77
+ const next = {};
78
+ if (isHost && SMIL_ELEMENTS.has(n.type)) {
79
+ next.begin = "0s";
80
+ next.dur = "0.001s";
81
+ next.repeatCount = "1";
82
+ next.repeatDur = void 0;
83
+ next.end = void 0;
84
+ }
85
+ if (props.children !== void 0) {
86
+ next.children = React.Children.map(props.children, walk);
87
+ }
88
+ return Object.keys(next).length > 0 ? React.cloneElement(n, next) : n;
89
+ };
90
+ return walk(node);
91
+ }
92
+ function motionTree(node, scale, still) {
93
+ return still ? stillTree(node) : retimeTree(node, scale);
94
+ }
95
+ export {
96
+ ANIMATION_SLOWDOWN,
97
+ animationScale,
98
+ durationScale,
99
+ motionTree,
100
+ retimeTree,
101
+ stillTree
102
+ };
@@ -1,5 +1,7 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
- import { forwardRef, useId, useSyncExternalStore } from "react";
2
+ import { forwardRef, useId, useState, useSyncExternalStore } from "react";
3
+ import { durationScale, stillTree, motionTree, animationScale } from "./animationSpeed.js";
4
+ import { useStillMotion } from "./reducedMotion.js";
3
5
  import { sharpenTree, roundTree, wireTree, MINI_SCALE } from "./sharpen.js";
4
6
  import { generateSmilAnimation } from "./smilAnimations.js";
5
7
  import { BOX_PERFECT_STACK, renderStackChildren, NTD_ICON_MARK, PERFECT_STACK } from "./stacking.js";
@@ -24,11 +26,12 @@ function useColorScheme() {
24
26
  return useSyncExternalStore(subscribeColorScheme, getColorScheme, () => "light");
25
27
  }
26
28
  const BOX_CENTER = [12, 12];
27
- function renderGradientDef(id, preset, type, angle, animation) {
29
+ function renderGradientDef(id, preset, type, angle, animation, timeScale = 1) {
28
30
  const stops = GRADIENT_PRESETS[preset];
29
31
  if (!stops) {
30
32
  return null;
31
33
  }
34
+ const loop = `${Number((3 * timeScale).toFixed(4))}s`;
32
35
  const stopEls = stops.map((color, i) => {
33
36
  const offset = `${Math.round(i / (stops.length - 1) * 100)}%`;
34
37
  const nextColor = stops[(i + 1) % stops.length];
@@ -38,7 +41,7 @@ function renderGradientDef(id, preset, type, angle, animation) {
38
41
  {
39
42
  attributeName: "stop-color",
40
43
  values: `${color};${nextColor};${color}`,
41
- dur: "3s",
44
+ dur: loop,
42
45
  repeatCount: "indefinite"
43
46
  }
44
47
  ) }, i);
@@ -58,7 +61,7 @@ function renderGradientDef(id, preset, type, angle, animation) {
58
61
  type: "rotate",
59
62
  from: "0 12 12",
60
63
  to: "360 12 12",
61
- dur: "3s",
64
+ dur: loop,
62
65
  repeatCount: "indefinite"
63
66
  },
64
67
  "spin"
@@ -72,7 +75,7 @@ function renderGradientDef(id, preset, type, angle, animation) {
72
75
  {
73
76
  attributeName: "x1",
74
77
  values: "-24;24;-24",
75
- dur: "3s",
78
+ dur: loop,
76
79
  repeatCount: "indefinite"
77
80
  },
78
81
  "shift-x1"
@@ -82,7 +85,7 @@ function renderGradientDef(id, preset, type, angle, animation) {
82
85
  {
83
86
  attributeName: "x2",
84
87
  values: "0;48;0",
85
- dur: "3s",
88
+ dur: loop,
86
89
  repeatCount: "indefinite"
87
90
  },
88
91
  "shift-x2"
@@ -165,7 +168,13 @@ function renderCutoutShape(shape, fill, maskId) {
165
168
  }
166
169
  }
167
170
  function createIcon(options) {
168
- const { displayName, viewBox = "0 0 24 24", paths, textCenter = BOX_CENTER } = options;
171
+ const {
172
+ displayName,
173
+ viewBox = "0 0 24 24",
174
+ paths,
175
+ textCenter = BOX_CENTER,
176
+ animated = false
177
+ } = options;
169
178
  const solidDuotone = (props) => paths.solid({ ...props, accentColor: props.secondaryColor, accentOpacity: 1 });
170
179
  const perfect = {
171
180
  x: options.perfectStackX ?? textCenter[0],
@@ -192,8 +201,13 @@ function createIcon(options) {
192
201
  strokeLinejoin: propLinejoin,
193
202
  animation,
194
203
  animationDuration,
204
+ animationSpeed,
205
+ animationMode = "loop",
206
+ playKey,
207
+ replayOnClick = false,
208
+ reducedMotion = "auto",
195
209
  smilAnimation,
196
- smilDuration = 1,
210
+ smilDuration,
197
211
  smilRepeatCount = "indefinite",
198
212
  smilEasing = "ease-in-out",
199
213
  smilDelay = 0,
@@ -226,6 +240,8 @@ function createIcon(options) {
226
240
  const gradId = useId();
227
241
  const uid = useId();
228
242
  const center = { x: 12, y: 12 };
243
+ const still = useStillMotion(reducedMotion);
244
+ const [replays, setReplays] = useState(0);
229
245
  const colorScheme = useColorScheme();
230
246
  const adaptiveColor = lightColor && darkColor ? colorScheme === "dark" ? darkColor : lightColor : void 0;
231
247
  const primaryColor = adaptiveColor ?? (color ? `var(${SEMANTIC_COLOR_VARS[color]}, ${SEMANTIC_COLOR_DEFAULTS[color]})` : propPrimaryColor);
@@ -253,7 +269,12 @@ function createIcon(options) {
253
269
  const filterId = useId();
254
270
  const isCutout = variant === "cutout";
255
271
  const maskId = useId();
256
- const classes = ["ntd-icon", animation ? `ntd-anim-${animation}` : "", className ?? ""].filter(Boolean).join(" ");
272
+ const classes = [
273
+ "ntd-icon",
274
+ animation && !still ? `ntd-anim-${animation}` : "",
275
+ reducedMotion === "ignore" ? "ntd-motion-ignore" : "",
276
+ className ?? ""
277
+ ].filter(Boolean).join(" ");
257
278
  const transforms = [];
258
279
  if (rotate) {
259
280
  transforms.push(`rotate(${rotate}deg)`);
@@ -269,26 +290,34 @@ function createIcon(options) {
269
290
  }
270
291
  const transformString = transforms.length > 0 ? transforms.join(" ") : void 0;
271
292
  const transitionValue = transition === true ? "transform 0.3s ease" : typeof transition === "string" ? `transform ${transition}` : void 0;
293
+ const timeScale = animationScale(animationSpeed);
272
294
  const animStyle = {
273
295
  // the variable carries the duration to the parts an icon animates inside itself
274
296
  ...animationDuration ? { animationDuration, "--ntd-anim-duration": animationDuration } : {},
297
+ // animations.css multiplies its times by it (default 2 there, the slowdown)
298
+ ...animationSpeed !== void 0 ? { "--ntd-anim-scale": String(timeScale) } : {},
299
+ // a looping animation once (1.3.18): animations.css repeats `var(--ntd-anim-iterations, infinite)`
300
+ ...animationMode === "single" ? { "--ntd-anim-iterations": "1" } : {},
275
301
  ...transformString ? { transform: transformString } : {},
276
302
  ...transformString ? { transformOrigin } : {},
277
303
  ...transitionValue ? { transition: transitionValue } : {},
278
304
  ...style
279
305
  };
280
- const smilAnimationElements = smilAnimation ? generateSmilAnimation(
306
+ const smilGenerated = smilAnimation ? generateSmilAnimation(
281
307
  {
282
308
  type: smilAnimation,
283
- duration: smilDuration,
309
+ // 1 s before 1.3.17 by default; a given smilDuration keeps its length
310
+ duration: (smilDuration ?? 1) * durationScale(smilDuration !== void 0, animationSpeed),
284
311
  repeatCount: smilRepeatCount,
285
312
  easing: smilEasing,
286
313
  delay: smilDelay
287
314
  },
288
315
  center
289
316
  ) : null;
317
+ const smilAnimationElements = still ? stillTree(smilGenerated) : smilGenerated;
290
318
  const needsSmilScaleWrapper = smilAnimation && ["heartbeat", "scale-pulse", "grow", "shrink", "rubber", "pop"].includes(smilAnimation);
291
319
  const rendered = isCutout ? renderCutoutShape(cutoutShape, gradient ? effectivePrimary : resolvedSecondary, maskId) : variant === "sharp" ? sharpenTree(renderer(renderProps)) : variant === "rounded" ? roundTree(renderer(renderProps)) : variant === "wireframe" ? wireTree(renderer(renderProps), effectivePrimary) : renderer(renderProps);
320
+ const paced = animated ? motionTree(rendered, timeScale, still) : rendered;
292
321
  const stacked = renderStackChildren(children, {
293
322
  variant,
294
323
  primaryColor: effectivePrimary,
@@ -300,9 +329,9 @@ function createIcon(options) {
300
329
  textSize
301
330
  });
302
331
  const drawn = stacked ? /* @__PURE__ */ jsxs(Fragment, { children: [
303
- rendered,
332
+ paced,
304
333
  stacked
305
- ] }) : rendered;
334
+ ] }) : paced;
306
335
  const iconContent = variant === "mini" ? /* @__PURE__ */ jsx(
307
336
  "g",
308
337
  {
@@ -327,22 +356,36 @@ function createIcon(options) {
327
356
  "aria-hidden": title ? void 0 : true,
328
357
  "aria-label": title,
329
358
  ...rest,
359
+ onClick: replayOnClick ? (event) => {
360
+ setReplays((n) => n + 1);
361
+ rest.onClick?.(event);
362
+ } : rest.onClick,
330
363
  children: [
331
364
  title && /* @__PURE__ */ jsx("title", { children: title }),
332
- gradient && /* @__PURE__ */ jsx("defs", { children: renderGradientDef(gradId, gradient, gradientType, gradientAngle, gradientAnimation) }),
365
+ gradient && /* @__PURE__ */ jsx("defs", { children: renderGradientDef(
366
+ gradId,
367
+ gradient,
368
+ gradientType,
369
+ gradientAngle,
370
+ still ? void 0 : gradientAnimation,
371
+ timeScale
372
+ ) }),
333
373
  is3d && /* @__PURE__ */ jsx("defs", { children: render3dFilterDef(filterId) }),
334
374
  isCutout && /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("mask", { id: maskId, children: [
335
375
  /* @__PURE__ */ jsx("rect", { x: "0", y: "0", width: "24", height: "24", fill: "white" }),
336
- /* @__PURE__ */ jsx("g", { transform: "translate(12,12) scale(0.8) translate(-12,-12)", children: paths.solid({
337
- ...renderProps,
338
- primaryColor: "black",
339
- secondaryColor: "black",
340
- accentColor: "white",
341
- primaryOpacity: 1,
342
- secondaryOpacity: 1,
343
- accentOpacity: 1,
344
- uid: `${uid}m`
345
- }) })
376
+ /* @__PURE__ */ jsx("g", { transform: "translate(12,12) scale(0.8) translate(-12,-12)", children: (() => {
377
+ const mask = paths.solid({
378
+ ...renderProps,
379
+ primaryColor: "black",
380
+ secondaryColor: "black",
381
+ accentColor: "white",
382
+ primaryOpacity: 1,
383
+ secondaryOpacity: 1,
384
+ accentOpacity: 1,
385
+ uid: `${uid}m`
386
+ });
387
+ return animated ? motionTree(mask, timeScale, still) : mask;
388
+ })() })
346
389
  ] }) }),
347
390
  smilAnimation ? needsSmilScaleWrapper ? /* @__PURE__ */ jsxs(
348
391
  "g",
@@ -359,7 +402,8 @@ function createIcon(options) {
359
402
  smilAnimationElements
360
403
  ] }) : is3d ? /* @__PURE__ */ jsx("g", { filter: `url(#${filterId})`, children: iconContent }) : iconContent
361
404
  ]
362
- }
405
+ },
406
+ `${playKey ?? ""}:${replays}`
363
407
  );
364
408
  }
365
409
  );