@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,356 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import * as d3 from 'd3';
|
|
3
|
+
import {
|
|
4
|
+
useForceSimulation,
|
|
5
|
+
type SimulationNode,
|
|
6
|
+
type SimulationLink,
|
|
7
|
+
type ForceSimulationOptions,
|
|
8
|
+
} from '../hooks/useForceSimulation';
|
|
9
|
+
import { cn } from '../utils/cn';
|
|
10
|
+
|
|
11
|
+
export interface GraphNode extends SimulationNode {
|
|
12
|
+
id: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
color?: string;
|
|
15
|
+
size?: number;
|
|
16
|
+
group?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface GraphLink extends SimulationLink {
|
|
20
|
+
color?: string;
|
|
21
|
+
width?: number;
|
|
22
|
+
label?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ForceDirectedGraphProps {
|
|
26
|
+
/**
|
|
27
|
+
* Array of nodes to display
|
|
28
|
+
*/
|
|
29
|
+
nodes: GraphNode[];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Array of links between nodes
|
|
33
|
+
*/
|
|
34
|
+
links: GraphLink[];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Width of the graph container
|
|
38
|
+
*/
|
|
39
|
+
width: number;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Height of the graph container
|
|
43
|
+
*/
|
|
44
|
+
height: number;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Force simulation options
|
|
48
|
+
*/
|
|
49
|
+
simulationOptions?: Partial<ForceSimulationOptions>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Whether to enable zoom and pan
|
|
53
|
+
* @default true
|
|
54
|
+
*/
|
|
55
|
+
enableZoom?: boolean;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Whether to enable node dragging
|
|
59
|
+
* @default true
|
|
60
|
+
*/
|
|
61
|
+
enableDrag?: boolean;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Callback when a node is clicked
|
|
65
|
+
*/
|
|
66
|
+
onNodeClick?: (node: GraphNode) => void;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Callback when a node is hovered
|
|
70
|
+
*/
|
|
71
|
+
onNodeHover?: (node: GraphNode | null) => void;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Callback when a link is clicked
|
|
75
|
+
*/
|
|
76
|
+
onLinkClick?: (link: GraphLink) => void;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Selected node ID
|
|
80
|
+
*/
|
|
81
|
+
selectedNodeId?: string;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Hovered node ID
|
|
85
|
+
*/
|
|
86
|
+
hoveredNodeId?: string;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Default node color
|
|
90
|
+
* @default "#69b3a2"
|
|
91
|
+
*/
|
|
92
|
+
defaultNodeColor?: string;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Default node size
|
|
96
|
+
* @default 10
|
|
97
|
+
*/
|
|
98
|
+
defaultNodeSize?: number;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Default link color
|
|
102
|
+
* @default "#999"
|
|
103
|
+
*/
|
|
104
|
+
defaultLinkColor?: string;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Default link width
|
|
108
|
+
* @default 1
|
|
109
|
+
*/
|
|
110
|
+
defaultLinkWidth?: number;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Whether to show node labels
|
|
114
|
+
* @default true
|
|
115
|
+
*/
|
|
116
|
+
showNodeLabels?: boolean;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Whether to show link labels
|
|
120
|
+
* @default false
|
|
121
|
+
*/
|
|
122
|
+
showLinkLabels?: boolean;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Additional CSS classes
|
|
126
|
+
*/
|
|
127
|
+
className?: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const ForceDirectedGraph: React.FC<ForceDirectedGraphProps> = ({
|
|
131
|
+
nodes: initialNodes,
|
|
132
|
+
links: initialLinks,
|
|
133
|
+
width,
|
|
134
|
+
height,
|
|
135
|
+
simulationOptions,
|
|
136
|
+
enableZoom = true,
|
|
137
|
+
enableDrag = true,
|
|
138
|
+
onNodeClick,
|
|
139
|
+
onNodeHover,
|
|
140
|
+
onLinkClick,
|
|
141
|
+
selectedNodeId,
|
|
142
|
+
hoveredNodeId,
|
|
143
|
+
defaultNodeColor = '#69b3a2',
|
|
144
|
+
defaultNodeSize = 10,
|
|
145
|
+
defaultLinkColor = '#999',
|
|
146
|
+
defaultLinkWidth = 1,
|
|
147
|
+
showNodeLabels = true,
|
|
148
|
+
showLinkLabels = false,
|
|
149
|
+
className,
|
|
150
|
+
}) => {
|
|
151
|
+
const svgRef = useRef<SVGSVGElement>(null);
|
|
152
|
+
const gRef = useRef<SVGGElement>(null);
|
|
153
|
+
const [transform, setTransform] = useState({ k: 1, x: 0, y: 0 });
|
|
154
|
+
|
|
155
|
+
// Initialize simulation
|
|
156
|
+
const { nodes, links, restart } = useForceSimulation(initialNodes, initialLinks, {
|
|
157
|
+
width,
|
|
158
|
+
height,
|
|
159
|
+
...simulationOptions,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Set up zoom behavior
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
if (!enableZoom || !svgRef.current || !gRef.current) return;
|
|
165
|
+
|
|
166
|
+
const svg = d3.select(svgRef.current);
|
|
167
|
+
const g = d3.select(gRef.current);
|
|
168
|
+
|
|
169
|
+
const zoom = d3
|
|
170
|
+
.zoom<SVGSVGElement, unknown>()
|
|
171
|
+
.scaleExtent([0.1, 10])
|
|
172
|
+
.on('zoom', (event) => {
|
|
173
|
+
g.attr('transform', event.transform);
|
|
174
|
+
setTransform(event.transform);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
svg.call(zoom);
|
|
178
|
+
|
|
179
|
+
return () => {
|
|
180
|
+
svg.on('.zoom', null);
|
|
181
|
+
};
|
|
182
|
+
}, [enableZoom]);
|
|
183
|
+
|
|
184
|
+
// Set up drag behavior
|
|
185
|
+
const handleDragStart = useCallback(
|
|
186
|
+
(event: React.MouseEvent, node: GraphNode) => {
|
|
187
|
+
if (!enableDrag) return;
|
|
188
|
+
event.stopPropagation();
|
|
189
|
+
node.fx = node.x;
|
|
190
|
+
node.fy = node.y;
|
|
191
|
+
restart();
|
|
192
|
+
},
|
|
193
|
+
[enableDrag, restart]
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const handleDrag = useCallback(
|
|
197
|
+
(event: React.MouseEvent, node: GraphNode) => {
|
|
198
|
+
if (!enableDrag) return;
|
|
199
|
+
const svg = svgRef.current;
|
|
200
|
+
if (!svg) return;
|
|
201
|
+
|
|
202
|
+
const rect = svg.getBoundingClientRect();
|
|
203
|
+
const x = (event.clientX - rect.left - transform.x) / transform.k;
|
|
204
|
+
const y = (event.clientY - rect.top - transform.y) / transform.k;
|
|
205
|
+
|
|
206
|
+
node.fx = x;
|
|
207
|
+
node.fy = y;
|
|
208
|
+
},
|
|
209
|
+
[enableDrag, transform]
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const handleDragEnd = useCallback(
|
|
213
|
+
(event: React.MouseEvent, node: GraphNode) => {
|
|
214
|
+
if (!enableDrag) return;
|
|
215
|
+
event.stopPropagation();
|
|
216
|
+
node.fx = null;
|
|
217
|
+
node.fy = null;
|
|
218
|
+
},
|
|
219
|
+
[enableDrag]
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const handleNodeClick = useCallback(
|
|
223
|
+
(node: GraphNode) => {
|
|
224
|
+
onNodeClick?.(node);
|
|
225
|
+
},
|
|
226
|
+
[onNodeClick]
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
const handleNodeMouseEnter = useCallback(
|
|
230
|
+
(node: GraphNode) => {
|
|
231
|
+
onNodeHover?.(node);
|
|
232
|
+
},
|
|
233
|
+
[onNodeHover]
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const handleNodeMouseLeave = useCallback(() => {
|
|
237
|
+
onNodeHover?.(null);
|
|
238
|
+
}, [onNodeHover]);
|
|
239
|
+
|
|
240
|
+
const handleLinkClick = useCallback(
|
|
241
|
+
(link: GraphLink) => {
|
|
242
|
+
onLinkClick?.(link);
|
|
243
|
+
},
|
|
244
|
+
[onLinkClick]
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
return (
|
|
248
|
+
<svg
|
|
249
|
+
ref={svgRef}
|
|
250
|
+
width={width}
|
|
251
|
+
height={height}
|
|
252
|
+
className={cn('bg-white dark:bg-gray-900', className)}
|
|
253
|
+
>
|
|
254
|
+
<defs>
|
|
255
|
+
{/* Arrow marker for directed graphs */}
|
|
256
|
+
<marker
|
|
257
|
+
id="arrow"
|
|
258
|
+
viewBox="0 0 10 10"
|
|
259
|
+
refX="20"
|
|
260
|
+
refY="5"
|
|
261
|
+
markerWidth="6"
|
|
262
|
+
markerHeight="6"
|
|
263
|
+
orient="auto"
|
|
264
|
+
>
|
|
265
|
+
<path d="M 0 0 L 10 5 L 0 10 z" fill={defaultLinkColor} />
|
|
266
|
+
</marker>
|
|
267
|
+
</defs>
|
|
268
|
+
|
|
269
|
+
<g ref={gRef}>
|
|
270
|
+
{/* Render links */}
|
|
271
|
+
{links.map((link, i) => {
|
|
272
|
+
const source = link.source as GraphNode;
|
|
273
|
+
const target = link.target as GraphNode;
|
|
274
|
+
if (!source.x || !source.y || !target.x || !target.y) return null;
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<g key={`link-${i}`}>
|
|
278
|
+
<line
|
|
279
|
+
x1={source.x}
|
|
280
|
+
y1={source.y}
|
|
281
|
+
x2={target.x}
|
|
282
|
+
y2={target.y}
|
|
283
|
+
stroke={link.color || defaultLinkColor}
|
|
284
|
+
strokeWidth={link.width || defaultLinkWidth}
|
|
285
|
+
opacity={0.6}
|
|
286
|
+
className="cursor-pointer transition-opacity hover:opacity-100"
|
|
287
|
+
onClick={() => handleLinkClick(link)}
|
|
288
|
+
/>
|
|
289
|
+
{showLinkLabels && link.label && (
|
|
290
|
+
<text
|
|
291
|
+
x={(source.x + target.x) / 2}
|
|
292
|
+
y={(source.y + target.y) / 2}
|
|
293
|
+
fill="#666"
|
|
294
|
+
fontSize="10"
|
|
295
|
+
textAnchor="middle"
|
|
296
|
+
dominantBaseline="middle"
|
|
297
|
+
pointerEvents="none"
|
|
298
|
+
>
|
|
299
|
+
{link.label}
|
|
300
|
+
</text>
|
|
301
|
+
)}
|
|
302
|
+
</g>
|
|
303
|
+
);
|
|
304
|
+
})}
|
|
305
|
+
|
|
306
|
+
{/* Render nodes */}
|
|
307
|
+
{nodes.map((node) => {
|
|
308
|
+
if (!node.x || !node.y) return null;
|
|
309
|
+
|
|
310
|
+
const isSelected = selectedNodeId === node.id;
|
|
311
|
+
const isHovered = hoveredNodeId === node.id;
|
|
312
|
+
const nodeSize = node.size || defaultNodeSize;
|
|
313
|
+
const nodeColor = node.color || defaultNodeColor;
|
|
314
|
+
|
|
315
|
+
return (
|
|
316
|
+
<g
|
|
317
|
+
key={node.id}
|
|
318
|
+
transform={`translate(${node.x},${node.y})`}
|
|
319
|
+
className="cursor-pointer"
|
|
320
|
+
onClick={() => handleNodeClick(node)}
|
|
321
|
+
onMouseEnter={() => handleNodeMouseEnter(node)}
|
|
322
|
+
onMouseLeave={handleNodeMouseLeave}
|
|
323
|
+
onMouseDown={(e) => handleDragStart(e, node)}
|
|
324
|
+
onMouseMove={(e) => handleDrag(e, node)}
|
|
325
|
+
onMouseUp={(e) => handleDragEnd(e, node)}
|
|
326
|
+
>
|
|
327
|
+
<circle
|
|
328
|
+
r={nodeSize}
|
|
329
|
+
fill={nodeColor}
|
|
330
|
+
stroke={isSelected ? '#000' : isHovered ? '#666' : 'none'}
|
|
331
|
+
strokeWidth={isSelected ? 3 : 2}
|
|
332
|
+
opacity={isHovered || isSelected ? 1 : 0.9}
|
|
333
|
+
className="transition-all"
|
|
334
|
+
/>
|
|
335
|
+
{showNodeLabels && node.label && (
|
|
336
|
+
<text
|
|
337
|
+
y={nodeSize + 15}
|
|
338
|
+
fill="#333"
|
|
339
|
+
fontSize="12"
|
|
340
|
+
textAnchor="middle"
|
|
341
|
+
dominantBaseline="middle"
|
|
342
|
+
pointerEvents="none"
|
|
343
|
+
className="select-none"
|
|
344
|
+
>
|
|
345
|
+
{node.label}
|
|
346
|
+
</text>
|
|
347
|
+
)}
|
|
348
|
+
</g>
|
|
349
|
+
);
|
|
350
|
+
})}
|
|
351
|
+
</g>
|
|
352
|
+
</svg>
|
|
353
|
+
);
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
ForceDirectedGraph.displayName = 'ForceDirectedGraph';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
import { cn } from '../utils/cn';
|
|
4
|
+
|
|
5
|
+
const badgeVariants = cva(
|
|
6
|
+
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default:
|
|
11
|
+
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
|
|
12
|
+
secondary:
|
|
13
|
+
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
14
|
+
destructive:
|
|
15
|
+
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
|
|
16
|
+
outline: 'text-foreground',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
variant: 'default',
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export interface BadgeProps
|
|
26
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
27
|
+
VariantProps<typeof badgeVariants> {}
|
|
28
|
+
|
|
29
|
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
30
|
+
return (
|
|
31
|
+
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
import { cn } from '../utils/cn';
|
|
4
|
+
|
|
5
|
+
const buttonVariants = cva(
|
|
6
|
+
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
11
|
+
destructive:
|
|
12
|
+
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
|
13
|
+
outline:
|
|
14
|
+
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
|
15
|
+
secondary:
|
|
16
|
+
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
17
|
+
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
|
18
|
+
link: 'text-primary underline-offset-4 hover:underline',
|
|
19
|
+
},
|
|
20
|
+
size: {
|
|
21
|
+
default: 'h-10 px-4 py-2',
|
|
22
|
+
sm: 'h-9 rounded-md px-3',
|
|
23
|
+
lg: 'h-11 rounded-md px-8',
|
|
24
|
+
icon: 'h-10 w-10',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
defaultVariants: {
|
|
28
|
+
variant: 'default',
|
|
29
|
+
size: 'default',
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export interface ButtonProps
|
|
35
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
36
|
+
VariantProps<typeof buttonVariants> {
|
|
37
|
+
asChild?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
41
|
+
({ className, variant, size, ...props }, ref) => {
|
|
42
|
+
return (
|
|
43
|
+
<button
|
|
44
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
45
|
+
ref={ref}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
Button.displayName = 'Button';
|
|
52
|
+
|
|
53
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
|
|
4
|
+
const Card = React.forwardRef<
|
|
5
|
+
HTMLDivElement,
|
|
6
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
7
|
+
>(({ className, ...props }, ref) => (
|
|
8
|
+
<div
|
|
9
|
+
ref={ref}
|
|
10
|
+
className={cn(
|
|
11
|
+
'rounded-lg border bg-card text-card-foreground shadow-sm',
|
|
12
|
+
className
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
));
|
|
17
|
+
Card.displayName = 'Card';
|
|
18
|
+
|
|
19
|
+
const CardHeader = React.forwardRef<
|
|
20
|
+
HTMLDivElement,
|
|
21
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
22
|
+
>(({ className, ...props }, ref) => (
|
|
23
|
+
<div
|
|
24
|
+
ref={ref}
|
|
25
|
+
className={cn('flex flex-col space-y-1.5 p-6', className)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
));
|
|
29
|
+
CardHeader.displayName = 'CardHeader';
|
|
30
|
+
|
|
31
|
+
const CardTitle = React.forwardRef<
|
|
32
|
+
HTMLParagraphElement,
|
|
33
|
+
React.HTMLAttributes<HTMLHeadingElement>
|
|
34
|
+
>(({ className, ...props }, ref) => (
|
|
35
|
+
<h3
|
|
36
|
+
ref={ref}
|
|
37
|
+
className={cn(
|
|
38
|
+
'text-2xl font-semibold leading-none tracking-tight',
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
));
|
|
44
|
+
CardTitle.displayName = 'CardTitle';
|
|
45
|
+
|
|
46
|
+
const CardDescription = React.forwardRef<
|
|
47
|
+
HTMLParagraphElement,
|
|
48
|
+
React.HTMLAttributes<HTMLParagraphElement>
|
|
49
|
+
>(({ className, ...props }, ref) => (
|
|
50
|
+
<p
|
|
51
|
+
ref={ref}
|
|
52
|
+
className={cn('text-sm text-muted-foreground', className)}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
));
|
|
56
|
+
CardDescription.displayName = 'CardDescription';
|
|
57
|
+
|
|
58
|
+
const CardContent = React.forwardRef<
|
|
59
|
+
HTMLDivElement,
|
|
60
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
61
|
+
>(({ className, ...props }, ref) => (
|
|
62
|
+
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
|
|
63
|
+
));
|
|
64
|
+
CardContent.displayName = 'CardContent';
|
|
65
|
+
|
|
66
|
+
const CardFooter = React.forwardRef<
|
|
67
|
+
HTMLDivElement,
|
|
68
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
69
|
+
>(({ className, ...props }, ref) => (
|
|
70
|
+
<div
|
|
71
|
+
ref={ref}
|
|
72
|
+
className={cn('flex items-center p-6 pt-0', className)}
|
|
73
|
+
{...props}
|
|
74
|
+
/>
|
|
75
|
+
));
|
|
76
|
+
CardFooter.displayName = 'CardFooter';
|
|
77
|
+
|
|
78
|
+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
|
|
4
|
+
export interface CheckboxProps
|
|
5
|
+
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {
|
|
6
|
+
label?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
|
10
|
+
({ className, label, id, ...props }, ref) => {
|
|
11
|
+
const checkboxId = id || React.useId();
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div className="flex items-center">
|
|
15
|
+
<input
|
|
16
|
+
type="checkbox"
|
|
17
|
+
id={checkboxId}
|
|
18
|
+
ref={ref}
|
|
19
|
+
className={cn(
|
|
20
|
+
'h-4 w-4 rounded border-gray-300 text-primary focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
{label && (
|
|
26
|
+
<label
|
|
27
|
+
htmlFor={checkboxId}
|
|
28
|
+
className="ml-2 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
29
|
+
>
|
|
30
|
+
{label}
|
|
31
|
+
</label>
|
|
32
|
+
)}
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
Checkbox.displayName = 'Checkbox';
|
|
38
|
+
|
|
39
|
+
export { Checkbox };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
|
|
4
|
+
export interface ContainerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const Container = React.forwardRef<HTMLDivElement, ContainerProps>(
|
|
9
|
+
({ className, size = 'lg', ...props }, ref) => {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
ref={ref}
|
|
13
|
+
className={cn(
|
|
14
|
+
'mx-auto w-full px-4 sm:px-6 lg:px-8',
|
|
15
|
+
{
|
|
16
|
+
'max-w-screen-sm': size === 'sm',
|
|
17
|
+
'max-w-screen-md': size === 'md',
|
|
18
|
+
'max-w-screen-lg': size === 'lg',
|
|
19
|
+
'max-w-screen-xl': size === 'xl',
|
|
20
|
+
'max-w-full': size === 'full',
|
|
21
|
+
},
|
|
22
|
+
className
|
|
23
|
+
)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
Container.displayName = 'Container';
|
|
30
|
+
|
|
31
|
+
export { Container };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
|
|
4
|
+
export interface GridProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
cols?: 1 | 2 | 3 | 4 | 5 | 6 | 12;
|
|
6
|
+
gap?: 'sm' | 'md' | 'lg' | 'xl';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const Grid = React.forwardRef<HTMLDivElement, GridProps>(
|
|
10
|
+
({ className, cols = 3, gap = 'md', ...props }, ref) => {
|
|
11
|
+
return (
|
|
12
|
+
<div
|
|
13
|
+
ref={ref}
|
|
14
|
+
className={cn(
|
|
15
|
+
'grid',
|
|
16
|
+
{
|
|
17
|
+
'grid-cols-1': cols === 1,
|
|
18
|
+
'grid-cols-1 sm:grid-cols-2': cols === 2,
|
|
19
|
+
'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3': cols === 3,
|
|
20
|
+
'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4': cols === 4,
|
|
21
|
+
'grid-cols-1 sm:grid-cols-2 lg:grid-cols-5': cols === 5,
|
|
22
|
+
'grid-cols-1 sm:grid-cols-2 lg:grid-cols-6': cols === 6,
|
|
23
|
+
'grid-cols-1 sm:grid-cols-2 lg:grid-cols-12': cols === 12,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
'gap-2': gap === 'sm',
|
|
27
|
+
'gap-4': gap === 'md',
|
|
28
|
+
'gap-6': gap === 'lg',
|
|
29
|
+
'gap-8': gap === 'xl',
|
|
30
|
+
},
|
|
31
|
+
className
|
|
32
|
+
)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
Grid.displayName = 'Grid';
|
|
39
|
+
|
|
40
|
+
export { Grid };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
|
|
4
|
+
export interface InputProps
|
|
5
|
+
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
|
6
|
+
|
|
7
|
+
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
8
|
+
({ className, type, ...props }, ref) => {
|
|
9
|
+
return (
|
|
10
|
+
<input
|
|
11
|
+
type={type}
|
|
12
|
+
className={cn(
|
|
13
|
+
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
14
|
+
className
|
|
15
|
+
)}
|
|
16
|
+
ref={ref}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
Input.displayName = 'Input';
|
|
23
|
+
|
|
24
|
+
export { Input };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
import { cn } from '../utils/cn';
|
|
4
|
+
|
|
5
|
+
const labelVariants = cva(
|
|
6
|
+
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
export interface LabelProps
|
|
10
|
+
extends React.LabelHTMLAttributes<HTMLLabelElement>,
|
|
11
|
+
VariantProps<typeof labelVariants> {}
|
|
12
|
+
|
|
13
|
+
const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
|
|
14
|
+
({ className, ...props }, ref) => (
|
|
15
|
+
<label
|
|
16
|
+
ref={ref}
|
|
17
|
+
className={cn(labelVariants(), className)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
);
|
|
22
|
+
Label.displayName = 'Label';
|
|
23
|
+
|
|
24
|
+
export { Label };
|