@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.
Files changed (91) hide show
  1. package/README.md +240 -0
  2. package/dist/charts/ForceDirectedGraph.d.ts +40 -0
  3. package/dist/charts/ForceDirectedGraph.js +294 -0
  4. package/dist/charts/ForceDirectedGraph.js.map +1 -0
  5. package/dist/components/badge.d.ts +13 -0
  6. package/dist/components/badge.js +32 -0
  7. package/dist/components/badge.js.map +1 -0
  8. package/dist/components/button.d.ts +14 -0
  9. package/dist/components/button.js +52 -0
  10. package/dist/components/button.js.map +1 -0
  11. package/dist/components/card.d.ts +10 -0
  12. package/dist/components/card.js +66 -0
  13. package/dist/components/card.js.map +1 -0
  14. package/dist/components/checkbox.d.ts +8 -0
  15. package/dist/components/checkbox.js +42 -0
  16. package/dist/components/checkbox.js.map +1 -0
  17. package/dist/components/container.d.ts +8 -0
  18. package/dist/components/container.js +36 -0
  19. package/dist/components/container.js.map +1 -0
  20. package/dist/components/grid.d.ts +9 -0
  21. package/dist/components/grid.js +44 -0
  22. package/dist/components/grid.js.map +1 -0
  23. package/dist/components/input.d.ts +7 -0
  24. package/dist/components/input.js +30 -0
  25. package/dist/components/input.js.map +1 -0
  26. package/dist/components/label.d.ts +10 -0
  27. package/dist/components/label.js +28 -0
  28. package/dist/components/label.js.map +1 -0
  29. package/dist/components/radio-group.d.ts +17 -0
  30. package/dist/components/radio-group.js +64 -0
  31. package/dist/components/radio-group.js.map +1 -0
  32. package/dist/components/select.d.ts +15 -0
  33. package/dist/components/select.js +45 -0
  34. package/dist/components/select.js.map +1 -0
  35. package/dist/components/separator.d.ts +9 -0
  36. package/dist/components/separator.js +30 -0
  37. package/dist/components/separator.js.map +1 -0
  38. package/dist/components/stack.d.ts +11 -0
  39. package/dist/components/stack.js +60 -0
  40. package/dist/components/stack.js.map +1 -0
  41. package/dist/components/switch.d.ts +9 -0
  42. package/dist/components/switch.js +49 -0
  43. package/dist/components/switch.js.map +1 -0
  44. package/dist/components/textarea.d.ts +7 -0
  45. package/dist/components/textarea.js +29 -0
  46. package/dist/components/textarea.js.map +1 -0
  47. package/dist/hooks/useD3.d.ts +6 -0
  48. package/dist/hooks/useD3.js +35 -0
  49. package/dist/hooks/useD3.js.map +1 -0
  50. package/dist/hooks/useDebounce.d.ts +3 -0
  51. package/dist/hooks/useDebounce.js +19 -0
  52. package/dist/hooks/useDebounce.js.map +1 -0
  53. package/dist/hooks/useForceSimulation.d.ts +39 -0
  54. package/dist/hooks/useForceSimulation.js +107 -0
  55. package/dist/hooks/useForceSimulation.js.map +1 -0
  56. package/dist/index.d.ts +27 -0
  57. package/dist/index.js +927 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/utils/cn.d.ts +5 -0
  60. package/dist/utils/cn.js +11 -0
  61. package/dist/utils/cn.js.map +1 -0
  62. package/dist/utils/colors.d.ts +19 -0
  63. package/dist/utils/colors.js +52 -0
  64. package/dist/utils/colors.js.map +1 -0
  65. package/dist/utils/formatters.d.ts +13 -0
  66. package/dist/utils/formatters.js +100 -0
  67. package/dist/utils/formatters.js.map +1 -0
  68. package/package.json +83 -0
  69. package/src/charts/ForceDirectedGraph.tsx +356 -0
  70. package/src/components/badge.tsx +35 -0
  71. package/src/components/button.tsx +53 -0
  72. package/src/components/card.tsx +78 -0
  73. package/src/components/checkbox.tsx +39 -0
  74. package/src/components/container.tsx +31 -0
  75. package/src/components/grid.tsx +40 -0
  76. package/src/components/input.tsx +24 -0
  77. package/src/components/label.tsx +24 -0
  78. package/src/components/radio-group.tsx +71 -0
  79. package/src/components/select.tsx +53 -0
  80. package/src/components/separator.tsx +29 -0
  81. package/src/components/stack.tsx +61 -0
  82. package/src/components/switch.tsx +49 -0
  83. package/src/components/textarea.tsx +23 -0
  84. package/src/hooks/useD3.ts +125 -0
  85. package/src/hooks/useDebounce.ts +44 -0
  86. package/src/hooks/useForceSimulation.ts +328 -0
  87. package/src/index.ts +51 -0
  88. package/src/utils/cn.ts +11 -0
  89. package/src/utils/colors.ts +58 -0
  90. package/src/utils/formatters.ts +161 -0
  91. 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';
@@ -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
+ };