Skip to content

Commit a8319f1

Browse files
committed
feat(ui): add missing shadcn/ui component library and types
- Create ui/src/components/ui/ with 9 shadcn/ui components: alert, badge, button, card, input, label, progress, select, tabs - Create ui/src/lib/utils.ts with cn() className merge utility - Create ui/src/lib/types.ts with UI type definitions (AssetType, AssetInfo, Balance, AssetHolding, Portfolio, DeploymentOptions) - All components include dark: Tailwind variants for theme support - Removed animation classes from select.tsx that required missing tailwindcss-animate plugin - Uses existing Radix UI primitives from ui/package.json
1 parent b1bc880 commit a8319f1

11 files changed

Lines changed: 575 additions & 0 deletions

File tree

ui/src/components/ui/alert.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import * as React from 'react';
2+
import { cva, type VariantProps } from 'class-variance-authority';
3+
import { cn } from '@/lib/utils';
4+
5+
const alertVariants = cva(
6+
'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-gray-950 dark:[&>svg]:text-gray-50',
7+
{
8+
variants: {
9+
variant: {
10+
default: 'bg-white text-gray-950 dark:bg-gray-800 dark:text-gray-50',
11+
destructive:
12+
'border-red-500/50 text-red-700 bg-red-50 dark:border-red-500/30 dark:text-red-300 dark:bg-red-950/30 [&>svg]:text-red-600 dark:[&>svg]:text-red-400',
13+
},
14+
},
15+
defaultVariants: {
16+
variant: 'default',
17+
},
18+
}
19+
);
20+
21+
const Alert = React.forwardRef<
22+
HTMLDivElement,
23+
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
24+
>(({ className, variant, ...props }, ref) => (
25+
<div
26+
ref={ref}
27+
role="alert"
28+
className={cn(alertVariants({ variant }), className)}
29+
{...props}
30+
/>
31+
));
32+
Alert.displayName = 'Alert';
33+
34+
const AlertTitle = React.forwardRef<
35+
HTMLHeadingElement,
36+
React.HTMLAttributes<HTMLHeadingElement>
37+
>(({ className, ...props }, ref) => (
38+
<h5
39+
ref={ref}
40+
className={cn('mb-1 font-medium leading-none tracking-tight', className)}
41+
{...props}
42+
/>
43+
));
44+
AlertTitle.displayName = 'AlertTitle';
45+
46+
const AlertDescription = React.forwardRef<
47+
HTMLParagraphElement,
48+
React.HTMLAttributes<HTMLParagraphElement>
49+
>(({ className, ...props }, ref) => (
50+
<div
51+
ref={ref}
52+
className={cn('text-sm [&_p]:leading-relaxed', className)}
53+
{...props}
54+
/>
55+
));
56+
AlertDescription.displayName = 'AlertDescription';
57+
58+
export { Alert, AlertTitle, AlertDescription };

ui/src/components/ui/badge.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import * as React from 'react';
2+
import { cva, type VariantProps } from 'class-variance-authority';
3+
import { cn } from '@/lib/utils';
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-blue-500 focus:ring-offset-2 dark:focus:ring-blue-400 dark:focus:ring-offset-gray-800',
7+
{
8+
variants: {
9+
variant: {
10+
default:
11+
'border-transparent bg-blue-600 text-white shadow hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600',
12+
secondary:
13+
'border-transparent bg-gray-100 text-gray-900 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-100 dark:hover:bg-gray-600',
14+
destructive:
15+
'border-transparent bg-red-600 text-white shadow hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600',
16+
outline:
17+
'text-gray-950 dark:text-gray-50',
18+
},
19+
},
20+
defaultVariants: {
21+
variant: 'default',
22+
},
23+
}
24+
);
25+
26+
export interface BadgeProps
27+
extends React.HTMLAttributes<HTMLDivElement>,
28+
VariantProps<typeof badgeVariants> {}
29+
30+
function Badge({ className, variant, ...props }: BadgeProps) {
31+
return (
32+
<div className={cn(badgeVariants({ variant }), className)} {...props} />
33+
);
34+
}
35+
36+
export { Badge, badgeVariants };

ui/src/components/ui/button.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import * as React from 'react';
2+
import { cva, type VariantProps } from 'class-variance-authority';
3+
import { cn } from '@/lib/utils';
4+
5+
const buttonVariants = cva(
6+
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
7+
{
8+
variants: {
9+
variant: {
10+
default:
11+
'bg-blue-600 text-white shadow hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600',
12+
destructive:
13+
'bg-red-600 text-white shadow-sm hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600',
14+
outline:
15+
'border border-gray-300 bg-white shadow-sm hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 dark:hover:bg-gray-700',
16+
secondary:
17+
'bg-gray-100 text-gray-900 shadow-sm hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-100 dark:hover:bg-gray-600',
18+
ghost:
19+
'hover:bg-gray-100 dark:hover:bg-gray-800 dark:text-gray-300',
20+
link:
21+
'text-blue-600 underline-offset-4 hover:underline dark:text-blue-400',
22+
},
23+
size: {
24+
default: 'h-9 px-4 py-2',
25+
sm: 'h-8 rounded-md px-3 text-xs',
26+
lg: 'h-10 rounded-md px-8',
27+
icon: 'h-9 w-9',
28+
},
29+
},
30+
defaultVariants: {
31+
variant: 'default',
32+
size: 'default',
33+
},
34+
}
35+
);
36+
37+
export interface ButtonProps
38+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
39+
VariantProps<typeof buttonVariants> {}
40+
41+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
42+
({ className, variant, size, children, ...props }, ref) => {
43+
return (
44+
<button
45+
className={cn(buttonVariants({ variant, size, className }))}
46+
ref={ref}
47+
{...props}
48+
>
49+
{children}
50+
</button>
51+
);
52+
}
53+
);
54+
Button.displayName = 'Button';
55+
56+
export { Button, buttonVariants };

ui/src/components/ui/card.tsx

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import * as React from 'react';
2+
import { cn } from '@/lib/utils';
3+
4+
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
5+
({ className, ...props }, ref) => (
6+
<div
7+
ref={ref}
8+
className={cn(
9+
'rounded-lg border border-gray-200 bg-white text-gray-950 shadow-sm dark:border-gray-700 dark:bg-gray-800 dark:text-gray-50',
10+
className
11+
)}
12+
{...props}
13+
/>
14+
)
15+
);
16+
Card.displayName = 'Card';
17+
18+
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
19+
({ className, ...props }, ref) => (
20+
<div
21+
ref={ref}
22+
className={cn('flex flex-col space-y-1.5 p-6', className)}
23+
{...props}
24+
/>
25+
)
26+
);
27+
CardHeader.displayName = 'CardHeader';
28+
29+
const CardTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
30+
({ className, ...props }, ref) => (
31+
<h3
32+
ref={ref}
33+
className={cn('text-2xl font-semibold leading-none tracking-tight', className)}
34+
{...props}
35+
/>
36+
)
37+
);
38+
CardTitle.displayName = 'CardTitle';
39+
40+
const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
41+
({ className, ...props }, ref) => (
42+
<p
43+
ref={ref}
44+
className={cn('text-sm text-gray-500 dark:text-gray-400', className)}
45+
{...props}
46+
/>
47+
)
48+
);
49+
CardDescription.displayName = 'CardDescription';
50+
51+
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
52+
({ className, ...props }, ref) => (
53+
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
54+
)
55+
);
56+
CardContent.displayName = 'CardContent';
57+
58+
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
59+
({ className, ...props }, ref) => (
60+
<div
61+
ref={ref}
62+
className={cn('flex items-center p-6 pt-0', className)}
63+
{...props}
64+
/>
65+
)
66+
);
67+
CardFooter.displayName = 'CardFooter';
68+
69+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };

ui/src/components/ui/input.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import * as React from 'react';
2+
import { cn } from '@/lib/utils';
3+
4+
const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
5+
({ className, type, ...props }, ref) => {
6+
return (
7+
<input
8+
type={type}
9+
className={cn(
10+
'flex h-9 w-full rounded-md border border-gray-300 bg-white px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-gray-950 placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-50 dark:placeholder:text-gray-500 dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-800',
11+
className
12+
)}
13+
ref={ref}
14+
{...props}
15+
/>
16+
);
17+
}
18+
);
19+
Input.displayName = 'Input';
20+
21+
export { Input };

ui/src/components/ui/label.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use client';
2+
3+
import * as React from 'react';
4+
import * as LabelPrimitive from '@radix-ui/react-label';
5+
import { cn } from '@/lib/utils';
6+
7+
const Label = React.forwardRef<
8+
React.ElementRef<typeof LabelPrimitive.Root>,
9+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
10+
>(({ className, ...props }, ref) => (
11+
<LabelPrimitive.Root
12+
ref={ref}
13+
className={cn(
14+
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-200',
15+
className
16+
)}
17+
{...props}
18+
/>
19+
));
20+
Label.displayName = LabelPrimitive.Root.displayName;
21+
22+
export { Label };

ui/src/components/ui/progress.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use client';
2+
3+
import * as React from 'react';
4+
import { cn } from '@/lib/utils';
5+
6+
interface ProgressProps extends React.HTMLAttributes<HTMLDivElement> {
7+
value?: number;
8+
max?: number;
9+
}
10+
11+
const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
12+
({ className, value = 0, max = 100, ...props }, ref) => {
13+
const percentage = max > 0 ? Math.min(100, Math.max(0, (value / max) * 100)) : 0;
14+
15+
return (
16+
<div
17+
ref={ref}
18+
role="progressbar"
19+
aria-valuemin={0}
20+
aria-valuemax={max}
21+
aria-valuenow={value}
22+
className={cn(
23+
'relative h-4 w-full overflow-hidden rounded-full bg-gray-100 dark:bg-gray-700',
24+
className
25+
)}
26+
{...props}
27+
>
28+
<div
29+
className="h-full w-full flex-1 bg-blue-600 transition-all duration-300 ease-in-out dark:bg-blue-500"
30+
style={{ transform: `translateX(-${100 - percentage}%)` }}
31+
/>
32+
</div>
33+
);
34+
}
35+
);
36+
Progress.displayName = 'Progress';
37+
38+
export { Progress };

0 commit comments

Comments
 (0)