@aiready/components 0.13.12 → 0.13.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/components",
3
- "version": "0.13.12",
3
+ "version": "0.13.14",
4
4
  "description": "Unified shared components library (UI, charts, hooks, utilities) for AIReady",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -128,7 +128,7 @@
128
128
  "framer-motion": "^12.35.0",
129
129
  "lucide-react": "^0.577.0",
130
130
  "tailwind-merge": "^3.0.0",
131
- "@aiready/core": "0.23.13"
131
+ "@aiready/core": "0.23.15"
132
132
  },
133
133
  "devDependencies": {
134
134
  "@testing-library/jest-dom": "^6.6.5",
@@ -106,6 +106,28 @@ export interface ForceDirectedGraphProps {
106
106
  onLayoutChange?: (layout: LayoutType) => void;
107
107
  }
108
108
 
109
+ /**
110
+ * Helper functions for graph node manipulation.
111
+ * Extracted to reduce semantic duplicate patterns.
112
+ */
113
+
114
+ /** Pins a node to its current position (sets fx/fy to current x/y) */
115
+ function pinNode(node: GraphNode): void {
116
+ node.fx = node.x;
117
+ node.fy = node.y;
118
+ }
119
+
120
+ /** Unpins a node (sets fx/fy to null) */
121
+ function unpinNode(node: GraphNode): void {
122
+ node.fx = null;
123
+ node.fy = null;
124
+ }
125
+
126
+ /** Unpins all nodes - helper for bulk unpin operations */
127
+ function unpinAllNodes(nodes: GraphNode[]): void {
128
+ nodes.forEach(unpinNode);
129
+ }
130
+
109
131
  /**
110
132
  * An interactive Force-Directed Graph component using D3.js for physics and React for rendering.
111
133
  *
@@ -218,26 +240,19 @@ export const ForceDirectedGraph = forwardRef<
218
240
  pinAll: () => {
219
241
  const newPinned = new Set<string>();
220
242
  nodes.forEach((node) => {
221
- node.fx = node.x;
222
- node.fy = node.y;
243
+ pinNode(node);
223
244
  newPinned.add(node.id);
224
245
  });
225
246
  setPinnedNodes(newPinned);
226
247
  restart();
227
248
  },
228
249
  unpinAll: () => {
229
- nodes.forEach((node) => {
230
- node.fx = null;
231
- node.fy = null;
232
- });
250
+ unpinAllNodes(nodes);
233
251
  setPinnedNodes(new Set());
234
252
  restart();
235
253
  },
236
254
  resetLayout: () => {
237
- nodes.forEach((node) => {
238
- node.fx = null;
239
- node.fy = null;
240
- });
255
+ unpinAllNodes(nodes);
241
256
  setPinnedNodes(new Set());
242
257
  restart();
243
258
  },
@@ -352,8 +367,7 @@ export const ForceDirectedGraph = forwardRef<
352
367
  event.stopPropagation();
353
368
  dragActiveRef.current = true;
354
369
  dragNodeRef.current = node;
355
- node.fx = node.x;
356
- node.fy = node.y;
370
+ pinNode(node);
357
371
  setPinnedNodes((prev) => new Set([...prev, node.id]));
358
372
  stop();
359
373
  },
@@ -378,8 +392,7 @@ export const ForceDirectedGraph = forwardRef<
378
392
  if (!event.active) restart();
379
393
  dragActiveRef.current = true;
380
394
  dragNodeRef.current = node;
381
- node.fx = node.x;
382
- node.fy = node.y;
395
+ pinNode(node);
383
396
  setPinnedNodes((prev) => new Set([...prev, node.id]));
384
397
  })
385
398
  .on('drag', (event: any) => {
@@ -416,12 +429,10 @@ export const ForceDirectedGraph = forwardRef<
416
429
  event.stopPropagation();
417
430
  if (!enableDrag) return;
418
431
  if (node.fx === null || node.fx === undefined) {
419
- node.fx = node.x;
420
- node.fy = node.y;
432
+ pinNode(node);
421
433
  setPinnedNodes((prev) => new Set([...prev, node.id]));
422
434
  } else {
423
- node.fx = null;
424
- node.fy = null;
435
+ unpinNode(node);
425
436
  setPinnedNodes((prev) => {
426
437
  const next = new Set(prev);
427
438
  next.delete(node.id);
@@ -440,10 +451,7 @@ export const ForceDirectedGraph = forwardRef<
440
451
  height={height}
441
452
  className={cn('bg-white dark:bg-gray-900', className)}
442
453
  onDoubleClick={() => {
443
- nodes.forEach((n) => {
444
- n.fx = null;
445
- n.fy = null;
446
- });
454
+ unpinAllNodes(nodes);
447
455
  setPinnedNodes(new Set());
448
456
  restart();
449
457
  }}
@@ -10,6 +10,37 @@ export interface IconBaseProps extends React.SVGProps<SVGSVGElement> {
10
10
  viewBox?: string;
11
11
  }
12
12
 
13
+ /**
14
+ * Common stroke props for line-based icons.
15
+ * Reduces duplication across icon components.
16
+ */
17
+ export const STROKE_ICON_PROPS = {
18
+ strokeWidth: 2,
19
+ strokeLinecap: 'round' as const,
20
+ strokeLinejoin: 'round' as const,
21
+ };
22
+
23
+ /**
24
+ * Creates an icon component with common stroke props pre-applied.
25
+ * Reduces semantic duplicate patterns in icon definitions.
26
+ *
27
+ * @param _name - Name of the icon (reserved for debugging/future use)
28
+ * @param children - SVG child elements (paths, circles, etc.)
29
+ * @param additionalProps - Additional props to pass to IconBase
30
+ * @returns Icon component with standard stroke styling
31
+ */
32
+ export function createStrokeIcon(
33
+ _name: string,
34
+ children: React.ReactNode,
35
+ additionalProps?: Partial<IconBaseProps>
36
+ ): React.ReactElement {
37
+ return (
38
+ <IconBase {...STROKE_ICON_PROPS} {...additionalProps}>
39
+ {children}
40
+ </IconBase>
41
+ );
42
+ }
43
+
13
44
  /**
14
45
  * A shared base component for all icons to reduce code duplication
15
46
  * and improve AI signal clarity.
@@ -1,60 +1,49 @@
1
- import { IconBase, IconBaseProps } from './IconBase';
1
+ import { IconBase, IconBaseProps, createStrokeIcon } from './IconBase';
2
2
 
3
+ // Icons using common stroke props - uses factory to reduce duplication
3
4
  export function AlertCircleIcon(props: IconBaseProps) {
4
- return (
5
- <IconBase
6
- strokeWidth="2"
7
- strokeLinecap="round"
8
- strokeLinejoin="round"
9
- {...props}
10
- >
5
+ return createStrokeIcon(
6
+ 'AlertCircle',
7
+ <>
11
8
  <circle cx="12" cy="12" r="10" />
12
9
  <line x1="12" y1="8" x2="12" y2="12" />
13
10
  <line x1="12" y1="16" x2="12.01" y2="16" />
14
- </IconBase>
11
+ </>,
12
+ props
15
13
  );
16
14
  }
17
15
 
18
16
  export function AlertTriangleIcon(props: IconBaseProps) {
19
- return (
20
- <IconBase
21
- strokeWidth="2"
22
- strokeLinecap="round"
23
- strokeLinejoin="round"
24
- {...props}
25
- >
17
+ return createStrokeIcon(
18
+ 'AlertTriangle',
19
+ <>
26
20
  <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
27
21
  <line x1="12" y1="9" x2="12" y2="13" />
28
22
  <line x1="12" y1="17" x2="12.01" y2="17" />
29
- </IconBase>
23
+ </>,
24
+ props
30
25
  );
31
26
  }
32
27
 
33
28
  export function ArrowRightIcon(props: IconBaseProps) {
34
- return (
35
- <IconBase
36
- strokeWidth="2"
37
- strokeLinecap="round"
38
- strokeLinejoin="round"
39
- {...props}
40
- >
29
+ return createStrokeIcon(
30
+ 'ArrowRight',
31
+ <>
41
32
  <line x1="5" y1="12" x2="19" y2="12" />
42
33
  <polyline points="12 5 19 12 12 19" />
43
- </IconBase>
34
+ </>,
35
+ props
44
36
  );
45
37
  }
46
38
 
47
39
  export function BrainIcon(props: IconBaseProps) {
48
- return (
49
- <IconBase
50
- strokeWidth="2"
51
- strokeLinecap="round"
52
- strokeLinejoin="round"
53
- {...props}
54
- >
40
+ return createStrokeIcon(
41
+ 'Brain',
42
+ <>
55
43
  <path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.74-3.41A2.5 2.5 0 0 1 2 14c0-1.5 1-2 1-2s-1-.5-1-2a2.5 2.5 0 0 1 2.3-2.48A2.5 2.5 0 0 1 7 5.5a2.5 2.5 0 0 1 2.5-3.5z" />
56
44
  <path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.74-3.41A2.5 2.5 0 0 0 22 14c0-1.5-1-2-1-2s1-.5 1-2a2.5 2.5 0 0 0-2.3-2.48A2.5 2.5 0 0 0 17 5.5a2.5 2.5 0 0 0-2.5-3.5z" />
57
- </IconBase>
45
+ </>,
46
+ props
58
47
  );
59
48
  }
60
49
 
@@ -70,88 +59,68 @@ export function ChartIcon(props: IconBaseProps) {
70
59
  }
71
60
 
72
61
  export function ClockIcon(props: IconBaseProps) {
73
- return (
74
- <IconBase
75
- strokeWidth="2"
76
- strokeLinecap="round"
77
- strokeLinejoin="round"
78
- {...props}
79
- >
62
+ return createStrokeIcon(
63
+ 'Clock',
64
+ <>
80
65
  <circle cx="12" cy="12" r="10" />
81
66
  <polyline points="12 6 12 12 16 14" />
82
- </IconBase>
67
+ </>,
68
+ props
83
69
  );
84
70
  }
85
71
 
86
72
  export function FileIcon(props: IconBaseProps) {
87
- return (
88
- <IconBase
89
- strokeWidth="2"
90
- strokeLinecap="round"
91
- strokeLinejoin="round"
92
- {...props}
93
- >
73
+ return createStrokeIcon(
74
+ 'File',
75
+ <>
94
76
  <path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z" />
95
77
  <polyline points="14 2 14 8 20 8" />
96
- </IconBase>
78
+ </>,
79
+ props
97
80
  );
98
81
  }
99
82
 
100
83
  export function HammerIcon(props: IconBaseProps) {
101
- return (
102
- <IconBase
103
- strokeWidth="2"
104
- strokeLinecap="round"
105
- strokeLinejoin="round"
106
- {...props}
107
- >
84
+ return createStrokeIcon(
85
+ 'Hammer',
86
+ <>
108
87
  <path d="M18.42 13.59L7.46 2.63a1 1 0 0 0-1.42 0l-4.7 4.7a1 1 0 0 0 0 1.42L11 18.23l1.07-1.07-1.41-1.41 1.42-1.42 1.41 1.41 1.41-1.41-1.41-1.41 1.42-1.42 1.41 1.41 2-2z" />
109
88
  <path d="M13 18l6 6" />
110
- </IconBase>
89
+ </>,
90
+ props
111
91
  );
112
92
  }
113
93
 
114
94
  export function InfoIcon(props: IconBaseProps) {
115
- return (
116
- <IconBase
117
- strokeWidth="2"
118
- strokeLinecap="round"
119
- strokeLinejoin="round"
120
- {...props}
121
- >
95
+ return createStrokeIcon(
96
+ 'Info',
97
+ <>
122
98
  <circle cx="12" cy="12" r="10" />
123
99
  <line x1="12" y1="16" x2="12" y2="12" />
124
100
  <line x1="12" y1="8" x2="12.01" y2="8" />
125
- </IconBase>
101
+ </>,
102
+ props
126
103
  );
127
104
  }
128
105
 
129
106
  export function PlayIcon(props: IconBaseProps) {
130
- return (
131
- <IconBase
132
- strokeWidth="2"
133
- strokeLinecap="round"
134
- strokeLinejoin="round"
135
- {...props}
136
- >
137
- <polygon points="5 3 19 12 5 21 5 3" />
138
- </IconBase>
107
+ return createStrokeIcon(
108
+ 'Play',
109
+ <polygon points="5 3 19 12 5 21 5 3" />,
110
+ props
139
111
  );
140
112
  }
141
113
 
142
114
  export function RefreshCwIcon(props: IconBaseProps) {
143
- return (
144
- <IconBase
145
- strokeWidth="2"
146
- strokeLinecap="round"
147
- strokeLinejoin="round"
148
- {...props}
149
- >
115
+ return createStrokeIcon(
116
+ 'RefreshCw',
117
+ <>
150
118
  <path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" />
151
119
  <path d="M21 3v5h-5" />
152
120
  <path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" />
153
121
  <path d="M3 21v-5h5" />
154
- </IconBase>
122
+ </>,
123
+ props
155
124
  );
156
125
  }
157
126
 
@@ -185,58 +154,44 @@ export function RocketIcon(props: IconBaseProps) {
185
154
  }
186
155
 
187
156
  export function SaveIcon(props: IconBaseProps) {
188
- return (
189
- <IconBase
190
- strokeWidth="2"
191
- strokeLinecap="round"
192
- strokeLinejoin="round"
193
- {...props}
194
- >
157
+ return createStrokeIcon(
158
+ 'Save',
159
+ <>
195
160
  <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
196
161
  <polyline points="17 21 17 13 7 13 7 21" />
197
162
  <polyline points="7 3 7 8 15 8" />
198
- </IconBase>
163
+ </>,
164
+ props
199
165
  );
200
166
  }
201
167
 
202
168
  export function SettingsIcon(props: IconBaseProps) {
203
- return (
204
- <IconBase
205
- strokeWidth="2"
206
- strokeLinecap="round"
207
- strokeLinejoin="round"
208
- {...props}
209
- >
169
+ return createStrokeIcon(
170
+ 'Settings',
171
+ <>
210
172
  <path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.38a2 2 0 0 0-.73-2.73l-.15-.1a2 2 0 0 1-1-1.72v-.51a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" />
211
173
  <circle cx="12" cy="12" r="3" />
212
- </IconBase>
174
+ </>,
175
+ props
213
176
  );
214
177
  }
215
178
 
216
179
  export function ShieldCheckIcon(props: IconBaseProps) {
217
- return (
218
- <IconBase
219
- strokeWidth="2"
220
- strokeLinecap="round"
221
- strokeLinejoin="round"
222
- {...props}
223
- >
180
+ return createStrokeIcon(
181
+ 'ShieldCheck',
182
+ <>
224
183
  <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
225
184
  <path d="M9 12l2 2 4-4" />
226
- </IconBase>
185
+ </>,
186
+ props
227
187
  );
228
188
  }
229
189
 
230
190
  export function ShieldIcon(props: IconBaseProps) {
231
- return (
232
- <IconBase
233
- strokeWidth="2"
234
- strokeLinecap="round"
235
- strokeLinejoin="round"
236
- {...props}
237
- >
238
- <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
239
- </IconBase>
191
+ return createStrokeIcon(
192
+ 'Shield',
193
+ <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />,
194
+ props
240
195
  );
241
196
  }
242
197
 
@@ -255,87 +210,67 @@ export function TargetIcon(props: IconBaseProps) {
255
210
  }
256
211
 
257
212
  export function TerminalIcon(props: IconBaseProps) {
258
- return (
259
- <IconBase
260
- strokeWidth="2"
261
- strokeLinecap="round"
262
- strokeLinejoin="round"
263
- {...props}
264
- >
213
+ return createStrokeIcon(
214
+ 'Terminal',
215
+ <>
265
216
  <polyline points="4 17 10 11 4 5" />
266
217
  <line x1="12" y1="19" x2="20" y2="19" />
267
- </IconBase>
218
+ </>,
219
+ props
268
220
  );
269
221
  }
270
222
 
271
223
  export function TrashIcon(props: IconBaseProps) {
272
- return (
273
- <IconBase
274
- strokeWidth="2"
275
- strokeLinecap="round"
276
- strokeLinejoin="round"
277
- {...props}
278
- >
224
+ return createStrokeIcon(
225
+ 'Trash',
226
+ <>
279
227
  <polyline points="3 6 5 6 21 6" />
280
228
  <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
281
- </IconBase>
229
+ </>,
230
+ props
282
231
  );
283
232
  }
284
233
 
285
234
  export function TrendingUpIcon(props: IconBaseProps) {
286
- return (
287
- <IconBase
288
- strokeWidth="2"
289
- strokeLinecap="round"
290
- strokeLinejoin="round"
291
- {...props}
292
- >
235
+ return createStrokeIcon(
236
+ 'TrendingUp',
237
+ <>
293
238
  <polyline points="23 6 13.5 15.5 8.5 10.5 1 18" />
294
239
  <polyline points="17 6 23 6 23 12" />
295
- </IconBase>
240
+ </>,
241
+ props
296
242
  );
297
243
  }
298
244
 
299
245
  export function UploadIcon(props: IconBaseProps) {
300
- return (
301
- <IconBase
302
- strokeWidth="2"
303
- strokeLinecap="round"
304
- strokeLinejoin="round"
305
- {...props}
306
- >
246
+ return createStrokeIcon(
247
+ 'Upload',
248
+ <>
307
249
  <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v4" />
308
250
  <polyline points="17 8 12 3 7 8" />
309
251
  <line x1="12" y1="3" x2="12" y2="15" />
310
- </IconBase>
252
+ </>,
253
+ props
311
254
  );
312
255
  }
313
256
 
314
257
  export function WalletIcon(props: IconBaseProps) {
315
- return (
316
- <IconBase
317
- strokeWidth="2"
318
- strokeLinecap="round"
319
- strokeLinejoin="round"
320
- {...props}
321
- >
258
+ return createStrokeIcon(
259
+ 'Wallet',
260
+ <>
322
261
  <path d="M21 12V7a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-1" />
323
262
  <path d="M16 12h5" />
324
263
  <circle cx="16" cy="12" r="1" />
325
- </IconBase>
264
+ </>,
265
+ props
326
266
  );
327
267
  }
328
268
 
329
269
  export function ZapIcon(props: IconBaseProps) {
330
- return (
331
- <IconBase
332
- strokeWidth="2"
333
- strokeLinecap="round"
334
- strokeLinejoin="round"
335
- {...props}
336
- >
337
- <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
338
- </IconBase>
270
+ return createStrokeIcon(
271
+ 'Zap',
272
+ <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />,
273
+ props
339
274
  );
340
275
  }
341
276
 
@@ -0,0 +1,31 @@
1
+ import { SimulationNode } from './simulation-types';
2
+
3
+ /**
4
+ * Stabilizes nodes by zeroing velocities and rounding positions.
5
+ * Extracted to reduce duplicate patterns in useForceSimulation.
6
+ */
7
+ export function stabilizeNodes(nodes: SimulationNode[]): void {
8
+ nodes.forEach((n) => {
9
+ (n as any).vx = 0;
10
+ (n as any).vy = 0;
11
+ if (typeof n.x === 'number') n.x = Number(n.x.toFixed(3));
12
+ if (typeof n.y === 'number') n.y = Number(n.y.toFixed(3));
13
+ });
14
+ }
15
+
16
+ /**
17
+ * Seeds nodes with random positions within bounds.
18
+ * Used as fallback when initial positioning fails.
19
+ */
20
+ export function seedRandomPositions(
21
+ nodes: SimulationNode[],
22
+ width: number,
23
+ height: number
24
+ ): void {
25
+ nodes.forEach((n) => {
26
+ n.x = Math.random() * width;
27
+ n.y = Math.random() * height;
28
+ (n as any).vx = (Math.random() - 0.5) * 10;
29
+ (n as any).vy = (Math.random() - 0.5) * 10;
30
+ });
31
+ }