@caidentity/testicon 0.3.0 → 1.0.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
@@ -1,21 +1,104 @@
1
1
  # @caidentity/testicon
2
2
 
3
- React icon package generated from your Design Library objects.
3
+ React icon components generated from SVG sources.
4
4
 
5
5
  - Version: 0.0.3
6
- - Install: `npm install @caidentity/testicon`
6
+ - Icons: 2
7
+ - Manifest: `iconsManifest` (id, name, component, viewBox, tags)
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @caidentity/testicon
13
+ ```
7
14
 
8
15
  ## Usage
9
16
 
10
17
  ```tsx
11
- import { IconBase } from '@caidentity/testicon'
12
- import { Home } from '@caidentity/testicon'
18
+ import { Icon } from '@caidentity/testicon'
13
19
 
14
20
  export function Example() {
15
21
  return (
16
22
  <div style={{ display: 'flex', gap: 12 }}>
17
- <Home size={24} title="Home" />
23
+ <Icon name="home" size={24} />
24
+ <Icon name="search" size={20} />
25
+ <Icon name="user" size={16} className="user-icon" />
18
26
  </div>
19
27
  )
20
28
  }
21
29
  ```
30
+
31
+ ## Component Props
32
+
33
+ The Icon component extends `SVGProps<SVGSVGElement>` with these additional props:
34
+
35
+ - `name: string` - **Required.** Name of the icon to render (case-insensitive)
36
+ - `size?: number | string` - Icon size in pixels (default: 24)
37
+ - `title?: string` - Accessible title for screen readers
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
+
48
+ All standard SVG props are supported (className, style, onClick, etc.).
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
+
69
+ ## Theming
70
+
71
+ Icons use `fill="currentColor"` by default, so they inherit the text color of their container:
72
+
73
+ ```tsx
74
+ <div style={{ color: 'blue' }}>
75
+ <Icon name="home" /> {/* Will be blue */}
76
+ </div>
77
+
78
+ <Icon name="home" style={{ color: 'red' }} /> {/* Override with style */}
79
+ ```
80
+
81
+ ## Available Icons
82
+
83
+ ```tsx
84
+ import { iconsManifest } from '@caidentity/testicon'
85
+
86
+ // Get all available icons
87
+ console.log(iconsManifest)
88
+
89
+ // Each icon has: { id, name, component, width, height, viewBox, tags? }
90
+ ```
91
+
92
+ ## TypeScript Support
93
+
94
+ ```tsx
95
+ import { Icon, IconName } from '@caidentity/testicon'
96
+
97
+ // Type-safe icon names
98
+ function MyComponent() {
99
+ return <Icon name="home" size={24} />
100
+ }
101
+
102
+ // IconName is a union of all available icon names
103
+ const iconName: IconName = 'home'
104
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caidentity/testicon",
3
- "version": "0.3.0",
3
+ "version": "1.0.0",
4
4
  "description": "Icons for @caidentity/testicon",
5
5
  "license": "MIT",
6
6
  "keywords": [
package/src/Icon.tsx CHANGED
@@ -1,14 +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": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"800\" height=\"600\" viewBox=\"0 0 800 600\">\n<metadata><![CDATA[{\"pageId\":\"page-1\",\"viewMode\":\"frames\",\"frame\":{\"id\":\"frame-t8zz5m1-1765658197783\",\"name\":\"Frame\",\"x\":1487,\"y\":234,\"w\":800,\"h\":600,\"overflow\":\"hidden\"},\"shapeIds\":[\"path_wyhsf97\",\"path_ibwab03\"]}]]></metadata>\n<g transform=\"translate(-1487, -234)\"><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(1762,551)\" /></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>",
12
30
  "viewBox": "0 0 800 600"
13
31
  }
14
32
  }
@@ -18,6 +36,17 @@ export const Icon: React.FC<IconProps> = ({
18
36
  size = 24,
19
37
  title,
20
38
  className,
39
+ fill,
40
+ duotone1,
41
+ duotone2,
42
+ duotone3,
43
+ animated,
44
+ animationDuration,
45
+ animationDelay,
46
+ animationLoop,
47
+ animationDirection,
48
+ onAnimationStart,
49
+ onAnimationEnd,
21
50
  ...props
22
51
  }) => {
23
52
  const iconData = iconRegistry[name.toLowerCase()]
@@ -27,19 +56,68 @@ export const Icon: React.FC<IconProps> = ({
27
56
  return null
28
57
  }
29
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
+
30
100
  return (
31
101
  <svg
32
102
  width={size}
33
103
  height={size}
34
104
  viewBox={iconData.viewBox}
35
- fill="currentColor"
105
+ fill={svgFill}
36
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}
37
113
  aria-hidden={title ? undefined : true}
38
114
  aria-labelledby={title ? `${name}-title` : undefined}
39
115
  role="img"
40
116
  {...props}
41
117
  >
42
- {title && <title id={`${name}-title`}>{title}</title>}
118
+ {title && <title id={
119
+
120
+ }>{title}</title>}
43
121
  <g dangerouslySetInnerHTML={{ __html: iconData.svg }} />
44
122
  </svg>
45
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",
@@ -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",
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { Icon } from './Icon'
2
+ export { IconName } from './icons'
2
3
  export { iconsManifest } from './icons/manifest'