feat(frontend): Complete shadcn migration of skeleton and styling components

Co-authored-by: elisiariocouto <818914+elisiariocouto@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-09-15 23:31:51 +00:00
committed by Elisiário Couto
parent bfb5a7ef76
commit c83386b1d5
13 changed files with 372 additions and 125 deletions

View File

@@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-select": "^2.2.6", "@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@tailwindcss/forms": "^0.5.10", "@tailwindcss/forms": "^0.5.10",
@@ -29,6 +30,7 @@
"react-day-picker": "^9.10.0", "react-day-picker": "^9.10.0",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"recharts": "^3.2.0", "recharts": "^3.2.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7" "tailwindcss-animate": "^1.0.7"
@@ -1637,6 +1639,30 @@
} }
} }
}, },
"node_modules/@radix-ui/react-progress": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz",
"integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-primitive": "2.1.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-select": { "node_modules/@radix-ui/react-select": {
"version": "2.2.6", "version": "2.2.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz",
@@ -5765,6 +5791,16 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/sonner": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
"integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
"license": "MIT",
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
}
},
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.7.6", "version": "0.7.6",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",

View File

@@ -12,6 +12,7 @@
"dependencies": { "dependencies": {
"@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-select": "^2.2.6", "@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@tailwindcss/forms": "^0.5.10", "@tailwindcss/forms": "^0.5.10",
@@ -31,6 +32,7 @@
"react-day-picker": "^9.10.0", "react-day-picker": "^9.10.0",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"recharts": "^3.2.0", "recharts": "^3.2.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7" "tailwindcss-animate": "^1.0.7"

View File

@@ -37,23 +37,23 @@ const getStatusIndicator = (status: string) => {
}; };
case "pending": case "pending":
return { return {
color: "bg-yellow-500", color: "bg-amber-500",
tooltip: "Pending", tooltip: "Pending",
}; };
case "error": case "error":
case "failed": case "failed":
return { return {
color: "bg-red-500", color: "bg-destructive",
tooltip: "Error", tooltip: "Error",
}; };
case "inactive": case "inactive":
return { return {
color: "bg-gray-500", color: "bg-muted-foreground",
tooltip: "Inactive", tooltip: "Inactive",
}; };
default: default:
return { return {
color: "bg-blue-500", color: "bg-primary",
tooltip: status, tooltip: status,
}; };
} }
@@ -201,8 +201,8 @@ export default function AccountsOverview() {
{uniqueBanks} {uniqueBanks}
</p> </p>
</div> </div>
<div className="p-3 bg-purple-100 dark:bg-purple-900/20 rounded-full"> <div className="p-3 bg-muted rounded-full">
<Building2 className="h-6 w-6 text-purple-600" /> <Building2 className="h-6 w-6 text-muted-foreground" />
</div> </div>
</div> </div>
</CardContent> </CardContent>

View File

@@ -1,6 +1,9 @@
import { Component } from "react"; import { Component } from "react";
import type { ErrorInfo, ReactNode } from "react"; import type { ErrorInfo, ReactNode } from "react";
import { AlertTriangle, RefreshCw } from "lucide-react"; import { AlertTriangle, RefreshCw } from "lucide-react";
import { Card, CardContent } from "./ui/card";
import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
import { Button } from "./ui/button";
interface Props { interface Props {
children: ReactNode; children: ReactNode;
@@ -39,46 +42,49 @@ class ErrorBoundary extends Component<Props, State> {
} }
return ( return (
<div className="bg-white rounded-lg shadow p-6"> <Card>
<div className="flex items-center justify-center text-center"> <CardContent className="p-6">
<div> <div className="flex items-center justify-center text-center">
<AlertTriangle className="h-12 w-12 text-red-400 mx-auto mb-4" /> <div>
<h3 className="text-lg font-medium text-gray-900 mb-2"> <AlertTriangle className="h-12 w-12 text-destructive mx-auto mb-4" />
Something went wrong <h3 className="text-lg font-medium text-foreground mb-2">
</h3> Something went wrong
<p className="text-gray-600 mb-4"> </h3>
An error occurred while rendering this component. Please try <p className="text-muted-foreground mb-4">
refreshing or check the console for more details. An error occurred while rendering this component. Please try
</p> refreshing or check the console for more details.
</p>
{this.state.error && ( {this.state.error && (
<div className="bg-red-50 border border-red-200 rounded-md p-3 mb-4 text-left"> <Alert variant="destructive" className="mb-4 text-left">
<p className="text-sm font-mono text-red-800"> <AlertTriangle className="h-4 w-4" />
<strong>Error:</strong> {this.state.error.message} <AlertTitle>Error Details</AlertTitle>
</p> <AlertDescription className="space-y-2">
{this.state.error.stack && ( <p className="text-sm font-mono">
<details className="mt-2"> <strong>Error:</strong> {this.state.error.message}
<summary className="text-sm text-red-600 cursor-pointer"> </p>
Stack trace {this.state.error.stack && (
</summary> <details className="mt-2">
<pre className="text-xs text-red-700 mt-1 whitespace-pre-wrap"> <summary className="text-sm cursor-pointer">
{this.state.error.stack} Stack trace
</pre> </summary>
</details> <pre className="text-xs mt-1 whitespace-pre-wrap">
)} {this.state.error.stack}
</div> </pre>
)} </details>
)}
</AlertDescription>
</Alert>
)}
<button <Button onClick={this.handleReset}>
onClick={this.handleReset} <RefreshCw className="h-4 w-4 mr-2" />
className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors" Try Again
> </Button>
<RefreshCw className="h-4 w-4 mr-2" /> </div>
Try Again
</button>
</div> </div>
</div> </CardContent>
</div> </Card>
); );
} }

View File

@@ -1,29 +1,32 @@
import { Skeleton } from "./ui/skeleton";
import { Card, CardContent } from "./ui/card";
export default function FiltersSkeleton() { export default function FiltersSkeleton() {
return ( return (
<div className="bg-white rounded-lg shadow animate-pulse"> <Card>
<div className="px-6 py-4 border-b border-gray-200"> <div className="px-6 py-4 border-b border-border">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="h-6 bg-gray-200 rounded w-32"></div> <Skeleton className="h-6 w-32" />
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div className="h-8 bg-gray-200 rounded w-24"></div> <Skeleton className="h-8 w-24" />
<div className="h-8 bg-gray-200 rounded w-20"></div> <Skeleton className="h-8 w-20" />
</div> </div>
</div> </div>
</div> </div>
<div className="px-6 py-4 border-b border-gray-200 bg-gray-50"> <CardContent className="px-6 py-4 border-b border-border bg-muted/30">
{/* Quick Date Filters Skeleton */} {/* Quick Date Filters Skeleton */}
<div className="mb-6"> <div className="mb-6">
<div className="h-4 bg-gray-200 rounded w-32 mb-3"></div> <Skeleton className="h-4 w-32 mb-3" />
<div className="space-y-2"> <div className="space-y-2">
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
<div className="h-10 bg-gray-200 rounded-lg w-24"></div> <Skeleton className="h-10 w-24 rounded-lg" />
<div className="h-10 bg-gray-200 rounded-lg w-20"></div> <Skeleton className="h-10 w-20 rounded-lg" />
<div className="h-10 bg-gray-200 rounded-lg w-28"></div> <Skeleton className="h-10 w-28 rounded-lg" />
</div> </div>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
<div className="h-10 bg-gray-200 rounded-lg w-24"></div> <Skeleton className="h-10 w-24 rounded-lg" />
<div className="h-10 bg-gray-200 rounded-lg w-20"></div> <Skeleton className="h-10 w-20 rounded-lg" />
</div> </div>
</div> </div>
</div> </div>
@@ -31,40 +34,40 @@ export default function FiltersSkeleton() {
{/* Filter Fields Skeleton */} {/* Filter Fields Skeleton */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="sm:col-span-2 lg:col-span-1"> <div className="sm:col-span-2 lg:col-span-1">
<div className="h-4 bg-gray-200 rounded w-16 mb-1"></div> <Skeleton className="h-4 w-16 mb-1" />
<div className="h-10 bg-gray-200 rounded"></div> <Skeleton className="h-10 w-full" />
</div> </div>
<div> <div>
<div className="h-4 bg-gray-200 rounded w-16 mb-1"></div> <Skeleton className="h-4 w-16 mb-1" />
<div className="h-10 bg-gray-200 rounded"></div> <Skeleton className="h-10 w-full" />
</div> </div>
<div> <div>
<div className="h-4 bg-gray-200 rounded w-20 mb-1"></div> <Skeleton className="h-4 w-20 mb-1" />
<div className="h-10 bg-gray-200 rounded"></div> <Skeleton className="h-10 w-full" />
</div> </div>
<div> <div>
<div className="h-4 bg-gray-200 rounded w-16 mb-1"></div> <Skeleton className="h-4 w-16 mb-1" />
<div className="h-10 bg-gray-200 rounded"></div> <Skeleton className="h-10 w-full" />
</div> </div>
</div> </div>
{/* Amount Range Filters Skeleton */} {/* Amount Range Filters Skeleton */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-4"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-4">
<div> <div>
<div className="h-4 bg-gray-200 rounded w-20 mb-1"></div> <Skeleton className="h-4 w-20 mb-1" />
<div className="h-10 bg-gray-200 rounded"></div> <Skeleton className="h-10 w-full" />
</div> </div>
<div> <div>
<div className="h-4 bg-gray-200 rounded w-20 mb-1"></div> <Skeleton className="h-4 w-20 mb-1" />
<div className="h-10 bg-gray-200 rounded"></div> <Skeleton className="h-10 w-full" />
</div> </div>
</div> </div>
</div> </CardContent>
{/* Results Summary Skeleton */} {/* Results Summary Skeleton */}
<div className="px-6 py-3 bg-gray-50 border-b border-gray-200"> <CardContent className="px-6 py-3 bg-muted/30 border-b border-border">
<div className="h-4 bg-gray-200 rounded w-48"></div> <Skeleton className="h-4 w-48" />
</div> </CardContent>
</div> </Card>
); );
} }

View File

@@ -49,15 +49,15 @@ export default function Header({ setSidebarOpen }: HeaderProps) {
<div className="flex items-center space-x-1"> <div className="flex items-center space-x-1">
{healthLoading ? ( {healthLoading ? (
<> <>
<Activity className="h-4 w-4 text-yellow-500 animate-pulse" /> <Activity className="h-4 w-4 text-muted-foreground animate-pulse" />
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">
Checking... Checking...
</span> </span>
</> </>
) : healthError || healthStatus?.status !== "healthy" ? ( ) : healthError || healthStatus?.status !== "healthy" ? (
<> <>
<WifiOff className="h-4 w-4 text-red-500" /> <WifiOff className="h-4 w-4 text-destructive" />
<span className="text-sm text-red-500">Disconnected</span> <span className="text-sm text-destructive">Disconnected</span>
</> </>
) : ( ) : (
<> <>

View File

@@ -1,17 +1,20 @@
import { RefreshCw } from "lucide-react"; import { RefreshCw } from "lucide-react";
import { cn } from "../lib/utils";
interface LoadingSpinnerProps { interface LoadingSpinnerProps {
message?: string; message?: string;
className?: string;
} }
export default function LoadingSpinner({ export default function LoadingSpinner({
message = "Loading...", message = "Loading...",
className,
}: LoadingSpinnerProps) { }: LoadingSpinnerProps) {
return ( return (
<div className="flex items-center justify-center p-8"> <div className={cn("flex items-center justify-center p-8", className)}>
<div className="text-center"> <div className="text-center">
<RefreshCw className="h-8 w-8 animate-spin text-blue-600 mx-auto mb-2" /> <RefreshCw className="h-8 w-8 animate-spin text-primary mx-auto mb-2" />
<p className="text-gray-600 text-sm">{message}</p> <p className="text-muted-foreground text-sm">{message}</p>
</div> </div>
</div> </div>
); );

View File

@@ -24,6 +24,7 @@ import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { Input } from "./ui/input"; import { Input } from "./ui/input";
import { Label } from "./ui/label"; import { Label } from "./ui/label";
import { Badge } from "./ui/badge";
import { import {
Select, Select,
SelectContent, SelectContent,
@@ -233,12 +234,8 @@ export default function Notifications() {
{service.name} {service.name}
</h4> </h4>
<div className="flex items-center space-x-2 mt-1"> <div className="flex items-center space-x-2 mt-1">
<span <Badge
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${ variant={service.enabled ? "default" : "destructive"}
service.enabled
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`}
> >
{service.enabled ? ( {service.enabled ? (
<CheckCircle className="h-3 w-3 mr-1" /> <CheckCircle className="h-3 w-3 mr-1" />
@@ -246,18 +243,14 @@ export default function Notifications() {
<AlertCircle className="h-3 w-3 mr-1" /> <AlertCircle className="h-3 w-3 mr-1" />
)} )}
{service.enabled ? "Enabled" : "Disabled"} {service.enabled ? "Enabled" : "Disabled"}
</span> </Badge>
<span <Badge
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${ variant={service.configured ? "secondary" : "outline"}
service.configured
? "bg-blue-100 text-blue-800"
: "bg-yellow-100 text-yellow-800"
}`}
> >
{service.configured {service.configured
? "Configured" ? "Configured"
: "Not Configured"} : "Not Configured"}
</span> </Badge>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,3 +1,6 @@
import { Skeleton } from "./ui/skeleton";
import { Card } from "./ui/card";
interface TransactionSkeletonProps { interface TransactionSkeletonProps {
rows?: number; rows?: number;
view?: "table" | "mobile"; view?: "table" | "mobile";
@@ -11,93 +14,89 @@ export default function TransactionSkeleton({
if (view === "mobile") { if (view === "mobile") {
return ( return (
<div className="bg-white rounded-lg shadow divide-y divide-gray-200"> <Card className="divide-y divide-border">
{skeletonRows.map((_, index) => ( {skeletonRows.map((_, index) => (
<div key={index} className="p-4 animate-pulse"> <div key={index} className="p-4">
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div className="flex-1"> <div className="flex-1">
<div className="flex items-start space-x-3"> <div className="flex items-start space-x-3">
<div className="p-2 rounded-full bg-gray-200 flex-shrink-0"> <Skeleton className="h-10 w-10 rounded-full flex-shrink-0" />
<div className="h-4 w-4 bg-gray-300 rounded"></div>
</div>
<div className="flex-1 min-w-0 space-y-2"> <div className="flex-1 min-w-0 space-y-2">
<div className="h-4 bg-gray-200 rounded w-3/4"></div> <Skeleton className="h-4 w-3/4" />
<div className="space-y-1"> <div className="space-y-1">
<div className="h-3 bg-gray-200 rounded w-1/2"></div> <Skeleton className="h-3 w-1/2" />
<div className="h-3 bg-gray-200 rounded w-2/3"></div> <Skeleton className="h-3 w-2/3" />
<div className="h-3 bg-gray-200 rounded w-1/3"></div> <Skeleton className="h-3 w-1/3" />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="text-right ml-3 flex-shrink-0 space-y-2"> <div className="text-right ml-3 flex-shrink-0 space-y-2">
<div className="h-6 bg-gray-200 rounded w-20"></div> <Skeleton className="h-6 w-20" />
<div className="h-4 bg-gray-200 rounded w-16 ml-auto"></div> <Skeleton className="h-4 w-16 ml-auto" />
<div className="h-6 bg-gray-200 rounded w-12 ml-auto"></div> <Skeleton className="h-6 w-12 ml-auto" />
</div> </div>
</div> </div>
</div> </div>
))} ))}
</div> </Card>
); );
} }
return ( return (
<div className="bg-white rounded-lg shadow overflow-hidden"> <Card className="overflow-hidden">
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200"> <table className="min-w-full divide-y divide-border">
<thead className="bg-gray-50"> <thead className="bg-muted/50">
<tr> <tr>
<th className="px-6 py-3 text-left"> <th className="px-6 py-3 text-left">
<div className="h-4 bg-gray-200 rounded w-20 animate-pulse"></div> <Skeleton className="h-4 w-20" />
</th> </th>
<th className="px-6 py-3 text-left"> <th className="px-6 py-3 text-left">
<div className="h-4 bg-gray-200 rounded w-16 animate-pulse"></div> <Skeleton className="h-4 w-16" />
</th> </th>
<th className="px-6 py-3 text-left"> <th className="px-6 py-3 text-left">
<div className="h-4 bg-gray-200 rounded w-12 animate-pulse"></div> <Skeleton className="h-4 w-12" />
</th> </th>
<th className="px-6 py-3 text-left"> <th className="px-6 py-3 text-left">
<div className="h-4 bg-gray-200 rounded w-8 animate-pulse"></div> <Skeleton className="h-4 w-8" />
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody className="bg-white divide-y divide-gray-200"> <tbody className="bg-card divide-y divide-border">
{skeletonRows.map((_, index) => ( {skeletonRows.map((_, index) => (
<tr key={index} className="animate-pulse"> <tr key={index}>
<td className="px-6 py-4"> <td className="px-6 py-4">
<div className="flex items-start space-x-3"> <div className="flex items-start space-x-3">
<div className="p-2 rounded-full bg-gray-200 flex-shrink-0"> <Skeleton className="h-10 w-10 rounded-full flex-shrink-0" />
<div className="h-4 w-4 bg-gray-300 rounded"></div>
</div>
<div className="flex-1 space-y-2"> <div className="flex-1 space-y-2">
<div className="h-4 bg-gray-200 rounded w-3/4"></div> <Skeleton className="h-4 w-3/4" />
<div className="space-y-1"> <div className="space-y-1">
<div className="h-3 bg-gray-200 rounded w-1/2"></div> <Skeleton className="h-3 w-1/2" />
<div className="h-3 bg-gray-200 rounded w-2/3"></div> <Skeleton className="h-3 w-2/3" />
</div> </div>
</div> </div>
</div> </div>
</td> </td>
<td className="px-6 py-4"> <td className="px-6 py-4">
<div className="text-right"> <div className="text-right">
<div className="h-6 bg-gray-200 rounded w-24 ml-auto mb-1"></div> <Skeleton className="h-6 w-24 ml-auto mb-1" />
</div> </div>
</td> </td>
<td className="px-6 py-4"> <td className="px-6 py-4">
<div className="space-y-1"> <div className="space-y-1">
<div className="h-4 bg-gray-200 rounded w-20"></div> <Skeleton className="h-4 w-20" />
<div className="h-3 bg-gray-200 rounded w-16"></div> <Skeleton className="h-3 w-16" />
</div> </div>
</td> </td>
<td className="px-6 py-4"> <td className="px-6 py-4">
<div className="h-6 bg-gray-200 rounded w-12"></div> <Skeleton className="h-6 w-12" />
</td> </td>
</tr> </tr>
))} ))}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </Card>
); );
} }

View File

@@ -0,0 +1,26 @@
import * as React from "react";
import * as ProgressPrimitive from "@radix-ui/react-progress";
import { cn } from "@/lib/utils";
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn(
"relative h-2 w-full overflow-hidden rounded-full bg-secondary",
className
)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
));
Progress.displayName = ProgressPrimitive.Root.displayName;
export { Progress };

View File

@@ -0,0 +1,138 @@
import * as React from "react";
import * as SheetPrimitive from "@radix-ui/react-dialog";
import { cva, type VariantProps } from "class-variance-authority";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";
const Sheet = SheetPrimitive.Root;
const SheetTrigger = SheetPrimitive.Trigger;
const SheetClose = SheetPrimitive.Close;
const SheetPortal = SheetPrimitive.Portal;
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
));
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
{
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
}
);
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
{...props}
>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
));
SheetContent.displayName = SheetPrimitive.Content.displayName;
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
);
SheetHeader.displayName = "SheetHeader";
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
);
SheetFooter.displayName = "SheetFooter";
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold text-foreground", className)}
{...props}
/>
));
SheetTitle.displayName = SheetPrimitive.Title.displayName;
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));
SheetDescription.displayName = SheetPrimitive.Description.displayName;
export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
};

View File

@@ -0,0 +1,14 @@
import { cn } from "@/lib/utils";
interface SkeletonProps extends React.HTMLAttributes<HTMLDivElement> {}
function Skeleton({ className, ...props }: SkeletonProps) {
return (
<div
className={cn("animate-pulse rounded-md bg-muted", className)}
{...props}
/>
);
}
export { Skeleton };

View File

@@ -0,0 +1,27 @@
"use client";
import { Toaster as Sonner } from "sonner";
type ToasterProps = React.ComponentProps<typeof Sonner>;
const Toaster = ({ ...props }: ToasterProps) => {
return (
<Sonner
className="toaster group"
toastOptions={{
classNames: {
toast:
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
description: "group-[.toast]:text-muted-foreground",
actionButton:
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
cancelButton:
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
},
}}
{...props}
/>
);
};
export { Toaster };