@caidentity/testicon 0.0.14 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  React icon components generated from SVG sources.
4
4
 
5
- - Version: 0.0.11
6
- - Icons: 3
5
+ - Version: 0.0.3
6
+ - Icons: 2
7
7
  - Manifest: `iconsManifest` (id, name, component, viewBox, tags)
8
8
 
9
9
  ## Install
@@ -36,8 +36,36 @@ The Icon component extends `SVGProps<SVGSVGElement>` with these additional props
36
36
  - `size?: number | string` - Icon size in pixels (default: 24)
37
37
  - `title?: string` - Accessible title for screen readers
38
38
 
39
+ ### Animation Props
40
+
41
+ - `animated?: boolean` - Enable or disable animation when available
42
+ - `animationDuration?: number` - Override animation duration in milliseconds
43
+ - `animationDelay?: number` - Delay before animation starts in milliseconds
44
+ - `animationLoop?: boolean | number` - `true` for infinite, `false` for one-time, or a number of iterations
45
+ - `animationDirection?: 'normal' | 'reverse' | 'alternate'` - Playback direction
46
+ - `onAnimationStart?: () => void` and `onAnimationEnd?: () => void`
47
+
39
48
  All standard SVG props are supported (className, style, onClick, etc.).
40
49
 
50
+ ### Duotone Props
51
+
52
+ - `duotone1?: string`, `duotone2?: string`, `duotone3?: string`
53
+ - Apply when the icon declares layered content via `duotoneLayers` in the registry.
54
+ - Mapped to CSS variables `--duotone1/2/3` for layer-targeted colors.
55
+ - When any duotone color is provided and the icon reports layered content, `fill` is omitted so layered colors determine appearance.
56
+ - Otherwise, `fill` falls back to `currentColor` or the explicit `fill` prop.
57
+
58
+ Example:
59
+
60
+ ```tsx
61
+ <Icon
62
+ name="logo"
63
+ duotone1="#4F46E5" // primary layer
64
+ duotone2="#A78BFA" // secondary layer
65
+ size={32}
66
+ />
67
+ ```
68
+
41
69
  ## Theming
42
70
 
43
71
  Icons use `fill="currentColor"` by default, so they inherit the text color of their container:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caidentity/testicon",
3
- "version": "0.0.14",
3
+ "version": "0.2.0",
4
4
  "description": "Icons for @caidentity/testicon",
5
5
  "license": "MIT",
6
6
  "keywords": [
package/src/Icon.tsx CHANGED
@@ -1,18 +1,32 @@
1
- import React, { SVGProps } from 'react'
1
+ import React, { SVGProps, useEffect } from 'react'
2
2
 
3
3
  export interface IconProps extends SVGProps<SVGSVGElement> {
4
4
  name: string
5
5
  size?: number | string
6
6
  title?: string
7
+
8
+ // Single color (backward compatible)
9
+ fill?: string
10
+
11
+ // Duotone colors (new)
12
+ duotone1?: string // Primary duotone layer color
13
+ duotone2?: string // Secondary duotone layer color
14
+ duotone3?: string // Tertiary duotone layer color (optional)
15
+
16
+ // Animation controls (new). When an icon has animation defaults in the registry,
17
+ // these props can override or toggle playback intent; consumers can pair with CSS.
18
+ animated?: boolean
19
+ animationDuration?: number
20
+ animationDelay?: number
21
+ animationLoop?: boolean | number
22
+ animationDirection?: 'normal' | 'reverse' | 'alternate'
23
+ onAnimationStart?: () => void
24
+ onAnimationEnd?: () => void
7
25
  }
8
26
 
9
27
  const iconRegistry = {
10
28
  "frame": {
11
- "svg": "<g transform=\"translate(-1486, -235)\"><path d=\"M 8 -90 L 422 -90 Q 430 -90 430 -82 L 430 172 Q 430 180 422 180 L 8 180 Q 0 180 0 172 L 0 -82 Q 0 -90 8 -90 Z\" fill=\"none\" fill-opacity=\"0\" stroke=\"#000000\" stroke-opacity=\"1\" strokeWidth=\"2\" strokeLinecap=\"butt\" strokeLinejoin=\"miter\" transform=\"translate(747,440)\" />\n<path d=\"M 0 0 L 0 153 L 186 153 L 186 -7\" fill=\"none\" fill-opacity=\"0\" stroke=\"#000000\" stroke-opacity=\"1\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" transform=\"translate(1761,552)\" />\n<path d=\"M 8 -90 L 422 -90 Q 430 -90 430 -82 L 430 172 Q 430 180 422 180 L 8 180 Q 0 180 0 172 L 0 -82 Q 0 -90 8 -90 Z\" fill=\"none\" fill-opacity=\"0\" stroke=\"#000000\" stroke-opacity=\"1\" strokeWidth=\"2\" strokeLinecap=\"butt\" strokeLinejoin=\"miter\" transform=\"translate(1611,438)\" /></g>",
12
- "viewBox": "0 0 800 600"
13
- },
14
- "frame-b0y6eye-1765734231799": {
15
- "svg": "<g transform=\"translate(-2349, -237)\"><path d=\"M 8 -90 L 422 -90 Q 430 -90 430 -82 L 430 172 Q 430 180 422 180 L 8 180 Q 0 180 0 172 L 0 -82 Q 0 -90 8 -90 Z\" fill=\"none\" fill-opacity=\"0\" stroke=\"#000000\" stroke-opacity=\"1\" strokeWidth=\"2\" strokeLinecap=\"butt\" strokeLinejoin=\"miter\" transform=\"translate(747,440)\" />\n<path d=\"M 4 0 L 4 0 Q 8 0 8 4 L 8 4 Q 8 8 4 8 L 4 8 Q 0 8 0 4 L 0 4 Q 0 0 4 0 Z\" fill=\"none\" fill-opacity=\"0\" stroke=\"#ffcfcfff\" stroke-opacity=\"1\" strokeWidth=\"2\" strokeLinecap=\"butt\" strokeLinejoin=\"miter\" transform=\"translate(1031,671)\" />\n<path d=\"M 0 0 L 0 153 L 186 153 L 186 -7\" fill=\"none\" fill-opacity=\"0\" stroke=\"#000000\" stroke-opacity=\"1\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" transform=\"translate(1761,552)\" />\n<path d=\"M 8 -90 L 422 -90 Q 430 -90 430 -82 L 430 172 Q 430 180 422 180 L 8 180 Q 0 180 0 172 L 0 -82 Q 0 -90 8 -90 Z\" fill=\"none\" fill-opacity=\"0\" stroke=\"#000000\" stroke-opacity=\"1\" strokeWidth=\"2\" strokeLinecap=\"butt\" strokeLinejoin=\"miter\" transform=\"translate(1611,438)\" />\n<path d=\"M 0 0 L 134 547 L 332 411 L 391 20 L 160 172\" fill=\"none\" fill-opacity=\"0\" stroke=\"#000000\" stroke-opacity=\"1\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" transform=\"translate(2424,313)\" />\n<path d=\"M 0 0 L -30 174 L 161 183 L 196 110 L 112 25 L -11 190 L 17 15 L 241 5 L -42 -119\" fill=\"none\" fill-opacity=\"0\" stroke=\"#000000\" stroke-opacity=\"1\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" transform=\"translate(2661,291)\" /></g>",
29
+ "svg": "<g transform=\"translate(-1490, -237)\"><path d=\"M 8 -90 L 422 -90 Q 430 -90 430 -82 L 430 172 Q 430 180 422 180 L 8 180 Q 0 180 0 172 L 0 -82 Q 0 -90 8 -90 Z\" fill=\"none\" fill-opacity=\"0\" stroke=\"#000000\" stroke-opacity=\"1\" strokeWidth=\"2\" strokeLinecap=\"butt\" strokeLinejoin=\"miter\" transform=\"translate(746,441)\" />\n<path d=\"M 4 0 L 4 0 Q 8 0 8 4 L 8 4 Q 8 8 4 8 L 4 8 Q 0 8 0 4 L 0 4 Q 0 0 4 0 Z\" fill=\"none\" fill-opacity=\"0\" stroke=\"#ffcfcfff\" stroke-opacity=\"1\" strokeWidth=\"2\" strokeLinecap=\"butt\" strokeLinejoin=\"miter\" transform=\"translate(1031,672)\" />\n<path d=\"M 0 0 L 0 153 L 186 153 L 186 -7\" fill=\"none\" fill-opacity=\"0\" stroke=\"#000000\" stroke-opacity=\"1\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" transform=\"translate(1765,554)\" /></g>",
16
30
  "viewBox": "0 0 800 600"
17
31
  }
18
32
  }
@@ -22,6 +36,17 @@ export const Icon: React.FC<IconProps> = ({
22
36
  size = 24,
23
37
  title,
24
38
  className,
39
+ fill,
40
+ duotone1,
41
+ duotone2,
42
+ duotone3,
43
+ animated,
44
+ animationDuration,
45
+ animationDelay,
46
+ animationLoop,
47
+ animationDirection,
48
+ onAnimationStart,
49
+ onAnimationEnd,
25
50
  ...props
26
51
  }) => {
27
52
  const iconData = iconRegistry[name.toLowerCase()]
@@ -31,19 +56,68 @@ export const Icon: React.FC<IconProps> = ({
31
56
  return null
32
57
  }
33
58
 
59
+ // Apply duotone colors to CSS variables for layer targeting
60
+ const duotoneVars: Record<string, string> = {}
61
+ if (iconData.duotoneLayers && iconData.duotoneLayers >= 2 && duotone1) duotoneVars['--duotone1'] = duotone1
62
+ if (iconData.duotoneLayers && iconData.duotoneLayers >= 2 && duotone2) duotoneVars['--duotone2'] = duotone2
63
+ if (iconData.duotoneLayers && iconData.duotoneLayers >= 3 && duotone3) duotoneVars['--duotone3'] = duotone3
64
+
65
+ const styleWithDuotone = {
66
+ ...props.style,
67
+ ...duotoneVars
68
+ }
69
+
70
+ // Use duotone colors if available, otherwise fall back to single color
71
+ // Note: when duotone is present, we intentionally omit 'fill' to let layered colors drive
72
+ // the appearance; otherwise we use 'currentColor' or 'fill' prop.
73
+ const hasDuotone = iconData.duotoneLayers && (duotone1 || duotone2 || duotone3)
74
+ const svgFill = hasDuotone ? undefined : (fill || 'currentColor')
75
+
76
+ // Derive animation intent from registry defaults and props
77
+ const meta = iconData.animation || null
78
+ const isAnimated = typeof animated === 'boolean' ? animated : !!(meta && meta.autoplay)
79
+ const durationMs = typeof animationDuration === 'number' ? animationDuration : (meta && typeof meta.duration === 'number' ? meta.duration : undefined)
80
+ const delayMs = typeof animationDelay === 'number' ? animationDelay : undefined
81
+ const iterCount = typeof animationLoop === 'number' ? String(animationLoop) : (animationLoop === true ? 'infinite' : (animationLoop === false ? '1' : (meta && meta.loop ? 'infinite' : undefined)))
82
+ const dir = animationDirection ?? (meta && meta.direction ? meta.direction : undefined)
83
+
84
+ // Merge animation style on top of duotone variables and incoming style.
85
+ // Consumers can add 'animationName' via props.style or class to bind keyframes.
86
+ const styleWithAnimation: React.CSSProperties = {
87
+ ...styleWithDuotone,
88
+ ...(durationMs !== undefined ? { animationDuration: String(durationMs) + 'ms' } : {}),
89
+ ...(delayMs !== undefined ? { animationDelay: String(delayMs) + 'ms' } : {}),
90
+ ...(iterCount !== undefined ? { animationIterationCount: iterCount } : {}),
91
+ ...(dir !== undefined ? { animationDirection: dir } : {}),
92
+ animationPlayState: isAnimated ? 'running' : 'paused'
93
+ }
94
+
95
+ useEffect(() => {
96
+ if (isAnimated) onAnimationStart?.()
97
+ else onAnimationEnd?.()
98
+ }, [isAnimated])
99
+
34
100
  return (
35
101
  <svg
36
102
  width={size}
37
103
  height={size}
38
104
  viewBox={iconData.viewBox}
39
- fill="currentColor"
105
+ fill={svgFill}
40
106
  className={className}
107
+ style={styleWithAnimation}
108
+ data-animated={isAnimated ? '1' : undefined}
109
+ data-animation-duration={durationMs}
110
+ data-animation-delay={delayMs}
111
+ data-animation-iter={iterCount}
112
+ data-animation-direction={dir}
41
113
  aria-hidden={title ? undefined : true}
42
114
  aria-labelledby={title ? `${name}-title` : undefined}
43
115
  role="img"
44
116
  {...props}
45
117
  >
46
- {title && <title id={`${name}-title`}>{title}</title>}
118
+ {title && <title id={
119
+
120
+ }>{title}</title>}
47
121
  <g dangerouslySetInnerHTML={{ __html: iconData.svg }} />
48
122
  </svg>
49
123
  )
@@ -5,7 +5,10 @@
5
5
  "component": "frame",
6
6
  "width": 800,
7
7
  "height": 600,
8
- "viewBox": "0 0 800 600"
8
+ "viewBox": "0 0 800 600",
9
+ "animation": {
10
+ "autoplay": false
11
+ }
9
12
  },
10
13
  {
11
14
  "id": "frame-t8zz5m1-1765658197783",
@@ -14,13 +17,5 @@
14
17
  "width": 800,
15
18
  "height": 600,
16
19
  "viewBox": "0 0 800 600"
17
- },
18
- {
19
- "id": "frame-b0y6eye-1765734231799",
20
- "name": "frame-b0y6eye-1765734231799",
21
- "component": "frame-b0y6eye-1765734231799",
22
- "width": 800,
23
- "height": 600,
24
- "viewBox": "0 0 800 600"
25
20
  }
26
21
  ]
@@ -7,6 +7,11 @@ export type IconManifestEntry = {
7
7
  height: number
8
8
  viewBox: string
9
9
  tags?: string[]
10
+ animation?: {
11
+ duration?: number
12
+ loop?: boolean | number
13
+ autoplay?: boolean
14
+ }
10
15
  }
11
16
 
12
17
  export const iconsManifest: IconManifestEntry[] = [
@@ -16,7 +21,10 @@ export const iconsManifest: IconManifestEntry[] = [
16
21
  "component": "frame",
17
22
  "width": 800,
18
23
  "height": 600,
19
- "viewBox": "0 0 800 600"
24
+ "viewBox": "0 0 800 600",
25
+ "animation": {
26
+ "autoplay": false
27
+ }
20
28
  },
21
29
  {
22
30
  "id": "frame-t8zz5m1-1765658197783",
@@ -25,13 +33,5 @@ export const iconsManifest: IconManifestEntry[] = [
25
33
  "width": 800,
26
34
  "height": 600,
27
35
  "viewBox": "0 0 800 600"
28
- },
29
- {
30
- "id": "frame-b0y6eye-1765734231799",
31
- "name": "frame-b0y6eye-1765734231799",
32
- "component": "frame-b0y6eye-1765734231799",
33
- "width": 800,
34
- "height": 600,
35
- "viewBox": "0 0 800 600"
36
36
  }
37
37
  ] as IconManifestEntry[]
package/src/icons.tsx CHANGED
@@ -1,8 +1,7 @@
1
1
  // Icon registry utilities
2
2
  export const iconNames = [
3
3
  "frame",
4
- "frame",
5
- "frame-b0y6eye-1765734231799"
4
+ "frame"
6
5
  ] as const
7
6
 
8
7
  export type IconName = typeof iconNames[number]