@aiready/components 0.1.31 → 0.1.32
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 +2 -2
- package/dist/charts/ForceDirectedGraph.js +49 -13
- package/dist/charts/ForceDirectedGraph.js.map +1 -1
- package/dist/components/badge.js.map +1 -1
- package/dist/components/button.js.map +1 -1
- package/dist/components/card.js.map +1 -1
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/container.js.map +1 -1
- package/dist/components/grid.js.map +1 -1
- package/dist/components/input.d.ts +1 -2
- package/dist/components/input.js.map +1 -1
- package/dist/components/label.js +1 -8
- package/dist/components/label.js.map +1 -1
- package/dist/components/radio-group.js.map +1 -1
- package/dist/components/select.js.map +1 -1
- package/dist/components/separator.js.map +1 -1
- package/dist/components/stack.js.map +1 -1
- package/dist/components/switch.js +29 -22
- package/dist/components/switch.js.map +1 -1
- package/dist/components/textarea.d.ts +1 -2
- package/dist/components/textarea.js.map +1 -1
- package/dist/hooks/useD3.js.map +1 -1
- package/dist/hooks/useDebounce.js.map +1 -1
- package/dist/hooks/useForceSimulation.d.ts +1 -0
- package/dist/hooks/useForceSimulation.js +37 -14
- package/dist/hooks/useForceSimulation.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +337 -141
- package/dist/index.js.map +1 -1
- package/dist/utils/cn.js.map +1 -1
- package/dist/utils/colors.js.map +1 -1
- package/dist/utils/formatters.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/smoke.test.js +1 -1
- package/src/__tests__/smoke.test.ts +3 -3
- package/src/charts/ForceDirectedGraph.tsx +583 -517
- package/src/charts/GraphControls.tsx +5 -2
- package/src/charts/LinkItem.tsx +17 -5
- package/src/charts/NodeItem.tsx +17 -2
- package/src/code-block/CodeBlock.tsx +53 -16
- package/src/code-block/index.ts +1 -1
- package/src/components/badge.tsx +3 -2
- package/src/components/button.tsx +3 -2
- package/src/components/card.tsx +8 -1
- package/src/components/checkbox.tsx +6 -4
- package/src/components/container.tsx +1 -1
- package/src/components/grid.tsx +1 -1
- package/src/components/input.tsx +2 -3
- package/src/components/label.tsx +4 -7
- package/src/components/radio-group.tsx +5 -3
- package/src/components/select.tsx +5 -3
- package/src/components/separator.tsx +1 -1
- package/src/components/stack.tsx +1 -1
- package/src/components/switch.tsx +15 -7
- package/src/components/textarea.tsx +2 -3
- package/src/data-display/ScoreBar.tsx +52 -15
- package/src/data-display/index.ts +7 -1
- package/src/feedback/ErrorDisplay.tsx +17 -4
- package/src/feedback/LoadingSpinner.tsx +8 -3
- package/src/feedback/index.ts +12 -2
- package/src/hooks/useD3.ts +1 -3
- package/src/hooks/useDebounce.ts +1 -1
- package/src/hooks/useForceSimulation.ts +142 -44
- package/src/index.ts +29 -9
- package/src/navigation/Breadcrumb.tsx +17 -8
- package/src/navigation/index.ts +5 -1
- package/src/theme/ThemeProvider.tsx +11 -3
- package/src/theme/index.ts +6 -1
- package/src/utils/cn.ts +1 -1
- package/src/utils/colors.ts +1 -1
- package/src/utils/formatters.ts +1 -1
- package/src/utils/score.ts +3 -1
- package/tailwind.config.js +1 -1
|
@@ -89,7 +89,6 @@ export const GraphControls: React.FC<GraphControlsProps> = ({
|
|
|
89
89
|
position = 'top-left',
|
|
90
90
|
className,
|
|
91
91
|
}) => {
|
|
92
|
-
|
|
93
92
|
if (!visible) return null;
|
|
94
93
|
|
|
95
94
|
const positionClasses: Record<string, string> = {
|
|
@@ -150,7 +149,11 @@ export const GraphControls: React.FC<GraphControlsProps> = ({
|
|
|
150
149
|
onClick={() => onManualLayoutToggle?.(!manualLayout)}
|
|
151
150
|
active={manualLayout}
|
|
152
151
|
icon="🔧"
|
|
153
|
-
label={
|
|
152
|
+
label={
|
|
153
|
+
manualLayout
|
|
154
|
+
? 'Manual layout: ON (drag freely)'
|
|
155
|
+
: 'Manual layout: OFF (forces active)'
|
|
156
|
+
}
|
|
154
157
|
/>
|
|
155
158
|
|
|
156
159
|
{/* Divider */}
|
package/src/charts/LinkItem.tsx
CHANGED
|
@@ -9,19 +9,31 @@ export interface LinkItemProps {
|
|
|
9
9
|
nodes?: GraphNode[]; // Optional nodes array to resolve string IDs to node objects
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export const LinkItem: React.FC<LinkItemProps> = ({
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
export const LinkItem: React.FC<LinkItemProps> = ({
|
|
13
|
+
link,
|
|
14
|
+
onClick,
|
|
15
|
+
defaultWidth,
|
|
16
|
+
showLabel = true,
|
|
17
|
+
nodes = [],
|
|
18
|
+
}) => {
|
|
19
|
+
const src =
|
|
20
|
+
(link.source as any)?.id ??
|
|
21
|
+
(typeof link.source === 'string' ? link.source : undefined);
|
|
22
|
+
const tgt =
|
|
23
|
+
(link.target as any)?.id ??
|
|
24
|
+
(typeof link.target === 'string' ? link.target : undefined);
|
|
15
25
|
|
|
16
26
|
// Helper to get node position from source/target (which could be node object or string ID)
|
|
17
|
-
const getNodePosition = (
|
|
27
|
+
const getNodePosition = (
|
|
28
|
+
nodeOrId: string | GraphNode
|
|
29
|
+
): { x: number; y: number } | null => {
|
|
18
30
|
if (typeof nodeOrId === 'object' && nodeOrId !== null) {
|
|
19
31
|
// It's a node object
|
|
20
32
|
const node = nodeOrId as GraphNode;
|
|
21
33
|
return { x: node.x ?? 0, y: node.y ?? 0 };
|
|
22
34
|
} else if (typeof nodeOrId === 'string') {
|
|
23
35
|
// It's a string ID, try to find in nodes array
|
|
24
|
-
const found = nodes.find(n => n.id === nodeOrId);
|
|
36
|
+
const found = nodes.find((n) => n.id === nodeOrId);
|
|
25
37
|
if (found) return { x: found.x ?? 0, y: found.y ?? 0 };
|
|
26
38
|
}
|
|
27
39
|
return null;
|
package/src/charts/NodeItem.tsx
CHANGED
|
@@ -56,10 +56,25 @@ export const NodeItem: React.FC<NodeItemProps> = ({
|
|
|
56
56
|
opacity={isHovered || isSelected ? 1 : 0.9}
|
|
57
57
|
/>
|
|
58
58
|
{pinned && (
|
|
59
|
-
<circle
|
|
59
|
+
<circle
|
|
60
|
+
r={nodeSize + 4}
|
|
61
|
+
fill="none"
|
|
62
|
+
stroke="#ff6b6b"
|
|
63
|
+
strokeWidth={1}
|
|
64
|
+
opacity={0.5}
|
|
65
|
+
className="pointer-events-none"
|
|
66
|
+
/>
|
|
60
67
|
)}
|
|
61
68
|
{showLabel && node.label && (
|
|
62
|
-
<text
|
|
69
|
+
<text
|
|
70
|
+
y={nodeSize + 15}
|
|
71
|
+
fill="#333"
|
|
72
|
+
fontSize="12"
|
|
73
|
+
textAnchor="middle"
|
|
74
|
+
dominantBaseline="middle"
|
|
75
|
+
pointerEvents="none"
|
|
76
|
+
className="select-none"
|
|
77
|
+
>
|
|
63
78
|
{node.label}
|
|
64
79
|
</text>
|
|
65
80
|
)}
|
|
@@ -13,8 +13,8 @@ export interface CodeBlockProps {
|
|
|
13
13
|
// Dedent helper - removes common leading indentation
|
|
14
14
|
function dedentCode(code: string): string {
|
|
15
15
|
// Normalize tabs to two spaces
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
const normalized = code.replace(/\t/g, ' ').replace(/[ \t]+$/gm, '');
|
|
17
|
+
|
|
18
18
|
const lines = normalized.split('\n');
|
|
19
19
|
if (lines.length <= 1) return normalized.trim();
|
|
20
20
|
|
|
@@ -23,7 +23,7 @@ function dedentCode(code: string): string {
|
|
|
23
23
|
while (start < lines.length && lines[start].trim() === '') start++;
|
|
24
24
|
let end = lines.length - 1;
|
|
25
25
|
while (end >= 0 && lines[end].trim() === '') end--;
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
if (start > end) return '';
|
|
28
28
|
const relevantLines = lines.slice(start, end + 1);
|
|
29
29
|
|
|
@@ -35,10 +35,15 @@ function dedentCode(code: string): string {
|
|
|
35
35
|
}, Infinity);
|
|
36
36
|
|
|
37
37
|
// Remove common indentation
|
|
38
|
-
const dedented =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
const dedented =
|
|
39
|
+
minIndent === Infinity || minIndent === 0
|
|
40
|
+
? relevantLines.join('\n')
|
|
41
|
+
: relevantLines
|
|
42
|
+
.map((l) =>
|
|
43
|
+
l.startsWith(' '.repeat(minIndent)) ? l.slice(minIndent) : l
|
|
44
|
+
)
|
|
45
|
+
.join('\n');
|
|
46
|
+
|
|
42
47
|
return dedented;
|
|
43
48
|
}
|
|
44
49
|
|
|
@@ -73,12 +78,32 @@ function CopyButton({ code }: { code: string }) {
|
|
|
73
78
|
title={copied ? 'Copied!' : 'Copy code'}
|
|
74
79
|
>
|
|
75
80
|
{copied ? (
|
|
76
|
-
<svg
|
|
77
|
-
|
|
81
|
+
<svg
|
|
82
|
+
className="h-4 w-4"
|
|
83
|
+
fill="none"
|
|
84
|
+
viewBox="0 0 24 24"
|
|
85
|
+
strokeWidth="1.5"
|
|
86
|
+
stroke="currentColor"
|
|
87
|
+
>
|
|
88
|
+
<path
|
|
89
|
+
strokeLinecap="round"
|
|
90
|
+
strokeLinejoin="round"
|
|
91
|
+
d="M4.5 12.75l6 6 9-13.5"
|
|
92
|
+
/>
|
|
78
93
|
</svg>
|
|
79
94
|
) : (
|
|
80
|
-
<svg
|
|
81
|
-
|
|
95
|
+
<svg
|
|
96
|
+
className="h-4 w-4"
|
|
97
|
+
fill="none"
|
|
98
|
+
viewBox="0 0 24 24"
|
|
99
|
+
strokeWidth="1.5"
|
|
100
|
+
stroke="currentColor"
|
|
101
|
+
>
|
|
102
|
+
<path
|
|
103
|
+
strokeLinecap="round"
|
|
104
|
+
strokeLinejoin="round"
|
|
105
|
+
d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"
|
|
106
|
+
/>
|
|
82
107
|
</svg>
|
|
83
108
|
)}
|
|
84
109
|
</button>
|
|
@@ -100,7 +125,9 @@ export function CodeBlock({
|
|
|
100
125
|
// Handle template literal children
|
|
101
126
|
try {
|
|
102
127
|
const raw = React.Children.toArray(children)
|
|
103
|
-
.map((c) =>
|
|
128
|
+
.map((c) =>
|
|
129
|
+
typeof c === 'string' ? c : typeof c === 'number' ? String(c) : ''
|
|
130
|
+
)
|
|
104
131
|
.join('');
|
|
105
132
|
return dedentCode(raw);
|
|
106
133
|
} catch {
|
|
@@ -109,7 +136,9 @@ export function CodeBlock({
|
|
|
109
136
|
}, [children]);
|
|
110
137
|
|
|
111
138
|
return (
|
|
112
|
-
<div
|
|
139
|
+
<div
|
|
140
|
+
className={`group relative my-4 overflow-hidden rounded-xl border border-slate-700 bg-slate-900 shadow-lg ${className}`}
|
|
141
|
+
>
|
|
113
142
|
{/* Header bar */}
|
|
114
143
|
{showHeader && (
|
|
115
144
|
<div className="flex items-center justify-between border-b border-slate-700 bg-slate-800/50 px-4 py-2">
|
|
@@ -138,10 +167,18 @@ export function CodeBlock({
|
|
|
138
167
|
}
|
|
139
168
|
|
|
140
169
|
// Inline code component
|
|
141
|
-
export function InlineCode({
|
|
170
|
+
export function InlineCode({
|
|
171
|
+
children,
|
|
172
|
+
className = '',
|
|
173
|
+
}: {
|
|
174
|
+
children: React.ReactNode;
|
|
175
|
+
className?: string;
|
|
176
|
+
}) {
|
|
142
177
|
return (
|
|
143
|
-
<code
|
|
178
|
+
<code
|
|
179
|
+
className={`rounded-md bg-slate-100 px-1.5 py-0.5 text-sm font-mono text-slate-800 ${className}`}
|
|
180
|
+
>
|
|
144
181
|
{children}
|
|
145
182
|
</code>
|
|
146
183
|
);
|
|
147
|
-
}
|
|
184
|
+
}
|
package/src/code-block/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { CodeBlock, InlineCode, type CodeBlockProps } from './CodeBlock';
|
|
1
|
+
export { CodeBlock, InlineCode, type CodeBlockProps } from './CodeBlock';
|
package/src/components/badge.tsx
CHANGED
|
@@ -23,7 +23,8 @@ const badgeVariants = cva(
|
|
|
23
23
|
);
|
|
24
24
|
|
|
25
25
|
export interface BadgeProps
|
|
26
|
-
extends
|
|
26
|
+
extends
|
|
27
|
+
React.HTMLAttributes<HTMLDivElement>,
|
|
27
28
|
VariantProps<typeof badgeVariants> {}
|
|
28
29
|
|
|
29
30
|
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
@@ -32,4 +33,4 @@ function Badge({ className, variant, ...props }: BadgeProps) {
|
|
|
32
33
|
);
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
export { Badge, badgeVariants };
|
|
36
|
+
export { Badge, badgeVariants };
|
|
@@ -32,7 +32,8 @@ const buttonVariants = cva(
|
|
|
32
32
|
);
|
|
33
33
|
|
|
34
34
|
export interface ButtonProps
|
|
35
|
-
extends
|
|
35
|
+
extends
|
|
36
|
+
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
36
37
|
VariantProps<typeof buttonVariants> {
|
|
37
38
|
asChild?: boolean;
|
|
38
39
|
}
|
|
@@ -50,4 +51,4 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
50
51
|
);
|
|
51
52
|
Button.displayName = 'Button';
|
|
52
53
|
|
|
53
|
-
export { Button, buttonVariants };
|
|
54
|
+
export { Button, buttonVariants };
|
package/src/components/card.tsx
CHANGED
|
@@ -75,4 +75,11 @@ const CardFooter = React.forwardRef<
|
|
|
75
75
|
));
|
|
76
76
|
CardFooter.displayName = 'CardFooter';
|
|
77
77
|
|
|
78
|
-
export {
|
|
78
|
+
export {
|
|
79
|
+
Card,
|
|
80
|
+
CardHeader,
|
|
81
|
+
CardFooter,
|
|
82
|
+
CardTitle,
|
|
83
|
+
CardDescription,
|
|
84
|
+
CardContent,
|
|
85
|
+
};
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { cn } from '../utils/cn';
|
|
3
3
|
|
|
4
|
-
export interface CheckboxProps
|
|
5
|
-
|
|
4
|
+
export interface CheckboxProps extends Omit<
|
|
5
|
+
React.InputHTMLAttributes<HTMLInputElement>,
|
|
6
|
+
'type'
|
|
7
|
+
> {
|
|
6
8
|
label?: string;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
|
10
12
|
({ className, label, id, ...props }, ref) => {
|
|
11
13
|
const checkboxId = id || React.useId();
|
|
12
|
-
|
|
14
|
+
|
|
13
15
|
return (
|
|
14
16
|
<div className="flex items-center">
|
|
15
17
|
<input
|
|
@@ -36,4 +38,4 @@ const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
|
|
36
38
|
);
|
|
37
39
|
Checkbox.displayName = 'Checkbox';
|
|
38
40
|
|
|
39
|
-
export { Checkbox };
|
|
41
|
+
export { Checkbox };
|
package/src/components/grid.tsx
CHANGED
package/src/components/input.tsx
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { cn } from '../utils/cn';
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
|
4
|
+
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
|
|
6
5
|
|
|
7
6
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
8
7
|
({ className, type, ...props }, ref) => {
|
|
@@ -21,4 +20,4 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
21
20
|
);
|
|
22
21
|
Input.displayName = 'Input';
|
|
23
22
|
|
|
24
|
-
export { Input };
|
|
23
|
+
export { Input };
|
package/src/components/label.tsx
CHANGED
|
@@ -7,18 +7,15 @@ const labelVariants = cva(
|
|
|
7
7
|
);
|
|
8
8
|
|
|
9
9
|
export interface LabelProps
|
|
10
|
-
extends
|
|
10
|
+
extends
|
|
11
|
+
React.LabelHTMLAttributes<HTMLLabelElement>,
|
|
11
12
|
VariantProps<typeof labelVariants> {}
|
|
12
13
|
|
|
13
14
|
const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
|
|
14
15
|
({ className, ...props }, ref) => (
|
|
15
|
-
<label
|
|
16
|
-
ref={ref}
|
|
17
|
-
className={cn(labelVariants(), className)}
|
|
18
|
-
{...props}
|
|
19
|
-
/>
|
|
16
|
+
<label ref={ref} className={cn(labelVariants(), className)} {...props} />
|
|
20
17
|
)
|
|
21
18
|
);
|
|
22
19
|
Label.displayName = 'Label';
|
|
23
20
|
|
|
24
|
-
export { Label };
|
|
21
|
+
export { Label };
|
|
@@ -7,8 +7,10 @@ export interface RadioOption {
|
|
|
7
7
|
disabled?: boolean;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export interface RadioGroupProps
|
|
11
|
-
|
|
10
|
+
export interface RadioGroupProps extends Omit<
|
|
11
|
+
React.HTMLAttributes<HTMLDivElement>,
|
|
12
|
+
'onChange'
|
|
13
|
+
> {
|
|
12
14
|
options: RadioOption[];
|
|
13
15
|
value?: string;
|
|
14
16
|
onChange?: (value: string) => void;
|
|
@@ -68,4 +70,4 @@ const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>(
|
|
|
68
70
|
);
|
|
69
71
|
RadioGroup.displayName = 'RadioGroup';
|
|
70
72
|
|
|
71
|
-
export { RadioGroup };
|
|
73
|
+
export { RadioGroup };
|
|
@@ -7,8 +7,10 @@ export interface SelectOption {
|
|
|
7
7
|
disabled?: boolean;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export interface SelectProps
|
|
11
|
-
|
|
10
|
+
export interface SelectProps extends Omit<
|
|
11
|
+
React.SelectHTMLAttributes<HTMLSelectElement>,
|
|
12
|
+
'onChange'
|
|
13
|
+
> {
|
|
12
14
|
options: SelectOption[];
|
|
13
15
|
placeholder?: string;
|
|
14
16
|
onChange?: (value: string) => void;
|
|
@@ -50,4 +52,4 @@ const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
|
|
|
50
52
|
);
|
|
51
53
|
Select.displayName = 'Select';
|
|
52
54
|
|
|
53
|
-
export { Select };
|
|
55
|
+
export { Select };
|
package/src/components/stack.tsx
CHANGED
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { cn } from '../utils/cn';
|
|
3
3
|
|
|
4
|
-
export interface SwitchProps
|
|
5
|
-
|
|
4
|
+
export interface SwitchProps extends Omit<
|
|
5
|
+
React.InputHTMLAttributes<HTMLInputElement>,
|
|
6
|
+
'type'
|
|
7
|
+
> {
|
|
6
8
|
label?: string;
|
|
7
9
|
onCheckedChange?: (checked: boolean) => void;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(
|
|
11
|
-
(
|
|
13
|
+
(
|
|
14
|
+
{ className, label, id, checked, onCheckedChange, onChange, ...props },
|
|
15
|
+
ref
|
|
16
|
+
) => {
|
|
12
17
|
const switchId = id || React.useId();
|
|
13
|
-
|
|
18
|
+
|
|
14
19
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
15
20
|
onChange?.(e);
|
|
16
21
|
onCheckedChange?.(e.target.checked);
|
|
17
22
|
};
|
|
18
|
-
|
|
23
|
+
|
|
19
24
|
return (
|
|
20
25
|
<div className="flex items-center">
|
|
21
|
-
<label
|
|
26
|
+
<label
|
|
27
|
+
htmlFor={switchId}
|
|
28
|
+
className="relative inline-flex cursor-pointer items-center"
|
|
29
|
+
>
|
|
22
30
|
<input
|
|
23
31
|
type="checkbox"
|
|
24
32
|
id={switchId}
|
|
@@ -46,4 +54,4 @@ const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(
|
|
|
46
54
|
);
|
|
47
55
|
Switch.displayName = 'Switch';
|
|
48
56
|
|
|
49
|
-
export { Switch };
|
|
57
|
+
export { Switch };
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { cn } from '../utils/cn';
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
|
4
|
+
export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
|
|
6
5
|
|
|
7
6
|
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
8
7
|
({ className, ...props }, ref) => {
|
|
@@ -20,4 +19,4 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
20
19
|
);
|
|
21
20
|
Textarea.displayName = 'Textarea';
|
|
22
21
|
|
|
23
|
-
export { Textarea };
|
|
22
|
+
export { Textarea };
|
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
import { getRating as getCoreRating } from '@aiready/core/client';
|
|
4
4
|
import { cn } from '../utils/cn';
|
|
5
5
|
|
|
6
|
-
export type ScoreRating =
|
|
6
|
+
export type ScoreRating =
|
|
7
|
+
| 'excellent'
|
|
8
|
+
| 'good'
|
|
9
|
+
| 'fair'
|
|
10
|
+
| 'needs-work'
|
|
11
|
+
| 'critical';
|
|
7
12
|
|
|
8
13
|
export interface ScoreBarProps {
|
|
9
14
|
score: number;
|
|
@@ -14,11 +19,22 @@ export interface ScoreBarProps {
|
|
|
14
19
|
className?: string;
|
|
15
20
|
}
|
|
16
21
|
|
|
17
|
-
const ratingConfig: Record<
|
|
18
|
-
|
|
22
|
+
const ratingConfig: Record<
|
|
23
|
+
ScoreRating,
|
|
24
|
+
{ color: string; bgColor: string; label: string }
|
|
25
|
+
> = {
|
|
26
|
+
excellent: {
|
|
27
|
+
color: 'bg-green-500',
|
|
28
|
+
bgColor: 'bg-green-100',
|
|
29
|
+
label: 'Excellent',
|
|
30
|
+
},
|
|
19
31
|
good: { color: 'bg-emerald-500', bgColor: 'bg-emerald-100', label: 'Good' },
|
|
20
32
|
fair: { color: 'bg-amber-500', bgColor: 'bg-amber-100', label: 'Fair' },
|
|
21
|
-
'needs-work': {
|
|
33
|
+
'needs-work': {
|
|
34
|
+
color: 'bg-orange-500',
|
|
35
|
+
bgColor: 'bg-orange-100',
|
|
36
|
+
label: 'Needs Work',
|
|
37
|
+
},
|
|
22
38
|
critical: { color: 'bg-red-500', bgColor: 'bg-red-100', label: 'Critical' },
|
|
23
39
|
};
|
|
24
40
|
|
|
@@ -26,11 +42,11 @@ const ratingConfig: Record<ScoreRating, { color: string; bgColor: string; label:
|
|
|
26
42
|
function getRating(score: number): ScoreRating {
|
|
27
43
|
const coreRating = getCoreRating(score);
|
|
28
44
|
const ratingMap: Record<string, ScoreRating> = {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
45
|
+
Excellent: 'excellent',
|
|
46
|
+
Good: 'good',
|
|
47
|
+
Fair: 'fair',
|
|
32
48
|
'Needs Work': 'needs-work',
|
|
33
|
-
|
|
49
|
+
Critical: 'critical',
|
|
34
50
|
};
|
|
35
51
|
return ratingMap[coreRating] || 'critical';
|
|
36
52
|
}
|
|
@@ -66,7 +82,11 @@ export function ScoreBar({
|
|
|
66
82
|
</div>
|
|
67
83
|
<div className={cn('w-full rounded-full bg-slate-200', sizes.height)}>
|
|
68
84
|
<div
|
|
69
|
-
className={cn(
|
|
85
|
+
className={cn(
|
|
86
|
+
'rounded-full transition-all duration-500',
|
|
87
|
+
config.color,
|
|
88
|
+
sizes.height
|
|
89
|
+
)}
|
|
70
90
|
style={{ width: `${percentage}%` }}
|
|
71
91
|
/>
|
|
72
92
|
</div>
|
|
@@ -85,15 +105,30 @@ export interface ScoreCardProps {
|
|
|
85
105
|
className?: string;
|
|
86
106
|
}
|
|
87
107
|
|
|
88
|
-
export function ScoreCard({
|
|
108
|
+
export function ScoreCard({
|
|
109
|
+
score,
|
|
110
|
+
title,
|
|
111
|
+
breakdown,
|
|
112
|
+
className,
|
|
113
|
+
}: ScoreCardProps) {
|
|
89
114
|
const rating = getRating(score);
|
|
90
115
|
const config = ratingConfig[rating];
|
|
91
116
|
|
|
92
117
|
return (
|
|
93
|
-
<div
|
|
118
|
+
<div
|
|
119
|
+
className={cn(
|
|
120
|
+
'rounded-xl border-2 border-slate-200 bg-white p-6 shadow-lg',
|
|
121
|
+
className
|
|
122
|
+
)}
|
|
123
|
+
>
|
|
94
124
|
<div className="mb-4">
|
|
95
125
|
<div className="text-4xl font-black text-slate-900">{score}/100</div>
|
|
96
|
-
<div
|
|
126
|
+
<div
|
|
127
|
+
className={cn(
|
|
128
|
+
'text-lg font-bold',
|
|
129
|
+
`text-${rating === 'excellent' ? 'green' : rating === 'good' ? 'emerald' : rating === 'fair' ? 'amber' : rating === 'needs-work' ? 'orange' : 'red'}-600`
|
|
130
|
+
)}
|
|
131
|
+
>
|
|
97
132
|
{config.label} Rating
|
|
98
133
|
</div>
|
|
99
134
|
{title && <p className="text-sm text-slate-600 mt-1">{title}</p>}
|
|
@@ -114,9 +149,11 @@ export function ScoreCard({ score, title, breakdown, className }: ScoreCardProps
|
|
|
114
149
|
|
|
115
150
|
{breakdown && breakdown.length > 0 && (
|
|
116
151
|
<div className="mt-4 text-xs text-slate-600 bg-slate-50 p-3 rounded-lg">
|
|
117
|
-
<strong>Formula:</strong>
|
|
118
|
-
|
|
119
|
-
|
|
152
|
+
<strong>Formula:</strong>{' '}
|
|
153
|
+
{breakdown
|
|
154
|
+
.map((item) => `${item.score}×${item.weight || 1}`)
|
|
155
|
+
.join(' + ')}{' '}
|
|
156
|
+
/ 100 = {score}
|
|
120
157
|
</div>
|
|
121
158
|
)}
|
|
122
159
|
</div>
|
|
@@ -41,7 +41,13 @@ export function ErrorDisplay({
|
|
|
41
41
|
onClick={retry}
|
|
42
42
|
className="mt-2 inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 transition-colors"
|
|
43
43
|
>
|
|
44
|
-
<svg
|
|
44
|
+
<svg
|
|
45
|
+
className="h-4 w-4"
|
|
46
|
+
fill="none"
|
|
47
|
+
viewBox="0 0 24 24"
|
|
48
|
+
strokeWidth="1.5"
|
|
49
|
+
stroke="currentColor"
|
|
50
|
+
>
|
|
45
51
|
<path
|
|
46
52
|
strokeLinecap="round"
|
|
47
53
|
strokeLinejoin="round"
|
|
@@ -65,13 +71,20 @@ export interface EmptyStateProps {
|
|
|
65
71
|
};
|
|
66
72
|
}
|
|
67
73
|
|
|
68
|
-
export function EmptyState({
|
|
74
|
+
export function EmptyState({
|
|
75
|
+
title,
|
|
76
|
+
description,
|
|
77
|
+
icon,
|
|
78
|
+
action,
|
|
79
|
+
}: EmptyStateProps) {
|
|
69
80
|
return (
|
|
70
81
|
<div className="flex flex-col items-center justify-center min-h-[200px] gap-4 p-8">
|
|
71
82
|
{icon && <div className="rounded-full bg-slate-100 p-3">{icon}</div>}
|
|
72
83
|
<div className="text-center">
|
|
73
84
|
<h3 className="text-lg font-semibold text-slate-900">{title}</h3>
|
|
74
|
-
{description &&
|
|
85
|
+
{description && (
|
|
86
|
+
<p className="mt-2 text-sm text-slate-500">{description}</p>
|
|
87
|
+
)}
|
|
75
88
|
</div>
|
|
76
89
|
{action && (
|
|
77
90
|
<button
|
|
@@ -83,4 +96,4 @@ export function EmptyState({ title, description, icon, action }: EmptyStateProps
|
|
|
83
96
|
)}
|
|
84
97
|
</div>
|
|
85
98
|
);
|
|
86
|
-
}
|
|
99
|
+
}
|
|
@@ -11,7 +11,10 @@ const sizeClasses = {
|
|
|
11
11
|
lg: 'h-12 w-12',
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
export function LoadingSpinner({
|
|
14
|
+
export function LoadingSpinner({
|
|
15
|
+
size = 'md',
|
|
16
|
+
className = '',
|
|
17
|
+
}: LoadingSpinnerProps) {
|
|
15
18
|
return (
|
|
16
19
|
<div className={`flex items-center justify-center ${className}`}>
|
|
17
20
|
<div
|
|
@@ -25,11 +28,13 @@ export interface LoadingOverlayProps {
|
|
|
25
28
|
message?: string;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
export function LoadingOverlay({
|
|
31
|
+
export function LoadingOverlay({
|
|
32
|
+
message = 'Loading...',
|
|
33
|
+
}: LoadingOverlayProps) {
|
|
29
34
|
return (
|
|
30
35
|
<div className="flex flex-col items-center justify-center min-h-[200px] gap-4">
|
|
31
36
|
<LoadingSpinner size="lg" />
|
|
32
37
|
<p className="text-sm text-slate-500 animate-pulse">{message}</p>
|
|
33
38
|
</div>
|
|
34
39
|
);
|
|
35
|
-
}
|
|
40
|
+
}
|