@aiready/components 0.1.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 +240 -0
- package/dist/charts/ForceDirectedGraph.d.ts +40 -0
- package/dist/charts/ForceDirectedGraph.js +294 -0
- package/dist/charts/ForceDirectedGraph.js.map +1 -0
- package/dist/components/badge.d.ts +13 -0
- package/dist/components/badge.js +32 -0
- package/dist/components/badge.js.map +1 -0
- package/dist/components/button.d.ts +14 -0
- package/dist/components/button.js +52 -0
- package/dist/components/button.js.map +1 -0
- package/dist/components/card.d.ts +10 -0
- package/dist/components/card.js +66 -0
- package/dist/components/card.js.map +1 -0
- package/dist/components/checkbox.d.ts +8 -0
- package/dist/components/checkbox.js +42 -0
- package/dist/components/checkbox.js.map +1 -0
- package/dist/components/container.d.ts +8 -0
- package/dist/components/container.js +36 -0
- package/dist/components/container.js.map +1 -0
- package/dist/components/grid.d.ts +9 -0
- package/dist/components/grid.js +44 -0
- package/dist/components/grid.js.map +1 -0
- package/dist/components/input.d.ts +7 -0
- package/dist/components/input.js +30 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/label.d.ts +10 -0
- package/dist/components/label.js +28 -0
- package/dist/components/label.js.map +1 -0
- package/dist/components/radio-group.d.ts +17 -0
- package/dist/components/radio-group.js +64 -0
- package/dist/components/radio-group.js.map +1 -0
- package/dist/components/select.d.ts +15 -0
- package/dist/components/select.js +45 -0
- package/dist/components/select.js.map +1 -0
- package/dist/components/separator.d.ts +9 -0
- package/dist/components/separator.js +30 -0
- package/dist/components/separator.js.map +1 -0
- package/dist/components/stack.d.ts +11 -0
- package/dist/components/stack.js +60 -0
- package/dist/components/stack.js.map +1 -0
- package/dist/components/switch.d.ts +9 -0
- package/dist/components/switch.js +49 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/textarea.d.ts +7 -0
- package/dist/components/textarea.js +29 -0
- package/dist/components/textarea.js.map +1 -0
- package/dist/hooks/useD3.d.ts +6 -0
- package/dist/hooks/useD3.js +35 -0
- package/dist/hooks/useD3.js.map +1 -0
- package/dist/hooks/useDebounce.d.ts +3 -0
- package/dist/hooks/useDebounce.js +19 -0
- package/dist/hooks/useDebounce.js.map +1 -0
- package/dist/hooks/useForceSimulation.d.ts +39 -0
- package/dist/hooks/useForceSimulation.js +107 -0
- package/dist/hooks/useForceSimulation.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +927 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/cn.d.ts +5 -0
- package/dist/utils/cn.js +11 -0
- package/dist/utils/cn.js.map +1 -0
- package/dist/utils/colors.d.ts +19 -0
- package/dist/utils/colors.js +52 -0
- package/dist/utils/colors.js.map +1 -0
- package/dist/utils/formatters.d.ts +13 -0
- package/dist/utils/formatters.js +100 -0
- package/dist/utils/formatters.js.map +1 -0
- package/package.json +83 -0
- package/src/charts/ForceDirectedGraph.tsx +356 -0
- package/src/components/badge.tsx +35 -0
- package/src/components/button.tsx +53 -0
- package/src/components/card.tsx +78 -0
- package/src/components/checkbox.tsx +39 -0
- package/src/components/container.tsx +31 -0
- package/src/components/grid.tsx +40 -0
- package/src/components/input.tsx +24 -0
- package/src/components/label.tsx +24 -0
- package/src/components/radio-group.tsx +71 -0
- package/src/components/select.tsx +53 -0
- package/src/components/separator.tsx +29 -0
- package/src/components/stack.tsx +61 -0
- package/src/components/switch.tsx +49 -0
- package/src/components/textarea.tsx +23 -0
- package/src/hooks/useD3.ts +125 -0
- package/src/hooks/useDebounce.ts +44 -0
- package/src/hooks/useForceSimulation.ts +328 -0
- package/src/index.ts +51 -0
- package/src/utils/cn.ts +11 -0
- package/src/utils/colors.ts +58 -0
- package/src/utils/formatters.ts +161 -0
- package/tailwind.config.js +46 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import * as d3 from 'd3';
|
|
3
|
+
|
|
4
|
+
export interface SimulationNode extends d3.SimulationNodeDatum {
|
|
5
|
+
id: string;
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface SimulationLink extends d3.SimulationLinkDatum<SimulationNode> {
|
|
10
|
+
source: string | SimulationNode;
|
|
11
|
+
target: string | SimulationNode;
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ForceSimulationOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Strength of the charge force (repulsion between nodes)
|
|
18
|
+
* @default -300
|
|
19
|
+
*/
|
|
20
|
+
chargeStrength?: number;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Distance for links between nodes
|
|
24
|
+
* @default 100
|
|
25
|
+
*/
|
|
26
|
+
linkDistance?: number;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Strength of the link force
|
|
30
|
+
* @default 1
|
|
31
|
+
*/
|
|
32
|
+
linkStrength?: number;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Strength of collision detection
|
|
36
|
+
* @default 1
|
|
37
|
+
*/
|
|
38
|
+
collisionStrength?: number;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Radius for collision detection (node size)
|
|
42
|
+
* @default 10
|
|
43
|
+
*/
|
|
44
|
+
collisionRadius?: number;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Strength of centering force
|
|
48
|
+
* @default 0.1
|
|
49
|
+
*/
|
|
50
|
+
centerStrength?: number;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Width of the simulation space
|
|
54
|
+
*/
|
|
55
|
+
width: number;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Height of the simulation space
|
|
59
|
+
*/
|
|
60
|
+
height: number;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Alpha decay rate (how quickly the simulation cools down)
|
|
64
|
+
* @default 0.0228
|
|
65
|
+
*/
|
|
66
|
+
alphaDecay?: number;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Velocity decay (friction)
|
|
70
|
+
* @default 0.4
|
|
71
|
+
*/
|
|
72
|
+
velocityDecay?: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface UseForceSimulationReturn {
|
|
76
|
+
/**
|
|
77
|
+
* Current nodes with positions
|
|
78
|
+
*/
|
|
79
|
+
nodes: SimulationNode[];
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Current links
|
|
83
|
+
*/
|
|
84
|
+
links: SimulationLink[];
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Restart the simulation
|
|
88
|
+
*/
|
|
89
|
+
restart: () => void;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Stop the simulation
|
|
93
|
+
*/
|
|
94
|
+
stop: () => void;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Whether the simulation is currently running
|
|
98
|
+
*/
|
|
99
|
+
isRunning: boolean;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Current alpha value (simulation heat)
|
|
103
|
+
*/
|
|
104
|
+
alpha: number;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Hook for managing d3-force simulations
|
|
109
|
+
* Automatically handles simulation lifecycle, tick updates, and cleanup
|
|
110
|
+
*
|
|
111
|
+
* @param initialNodes - Initial nodes for the simulation
|
|
112
|
+
* @param initialLinks - Initial links for the simulation
|
|
113
|
+
* @param options - Configuration options for the force simulation
|
|
114
|
+
* @returns Simulation state and control functions
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```tsx
|
|
118
|
+
* function NetworkGraph() {
|
|
119
|
+
* const nodes = [
|
|
120
|
+
* { id: 'node1', name: 'Node 1' },
|
|
121
|
+
* { id: 'node2', name: 'Node 2' },
|
|
122
|
+
* { id: 'node3', name: 'Node 3' },
|
|
123
|
+
* ];
|
|
124
|
+
*
|
|
125
|
+
* const links = [
|
|
126
|
+
* { source: 'node1', target: 'node2' },
|
|
127
|
+
* { source: 'node2', target: 'node3' },
|
|
128
|
+
* ];
|
|
129
|
+
*
|
|
130
|
+
* const { nodes: simulatedNodes, links: simulatedLinks, restart } = useForceSimulation(
|
|
131
|
+
* nodes,
|
|
132
|
+
* links,
|
|
133
|
+
* {
|
|
134
|
+
* width: 800,
|
|
135
|
+
* height: 600,
|
|
136
|
+
* chargeStrength: -500,
|
|
137
|
+
* linkDistance: 150,
|
|
138
|
+
* }
|
|
139
|
+
* );
|
|
140
|
+
*
|
|
141
|
+
* return (
|
|
142
|
+
* <svg width={800} height={600}>
|
|
143
|
+
* {simulatedLinks.map((link, i) => (
|
|
144
|
+
* <line
|
|
145
|
+
* key={i}
|
|
146
|
+
* x1={(link.source as SimulationNode).x}
|
|
147
|
+
* y1={(link.source as SimulationNode).y}
|
|
148
|
+
* x2={(link.target as SimulationNode).x}
|
|
149
|
+
* y2={(link.target as SimulationNode).y}
|
|
150
|
+
* stroke="#999"
|
|
151
|
+
* />
|
|
152
|
+
* ))}
|
|
153
|
+
* {simulatedNodes.map((node) => (
|
|
154
|
+
* <circle
|
|
155
|
+
* key={node.id}
|
|
156
|
+
* cx={node.x}
|
|
157
|
+
* cy={node.y}
|
|
158
|
+
* r={10}
|
|
159
|
+
* fill="#69b3a2"
|
|
160
|
+
* />
|
|
161
|
+
* ))}
|
|
162
|
+
* </svg>
|
|
163
|
+
* );
|
|
164
|
+
* }
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
export function useForceSimulation(
|
|
168
|
+
initialNodes: SimulationNode[],
|
|
169
|
+
initialLinks: SimulationLink[],
|
|
170
|
+
options: ForceSimulationOptions
|
|
171
|
+
): UseForceSimulationReturn {
|
|
172
|
+
const {
|
|
173
|
+
chargeStrength = -300,
|
|
174
|
+
linkDistance = 100,
|
|
175
|
+
linkStrength = 1,
|
|
176
|
+
collisionStrength = 1,
|
|
177
|
+
collisionRadius = 10,
|
|
178
|
+
centerStrength = 0.1,
|
|
179
|
+
width,
|
|
180
|
+
height,
|
|
181
|
+
alphaDecay = 0.0228,
|
|
182
|
+
velocityDecay = 0.4,
|
|
183
|
+
} = options;
|
|
184
|
+
|
|
185
|
+
const [nodes, setNodes] = useState<SimulationNode[]>(initialNodes);
|
|
186
|
+
const [links, setLinks] = useState<SimulationLink[]>(initialLinks);
|
|
187
|
+
const [isRunning, setIsRunning] = useState(false);
|
|
188
|
+
const [alpha, setAlpha] = useState(1);
|
|
189
|
+
|
|
190
|
+
const simulationRef = useRef<d3.Simulation<SimulationNode, SimulationLink> | null>(null);
|
|
191
|
+
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
// Create a copy of nodes and links to avoid mutating the original data
|
|
194
|
+
const nodesCopy = initialNodes.map((node) => ({ ...node }));
|
|
195
|
+
const linksCopy = initialLinks.map((link) => ({ ...link }));
|
|
196
|
+
|
|
197
|
+
// Create the simulation
|
|
198
|
+
const simulation = d3
|
|
199
|
+
.forceSimulation<SimulationNode>(nodesCopy)
|
|
200
|
+
.force(
|
|
201
|
+
'link',
|
|
202
|
+
d3
|
|
203
|
+
.forceLink<SimulationNode, SimulationLink>(linksCopy)
|
|
204
|
+
.id((d) => d.id)
|
|
205
|
+
.distance(linkDistance)
|
|
206
|
+
.strength(linkStrength)
|
|
207
|
+
)
|
|
208
|
+
.force('charge', d3.forceManyBody().strength(chargeStrength))
|
|
209
|
+
.force('center', d3.forceCenter(width / 2, height / 2).strength(centerStrength))
|
|
210
|
+
.force(
|
|
211
|
+
'collision',
|
|
212
|
+
d3.forceCollide<SimulationNode>().radius(collisionRadius).strength(collisionStrength)
|
|
213
|
+
)
|
|
214
|
+
.alphaDecay(alphaDecay)
|
|
215
|
+
.velocityDecay(velocityDecay);
|
|
216
|
+
|
|
217
|
+
simulationRef.current = simulation;
|
|
218
|
+
|
|
219
|
+
// Update state on each tick
|
|
220
|
+
simulation.on('tick', () => {
|
|
221
|
+
setNodes([...nodesCopy]);
|
|
222
|
+
setLinks([...linksCopy]);
|
|
223
|
+
setAlpha(simulation.alpha());
|
|
224
|
+
setIsRunning(simulation.alpha() > simulation.alphaMin());
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
simulation.on('end', () => {
|
|
228
|
+
setIsRunning(false);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Cleanup on unmount
|
|
232
|
+
return () => {
|
|
233
|
+
simulation.stop();
|
|
234
|
+
};
|
|
235
|
+
}, [
|
|
236
|
+
initialNodes,
|
|
237
|
+
initialLinks,
|
|
238
|
+
chargeStrength,
|
|
239
|
+
linkDistance,
|
|
240
|
+
linkStrength,
|
|
241
|
+
collisionStrength,
|
|
242
|
+
collisionRadius,
|
|
243
|
+
centerStrength,
|
|
244
|
+
width,
|
|
245
|
+
height,
|
|
246
|
+
alphaDecay,
|
|
247
|
+
velocityDecay,
|
|
248
|
+
]);
|
|
249
|
+
|
|
250
|
+
const restart = () => {
|
|
251
|
+
if (simulationRef.current) {
|
|
252
|
+
simulationRef.current.alpha(1).restart();
|
|
253
|
+
setIsRunning(true);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const stop = () => {
|
|
258
|
+
if (simulationRef.current) {
|
|
259
|
+
simulationRef.current.stop();
|
|
260
|
+
setIsRunning(false);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
nodes,
|
|
266
|
+
links,
|
|
267
|
+
restart,
|
|
268
|
+
stop,
|
|
269
|
+
isRunning,
|
|
270
|
+
alpha,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Hook for creating a draggable force simulation
|
|
276
|
+
* Provides drag handlers that can be attached to node elements
|
|
277
|
+
*
|
|
278
|
+
* @param simulation - The d3 force simulation instance
|
|
279
|
+
* @returns Drag behavior that can be applied to nodes
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* ```tsx
|
|
283
|
+
* function DraggableNetworkGraph() {
|
|
284
|
+
* const simulation = useRef<d3.Simulation<SimulationNode, SimulationLink>>();
|
|
285
|
+
* const drag = useDrag(simulation.current);
|
|
286
|
+
*
|
|
287
|
+
* return (
|
|
288
|
+
* <svg>
|
|
289
|
+
* {nodes.map((node) => (
|
|
290
|
+
* <circle
|
|
291
|
+
* key={node.id}
|
|
292
|
+
* {...drag}
|
|
293
|
+
* cx={node.x}
|
|
294
|
+
* cy={node.y}
|
|
295
|
+
* r={10}
|
|
296
|
+
* />
|
|
297
|
+
* ))}
|
|
298
|
+
* </svg>
|
|
299
|
+
* );
|
|
300
|
+
* }
|
|
301
|
+
* ```
|
|
302
|
+
*/
|
|
303
|
+
export function useDrag(simulation: d3.Simulation<SimulationNode, any> | null | undefined) {
|
|
304
|
+
const dragStarted = (event: any, node: SimulationNode) => {
|
|
305
|
+
if (!simulation) return;
|
|
306
|
+
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
307
|
+
node.fx = node.x;
|
|
308
|
+
node.fy = node.y;
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const dragged = (event: any, node: SimulationNode) => {
|
|
312
|
+
node.fx = event.x;
|
|
313
|
+
node.fy = event.y;
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const dragEnded = (event: any, node: SimulationNode) => {
|
|
317
|
+
if (!simulation) return;
|
|
318
|
+
if (!event.active) simulation.alphaTarget(0);
|
|
319
|
+
node.fx = null;
|
|
320
|
+
node.fy = null;
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
onDragStart: dragStarted,
|
|
325
|
+
onDrag: dragged,
|
|
326
|
+
onDragEnd: dragEnded,
|
|
327
|
+
};
|
|
328
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
export { Button, buttonVariants, type ButtonProps } from './components/button';
|
|
3
|
+
export {
|
|
4
|
+
Card,
|
|
5
|
+
CardHeader,
|
|
6
|
+
CardFooter,
|
|
7
|
+
CardTitle,
|
|
8
|
+
CardDescription,
|
|
9
|
+
CardContent,
|
|
10
|
+
} from './components/card';
|
|
11
|
+
export { Input, type InputProps } from './components/input';
|
|
12
|
+
export { Label, type LabelProps } from './components/label';
|
|
13
|
+
export { Badge, badgeVariants, type BadgeProps } from './components/badge';
|
|
14
|
+
|
|
15
|
+
// Layout Components
|
|
16
|
+
export { Container, type ContainerProps } from './components/container';
|
|
17
|
+
export { Grid, type GridProps } from './components/grid';
|
|
18
|
+
export { Stack, type StackProps } from './components/stack';
|
|
19
|
+
export { Separator, type SeparatorProps } from './components/separator';
|
|
20
|
+
|
|
21
|
+
// Form Components
|
|
22
|
+
export { Checkbox, type CheckboxProps } from './components/checkbox';
|
|
23
|
+
export { RadioGroup, type RadioGroupProps, type RadioOption } from './components/radio-group';
|
|
24
|
+
export { Switch, type SwitchProps } from './components/switch';
|
|
25
|
+
export { Textarea, type TextareaProps } from './components/textarea';
|
|
26
|
+
export { Select, type SelectProps, type SelectOption } from './components/select';
|
|
27
|
+
|
|
28
|
+
// Utils
|
|
29
|
+
export { cn } from './utils/cn';
|
|
30
|
+
export * from './utils/colors';
|
|
31
|
+
export * from './utils/formatters';
|
|
32
|
+
|
|
33
|
+
// Hooks
|
|
34
|
+
export { useDebounce } from './hooks/useDebounce';
|
|
35
|
+
export { useD3, useD3WithResize } from './hooks/useD3';
|
|
36
|
+
export {
|
|
37
|
+
useForceSimulation,
|
|
38
|
+
useDrag,
|
|
39
|
+
type SimulationNode,
|
|
40
|
+
type SimulationLink,
|
|
41
|
+
type ForceSimulationOptions,
|
|
42
|
+
type UseForceSimulationReturn,
|
|
43
|
+
} from './hooks/useForceSimulation';
|
|
44
|
+
|
|
45
|
+
// Charts
|
|
46
|
+
export {
|
|
47
|
+
ForceDirectedGraph,
|
|
48
|
+
type GraphNode,
|
|
49
|
+
type GraphLink,
|
|
50
|
+
type ForceDirectedGraphProps,
|
|
51
|
+
} from './charts/ForceDirectedGraph';
|
package/src/utils/cn.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ClassValue, clsx } from 'clsx';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Merges class names using clsx and tailwind-merge
|
|
6
|
+
* @param inputs - Class values to merge
|
|
7
|
+
* @returns Merged class names
|
|
8
|
+
*/
|
|
9
|
+
export function cn(...inputs: ClassValue[]) {
|
|
10
|
+
return twMerge(clsx(inputs));
|
|
11
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color utilities for charts and visualizations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Severity colors for issue highlighting
|
|
6
|
+
export const severityColors = {
|
|
7
|
+
critical: '#ef4444', // red-500
|
|
8
|
+
major: '#f59e0b', // amber-500
|
|
9
|
+
minor: '#eab308', // yellow-500
|
|
10
|
+
info: '#60a5fa', // blue-400
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
// Domain colors for clustering and categorization
|
|
14
|
+
export const domainColors = [
|
|
15
|
+
'#3b82f6', // blue-500
|
|
16
|
+
'#8b5cf6', // violet-500
|
|
17
|
+
'#ec4899', // pink-500
|
|
18
|
+
'#10b981', // green-500
|
|
19
|
+
'#f59e0b', // amber-500
|
|
20
|
+
'#06b6d4', // cyan-500
|
|
21
|
+
'#f97316', // orange-500
|
|
22
|
+
'#a855f7', // purple-500
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
25
|
+
// Chart colors
|
|
26
|
+
export const chartColors = {
|
|
27
|
+
primary: '#3b82f6',
|
|
28
|
+
success: '#10b981',
|
|
29
|
+
warning: '#f59e0b',
|
|
30
|
+
danger: '#ef4444',
|
|
31
|
+
info: '#06b6d4',
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get a color for a domain/category by index
|
|
36
|
+
*/
|
|
37
|
+
export function getDomainColor(index: number): string {
|
|
38
|
+
return domainColors[index % domainColors.length];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get severity color by level
|
|
43
|
+
*/
|
|
44
|
+
export function getSeverityColor(
|
|
45
|
+
severity: keyof typeof severityColors
|
|
46
|
+
): string {
|
|
47
|
+
return severityColors[severity];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Convert hex color to RGBA
|
|
52
|
+
*/
|
|
53
|
+
export function hexToRgba(hex: string, alpha: number): string {
|
|
54
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
55
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
56
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
57
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
58
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Number formatting utilities for data visualization
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Format a number with commas for thousands
|
|
7
|
+
* @example formatNumber(1234567) => "1,234,567"
|
|
8
|
+
*/
|
|
9
|
+
export function formatNumber(value: number): string {
|
|
10
|
+
return new Intl.NumberFormat('en-US').format(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Format a number with K/M/B abbreviations
|
|
15
|
+
* @example formatCompactNumber(1234) => "1.2K"
|
|
16
|
+
* @example formatCompactNumber(1234567) => "1.2M"
|
|
17
|
+
*/
|
|
18
|
+
export function formatCompactNumber(value: number): string {
|
|
19
|
+
return new Intl.NumberFormat('en-US', {
|
|
20
|
+
notation: 'compact',
|
|
21
|
+
maximumFractionDigits: 1,
|
|
22
|
+
}).format(value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Format a percentage with optional decimal places
|
|
27
|
+
* @example formatPercentage(0.1234) => "12.3%"
|
|
28
|
+
* @example formatPercentage(0.1234, 0) => "12%"
|
|
29
|
+
*/
|
|
30
|
+
export function formatPercentage(value: number, decimals: number = 1): string {
|
|
31
|
+
return `${(value * 100).toFixed(decimals)}%`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Format file size in bytes to human-readable format
|
|
36
|
+
* @example formatFileSize(1024) => "1.0 KB"
|
|
37
|
+
* @example formatFileSize(1048576) => "1.0 MB"
|
|
38
|
+
*/
|
|
39
|
+
export function formatFileSize(bytes: number): string {
|
|
40
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
41
|
+
let size = bytes;
|
|
42
|
+
let unitIndex = 0;
|
|
43
|
+
|
|
44
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
45
|
+
size /= 1024;
|
|
46
|
+
unitIndex++;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Format a date as relative time (e.g., "2 hours ago")
|
|
54
|
+
* @example formatRelativeTime(new Date(Date.now() - 3600000)) => "1 hour ago"
|
|
55
|
+
*/
|
|
56
|
+
export function formatRelativeTime(date: Date): string {
|
|
57
|
+
const now = new Date();
|
|
58
|
+
const diffMs = now.getTime() - date.getTime();
|
|
59
|
+
const diffSec = Math.floor(diffMs / 1000);
|
|
60
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
61
|
+
const diffHour = Math.floor(diffMin / 60);
|
|
62
|
+
const diffDay = Math.floor(diffHour / 24);
|
|
63
|
+
const diffMonth = Math.floor(diffDay / 30);
|
|
64
|
+
const diffYear = Math.floor(diffDay / 365);
|
|
65
|
+
|
|
66
|
+
if (diffYear > 0) {
|
|
67
|
+
return `${diffYear} year${diffYear > 1 ? 's' : ''} ago`;
|
|
68
|
+
}
|
|
69
|
+
if (diffMonth > 0) {
|
|
70
|
+
return `${diffMonth} month${diffMonth > 1 ? 's' : ''} ago`;
|
|
71
|
+
}
|
|
72
|
+
if (diffDay > 0) {
|
|
73
|
+
return `${diffDay} day${diffDay > 1 ? 's' : ''} ago`;
|
|
74
|
+
}
|
|
75
|
+
if (diffHour > 0) {
|
|
76
|
+
return `${diffHour} hour${diffHour > 1 ? 's' : ''} ago`;
|
|
77
|
+
}
|
|
78
|
+
if (diffMin > 0) {
|
|
79
|
+
return `${diffMin} minute${diffMin > 1 ? 's' : ''} ago`;
|
|
80
|
+
}
|
|
81
|
+
return 'just now';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Format a date in short format (e.g., "Jan 15, 2024")
|
|
86
|
+
*/
|
|
87
|
+
export function formatDate(date: Date): string {
|
|
88
|
+
return new Intl.DateTimeFormat('en-US', {
|
|
89
|
+
year: 'numeric',
|
|
90
|
+
month: 'short',
|
|
91
|
+
day: 'numeric',
|
|
92
|
+
}).format(date);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Format a date with time (e.g., "Jan 15, 2024 at 3:45 PM")
|
|
97
|
+
*/
|
|
98
|
+
export function formatDateTime(date: Date): string {
|
|
99
|
+
return new Intl.DateTimeFormat('en-US', {
|
|
100
|
+
year: 'numeric',
|
|
101
|
+
month: 'short',
|
|
102
|
+
day: 'numeric',
|
|
103
|
+
hour: 'numeric',
|
|
104
|
+
minute: '2-digit',
|
|
105
|
+
hour12: true,
|
|
106
|
+
}).format(date);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Format duration in milliseconds to human-readable format
|
|
111
|
+
* @example formatDuration(3661000) => "1h 1m 1s"
|
|
112
|
+
*/
|
|
113
|
+
export function formatDuration(ms: number): string {
|
|
114
|
+
const seconds = Math.floor(ms / 1000);
|
|
115
|
+
const minutes = Math.floor(seconds / 60);
|
|
116
|
+
const hours = Math.floor(minutes / 60);
|
|
117
|
+
|
|
118
|
+
if (hours > 0) {
|
|
119
|
+
return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
|
|
120
|
+
}
|
|
121
|
+
if (minutes > 0) {
|
|
122
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
123
|
+
}
|
|
124
|
+
return `${seconds}s`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Format a metric value with appropriate unit and precision
|
|
129
|
+
* Useful for displaying various metrics in charts
|
|
130
|
+
*/
|
|
131
|
+
export function formatMetric(
|
|
132
|
+
value: number,
|
|
133
|
+
unit: 'number' | 'bytes' | 'percentage' | 'duration' = 'number'
|
|
134
|
+
): string {
|
|
135
|
+
switch (unit) {
|
|
136
|
+
case 'bytes':
|
|
137
|
+
return formatFileSize(value);
|
|
138
|
+
case 'percentage':
|
|
139
|
+
return formatPercentage(value);
|
|
140
|
+
case 'duration':
|
|
141
|
+
return formatDuration(value);
|
|
142
|
+
default:
|
|
143
|
+
return formatCompactNumber(value);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Format a range of values
|
|
149
|
+
* @example formatRange(100, 200) => "100 - 200"
|
|
150
|
+
*/
|
|
151
|
+
export function formatRange(min: number, max: number): string {
|
|
152
|
+
return `${formatCompactNumber(min)} - ${formatCompactNumber(max)}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Format a number with a specific number of decimal places
|
|
157
|
+
* @example formatDecimal(3.14159, 2) => "3.14"
|
|
158
|
+
*/
|
|
159
|
+
export function formatDecimal(value: number, decimals: number = 2): string {
|
|
160
|
+
return value.toFixed(decimals);
|
|
161
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
|
2
|
+
export default {
|
|
3
|
+
darkMode: ['class'],
|
|
4
|
+
content: ['./src/**/*.{ts,tsx}'],
|
|
5
|
+
theme: {
|
|
6
|
+
extend: {
|
|
7
|
+
colors: {
|
|
8
|
+
border: 'hsl(var(--border))',
|
|
9
|
+
input: 'hsl(var(--input))',
|
|
10
|
+
ring: 'hsl(var(--ring))',
|
|
11
|
+
background: 'hsl(var(--background))',
|
|
12
|
+
foreground: 'hsl(var(--foreground))',
|
|
13
|
+
primary: {
|
|
14
|
+
DEFAULT: 'hsl(var(--primary))',
|
|
15
|
+
foreground: 'hsl(var(--primary-foreground))',
|
|
16
|
+
},
|
|
17
|
+
secondary: {
|
|
18
|
+
DEFAULT: 'hsl(var(--secondary))',
|
|
19
|
+
foreground: 'hsl(var(--secondary-foreground))',
|
|
20
|
+
},
|
|
21
|
+
destructive: {
|
|
22
|
+
DEFAULT: 'hsl(var(--destructive))',
|
|
23
|
+
foreground: 'hsl(var(--destructive-foreground))',
|
|
24
|
+
},
|
|
25
|
+
muted: {
|
|
26
|
+
DEFAULT: 'hsl(var(--muted))',
|
|
27
|
+
foreground: 'hsl(var(--muted-foreground))',
|
|
28
|
+
},
|
|
29
|
+
accent: {
|
|
30
|
+
DEFAULT: 'hsl(var(--accent))',
|
|
31
|
+
foreground: 'hsl(var(--accent-foreground))',
|
|
32
|
+
},
|
|
33
|
+
card: {
|
|
34
|
+
DEFAULT: 'hsl(var(--card))',
|
|
35
|
+
foreground: 'hsl(var(--card-foreground))',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
borderRadius: {
|
|
39
|
+
lg: 'var(--radius)',
|
|
40
|
+
md: 'calc(var(--radius) - 2px)',
|
|
41
|
+
sm: 'calc(var(--radius) - 4px)',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
plugins: [],
|
|
46
|
+
};
|