mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 10:23:41 +00:00
feature: Migrate FileUpload to shadcn/ui and fix dark mode
Migrated FileUpload component to use shadcn/ui Card, Button, Alert, and Progress components. Updated dark mode CSS variables to use media query approach for automatic system theme detection. Fixed Card component padding and button styling for better visual consistency. Changes: - Replaced custom div with shadcn Card and CardContent components - Migrated buttons to shadcn Button component with outline variant for Choose File - Replaced custom alerts with shadcn Alert components - Replaced custom progress bars with shadcn Progress component - Fixed Card padding by adding p-0 to Card and rounded-lg to CardContent - Changed dark mode from .dark class to @media (prefers-color-scheme: dark) - Fixed primary-foreground color in dark mode for proper white text contrast 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
bd80e95004
commit
ed3950b5d0
3 changed files with 125 additions and 132 deletions
|
|
@ -60,7 +60,8 @@
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
--background: 222.2 84% 4.9%;
|
--background: 222.2 84% 4.9%;
|
||||||
--foreground: 210 40% 98%;
|
--foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
|
@ -71,7 +72,7 @@
|
||||||
--popover-foreground: 210 40% 98%;
|
--popover-foreground: 210 40% 98%;
|
||||||
|
|
||||||
--primary: 217.2 91.2% 59.8%; /* blue-500 lighter for dark */
|
--primary: 217.2 91.2% 59.8%; /* blue-500 lighter for dark */
|
||||||
--primary-foreground: 222.2 47.4% 11.2%;
|
--primary-foreground: 210 40% 98%;
|
||||||
|
|
||||||
--secondary: 20.5 90.2% 48.2%; /* orange-600 for dark */
|
--secondary: 20.5 90.2% 48.2%; /* orange-600 for dark */
|
||||||
--secondary-foreground: 210 40% 98%;
|
--secondary-foreground: 210 40% 98%;
|
||||||
|
|
@ -98,6 +99,7 @@
|
||||||
--input: 217.2 32.6% 17.5%;
|
--input: 217.2 32.6% 17.5%;
|
||||||
--ring: 224.3 76.3% 48%;
|
--ring: 224.3 76.3% 48%;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,12 @@ import {
|
||||||
} from "@heroicons/react/24/solid";
|
} from "@heroicons/react/24/solid";
|
||||||
import { createFileService } from "../platform";
|
import { createFileService } from "../platform";
|
||||||
import type { IFileService } from "../platform/interfaces/IFileService";
|
import type { IFileService } from "../platform/interfaces/IFileService";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||||
|
import { Progress } from "@/components/ui/progress";
|
||||||
|
import { Loader2 } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
export function FileUpload() {
|
export function FileUpload() {
|
||||||
// Machine store
|
// Machine store
|
||||||
|
|
@ -202,12 +208,11 @@ export function FileUpload() {
|
||||||
: "text-gray-600 dark:text-gray-400";
|
: "text-gray-600 dark:text-gray-400";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Card className={cn("p-0 border-l-4", borderColor)}>
|
||||||
className={`bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 ${borderColor}`}
|
<CardContent className="p-4 rounded-lg">
|
||||||
>
|
|
||||||
<div className="flex items-start gap-3 mb-3">
|
<div className="flex items-start gap-3 mb-3">
|
||||||
<DocumentTextIcon
|
<DocumentTextIcon
|
||||||
className={`w-6 h-6 ${iconColor} flex-shrink-0 mt-0.5`}
|
className={cn("w-6 h-6 flex-shrink-0 mt-0.5", iconColor)}
|
||||||
/>
|
/>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-1">
|
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-1">
|
||||||
|
|
@ -253,40 +258,41 @@ export function FileUpload() {
|
||||||
className="hidden"
|
className="hidden"
|
||||||
disabled={isLoading || patternUploaded || isUploading}
|
disabled={isLoading || patternUploaded || isUploading}
|
||||||
/>
|
/>
|
||||||
<label
|
<Button
|
||||||
htmlFor={fileService.hasNativeDialogs() ? undefined : "file-input"}
|
asChild={!fileService.hasNativeDialogs()}
|
||||||
onClick={
|
onClick={
|
||||||
fileService.hasNativeDialogs()
|
fileService.hasNativeDialogs()
|
||||||
? () => handleFileChange()
|
? () => handleFileChange()
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
className={`flex-[2] flex items-center justify-center gap-2 px-3 py-2.5 sm:py-2 rounded font-semibold text-sm transition-all ${
|
disabled={isLoading || patternUploaded || isUploading}
|
||||||
isLoading || patternUploaded || isUploading
|
variant="outline"
|
||||||
? "opacity-50 cursor-not-allowed bg-gray-400 dark:bg-gray-600 text-white"
|
className="flex-[2]"
|
||||||
: "cursor-pointer bg-gray-600 dark:bg-gray-700 text-white hover:bg-gray-700 dark:hover:bg-gray-600"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
|
{fileService.hasNativeDialogs() ? (
|
||||||
|
<>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<>
|
<>
|
||||||
<svg
|
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
||||||
className="w-3.5 h-3.5 animate-spin"
|
<span>Loading...</span>
|
||||||
fill="none"
|
</>
|
||||||
viewBox="0 0 24 24"
|
) : patternUploaded ? (
|
||||||
>
|
<>
|
||||||
<circle
|
<CheckCircleIcon className="w-3.5 h-3.5" />
|
||||||
className="opacity-25"
|
<span>Locked</span>
|
||||||
cx="12"
|
</>
|
||||||
cy="12"
|
) : (
|
||||||
r="10"
|
<>
|
||||||
stroke="currentColor"
|
<FolderOpenIcon className="w-3.5 h-3.5" />
|
||||||
strokeWidth="4"
|
<span>Choose PES File</span>
|
||||||
></circle>
|
</>
|
||||||
<path
|
)}
|
||||||
className="opacity-75"
|
</>
|
||||||
fill="currentColor"
|
) : (
|
||||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
<label htmlFor="file-input" className="flex items-center gap-2">
|
||||||
></path>
|
{isLoading ? (
|
||||||
</svg>
|
<>
|
||||||
|
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
||||||
<span>Loading...</span>
|
<span>Loading...</span>
|
||||||
</>
|
</>
|
||||||
) : patternUploaded ? (
|
) : patternUploaded ? (
|
||||||
|
|
@ -301,15 +307,17 @@ export function FileUpload() {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</label>
|
</label>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
{pesData &&
|
{pesData &&
|
||||||
canUploadPattern(machineStatus) &&
|
canUploadPattern(machineStatus) &&
|
||||||
!patternUploaded &&
|
!patternUploaded &&
|
||||||
uploadProgress < 100 && (
|
uploadProgress < 100 && (
|
||||||
<button
|
<Button
|
||||||
onClick={handleUpload}
|
onClick={handleUpload}
|
||||||
disabled={!isConnected || isUploading || !boundsCheck.fits}
|
disabled={!isConnected || isUploading || !boundsCheck.fits}
|
||||||
className="flex-1 px-3 py-2.5 sm:py-2 bg-primary-600 dark:bg-primary-700 text-white rounded font-semibold text-sm hover:bg-primary-700 dark:hover:bg-primary-600 active:bg-primary-800 dark:active:bg-primary-500 transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
|
className="flex-1"
|
||||||
aria-label={
|
aria-label={
|
||||||
isUploading
|
isUploading
|
||||||
? `Uploading pattern: ${uploadProgress.toFixed(0)}% complete`
|
? `Uploading pattern: ${uploadProgress.toFixed(0)}% complete`
|
||||||
|
|
@ -318,36 +326,18 @@ export function FileUpload() {
|
||||||
>
|
>
|
||||||
{isUploading ? (
|
{isUploading ? (
|
||||||
<>
|
<>
|
||||||
<svg
|
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
||||||
className="w-3.5 h-3.5 animate-spin inline mr-1"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<circle
|
|
||||||
className="opacity-25"
|
|
||||||
cx="12"
|
|
||||||
cy="12"
|
|
||||||
r="10"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="4"
|
|
||||||
></circle>
|
|
||||||
<path
|
|
||||||
className="opacity-75"
|
|
||||||
fill="currentColor"
|
|
||||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
{uploadProgress > 0
|
{uploadProgress > 0
|
||||||
? uploadProgress.toFixed(0) + "%"
|
? uploadProgress.toFixed(0) + "%"
|
||||||
: "Uploading"}
|
: "Uploading"}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<ArrowUpTrayIcon className="w-3.5 h-3.5 inline mr-1" />
|
<ArrowUpTrayIcon className="w-3.5 h-3.5" />
|
||||||
Upload
|
Upload
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -364,12 +354,7 @@ export function FileUpload() {
|
||||||
{pyodideProgress.toFixed(0)}%
|
{pyodideProgress.toFixed(0)}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-2.5 bg-gray-300 dark:bg-gray-600 rounded-full overflow-hidden shadow-inner relative">
|
<Progress value={pyodideProgress} className="h-2.5" />
|
||||||
<div
|
|
||||||
className="h-full bg-gradient-to-r from-primary-500 via-primary-600 to-primary-700 dark:from-primary-600 dark:via-primary-700 dark:to-primary-800 transition-all duration-300 ease-out relative overflow-hidden after:absolute after:inset-0 after:bg-gradient-to-r after:from-transparent after:via-white/30 after:to-transparent after:animate-[shimmer_2s_infinite] rounded-full"
|
|
||||||
style={{ width: `${pyodideProgress}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1.5 italic">
|
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1.5 italic">
|
||||||
{isLoading && !pyodideReady
|
{isLoading && !pyodideReady
|
||||||
? "File dialog will open automatically when ready"
|
? "File dialog will open automatically when ready"
|
||||||
|
|
@ -393,15 +378,22 @@ export function FileUpload() {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{pesData && !canUploadPattern(machineStatus) && (
|
{pesData && !canUploadPattern(machineStatus) && (
|
||||||
<div className="bg-warning-100 dark:bg-warning-900/20 text-warning-800 dark:text-warning-200 px-3 py-2 rounded border border-warning-200 dark:border-warning-800 text-sm">
|
<Alert className="bg-warning-100 dark:bg-warning-900/20 border-warning-200 dark:border-warning-800">
|
||||||
|
<AlertDescription className="text-warning-800 dark:text-warning-200 text-sm">
|
||||||
Cannot upload while {getMachineStateCategory(machineStatus)}
|
Cannot upload while {getMachineStateCategory(machineStatus)}
|
||||||
</div>
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{pesData && boundsCheck.error && (
|
{pesData && boundsCheck.error && (
|
||||||
<div className="bg-danger-100 dark:bg-danger-900/20 text-danger-800 dark:text-danger-200 px-3 py-2 rounded border border-danger-200 dark:border-danger-800 text-sm">
|
<Alert
|
||||||
|
variant="destructive"
|
||||||
|
className="bg-danger-100 dark:bg-danger-900/20 border-danger-200 dark:border-danger-800"
|
||||||
|
>
|
||||||
|
<AlertDescription className="text-danger-800 dark:text-danger-200 text-sm">
|
||||||
<strong>Pattern too large:</strong> {boundsCheck.error}
|
<strong>Pattern too large:</strong> {boundsCheck.error}
|
||||||
</div>
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -417,14 +409,13 @@ export function FileUpload() {
|
||||||
: "Starting..."}
|
: "Starting..."}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-2.5 bg-gray-300 dark:bg-gray-600 rounded-full overflow-hidden shadow-inner relative">
|
<Progress
|
||||||
<div
|
value={uploadProgress}
|
||||||
className="h-full bg-gradient-to-r from-secondary-500 via-secondary-600 to-secondary-700 dark:from-secondary-600 dark:via-secondary-700 dark:to-secondary-800 transition-all duration-300 ease-out relative overflow-hidden after:absolute after:inset-0 after:bg-gradient-to-r after:from-transparent after:via-white/30 after:to-transparent after:animate-[shimmer_2s_infinite] rounded-full"
|
className="h-2.5 [&>div]:bg-gradient-to-r [&>div]:from-secondary-500 [&>div]:via-secondary-600 [&>div]:to-secondary-700 dark:[&>div]:from-secondary-600 dark:[&>div]:via-secondary-700 dark:[&>div]:to-secondary-800"
|
||||||
style={{ width: `${uploadProgress}%` }}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</CardContent>
|
||||||
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,8 @@ export function PatternSummaryCard() {
|
||||||
|
|
||||||
const canDelete = canDeletePattern(machineStatus);
|
const canDelete = canDeletePattern(machineStatus);
|
||||||
return (
|
return (
|
||||||
<Card className="border-l-4 border-primary-600 dark:border-primary-500">
|
<Card className="p-0 border-l-4 border-primary-600 dark:border-primary-500">
|
||||||
<CardContent className="p-4">
|
<CardContent className="p-4 rounded-lg">
|
||||||
<div className="flex items-start gap-3 mb-3">
|
<div className="flex items-start gap-3 mb-3">
|
||||||
<DocumentTextIcon className="w-6 h-6 text-primary-600 dark:text-primary-400 flex-shrink-0 mt-0.5" />
|
<DocumentTextIcon className="w-6 h-6 text-primary-600 dark:text-primary-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue