mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 02:13: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
54
src/App.css
54
src/App.css
|
|
@ -60,43 +60,45 @@
|
|||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
|
||||
--primary: 217.2 91.2% 59.8%; /* blue-500 lighter for dark */
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--primary: 217.2 91.2% 59.8%; /* blue-500 lighter for dark */
|
||||
--primary-foreground: 210 40% 98%;
|
||||
|
||||
--secondary: 20.5 90.2% 48.2%; /* orange-600 for dark */
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--secondary: 20.5 90.2% 48.2%; /* orange-600 for dark */
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
|
||||
--accent: 263.4 70% 50.4%; /* purple-600 for dark */
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--accent: 263.4 70% 50.4%; /* purple-600 for dark */
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%; /* red-900 */
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--destructive: 0 62.8% 30.6%; /* red-900 */
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--success: 142.1 70.6% 45.3%; /* green-500 for dark */
|
||||
--success-foreground: 210 40% 98%;
|
||||
--success: 142.1 70.6% 45.3%; /* green-500 for dark */
|
||||
--success-foreground: 210 40% 98%;
|
||||
|
||||
--warning: 47.9 95.8% 53.1%; /* amber-400 for dark */
|
||||
--warning-foreground: 26 83.3% 14.1%;
|
||||
--warning: 47.9 95.8% 53.1%; /* amber-400 for dark */
|
||||
--warning-foreground: 26 83.3% 14.1%;
|
||||
|
||||
--info: 188.7 85.7% 53.3%; /* cyan-500 */
|
||||
--info-foreground: 210 40% 98%;
|
||||
--info: 188.7 85.7% 53.3%; /* cyan-500 */
|
||||
--info-foreground: 210 40% 98%;
|
||||
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 224.3 76.3% 48%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 224.3 76.3% 48%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,12 @@ import {
|
|||
} from "@heroicons/react/24/solid";
|
||||
import { createFileService } from "../platform";
|
||||
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() {
|
||||
// Machine store
|
||||
|
|
@ -202,31 +208,30 @@ export function FileUpload() {
|
|||
: "text-gray-600 dark:text-gray-400";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 ${borderColor}`}
|
||||
>
|
||||
<div className="flex items-start gap-3 mb-3">
|
||||
<DocumentTextIcon
|
||||
className={`w-6 h-6 ${iconColor} flex-shrink-0 mt-0.5`}
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-1">
|
||||
Pattern File
|
||||
</h3>
|
||||
{pesData && displayFileName ? (
|
||||
<p
|
||||
className="text-xs text-gray-600 dark:text-gray-400 truncate"
|
||||
title={displayFileName}
|
||||
>
|
||||
{displayFileName}
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400">
|
||||
No pattern loaded
|
||||
</p>
|
||||
)}
|
||||
<Card className={cn("p-0 border-l-4", borderColor)}>
|
||||
<CardContent className="p-4 rounded-lg">
|
||||
<div className="flex items-start gap-3 mb-3">
|
||||
<DocumentTextIcon
|
||||
className={cn("w-6 h-6 flex-shrink-0 mt-0.5", iconColor)}
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-1">
|
||||
Pattern File
|
||||
</h3>
|
||||
{pesData && displayFileName ? (
|
||||
<p
|
||||
className="text-xs text-gray-600 dark:text-gray-400 truncate"
|
||||
title={displayFileName}
|
||||
>
|
||||
{displayFileName}
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400">
|
||||
No pattern loaded
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{resumeAvailable && resumeFileName && (
|
||||
<div className="bg-success-50 dark:bg-success-900/20 border border-success-200 dark:border-success-800 px-3 py-2 rounded mb-3">
|
||||
|
|
@ -253,63 +258,66 @@ export function FileUpload() {
|
|||
className="hidden"
|
||||
disabled={isLoading || patternUploaded || isUploading}
|
||||
/>
|
||||
<label
|
||||
htmlFor={fileService.hasNativeDialogs() ? undefined : "file-input"}
|
||||
<Button
|
||||
asChild={!fileService.hasNativeDialogs()}
|
||||
onClick={
|
||||
fileService.hasNativeDialogs()
|
||||
? () => handleFileChange()
|
||||
: 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 ${
|
||||
isLoading || patternUploaded || isUploading
|
||||
? "opacity-50 cursor-not-allowed bg-gray-400 dark:bg-gray-600 text-white"
|
||||
: "cursor-pointer bg-gray-600 dark:bg-gray-700 text-white hover:bg-gray-700 dark:hover:bg-gray-600"
|
||||
}`}
|
||||
disabled={isLoading || patternUploaded || isUploading}
|
||||
variant="outline"
|
||||
className="flex-[2]"
|
||||
>
|
||||
{isLoading ? (
|
||||
{fileService.hasNativeDialogs() ? (
|
||||
<>
|
||||
<svg
|
||||
className="w-3.5 h-3.5 animate-spin"
|
||||
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>
|
||||
<span>Loading...</span>
|
||||
</>
|
||||
) : patternUploaded ? (
|
||||
<>
|
||||
<CheckCircleIcon className="w-3.5 h-3.5" />
|
||||
<span>Locked</span>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
||||
<span>Loading...</span>
|
||||
</>
|
||||
) : patternUploaded ? (
|
||||
<>
|
||||
<CheckCircleIcon className="w-3.5 h-3.5" />
|
||||
<span>Locked</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FolderOpenIcon className="w-3.5 h-3.5" />
|
||||
<span>Choose PES File</span>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FolderOpenIcon className="w-3.5 h-3.5" />
|
||||
<span>Choose PES File</span>
|
||||
</>
|
||||
<label htmlFor="file-input" className="flex items-center gap-2">
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
||||
<span>Loading...</span>
|
||||
</>
|
||||
) : patternUploaded ? (
|
||||
<>
|
||||
<CheckCircleIcon className="w-3.5 h-3.5" />
|
||||
<span>Locked</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FolderOpenIcon className="w-3.5 h-3.5" />
|
||||
<span>Choose PES File</span>
|
||||
</>
|
||||
)}
|
||||
</label>
|
||||
)}
|
||||
</label>
|
||||
</Button>
|
||||
|
||||
{pesData &&
|
||||
canUploadPattern(machineStatus) &&
|
||||
!patternUploaded &&
|
||||
uploadProgress < 100 && (
|
||||
<button
|
||||
<Button
|
||||
onClick={handleUpload}
|
||||
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={
|
||||
isUploading
|
||||
? `Uploading pattern: ${uploadProgress.toFixed(0)}% complete`
|
||||
|
|
@ -318,36 +326,18 @@ export function FileUpload() {
|
|||
>
|
||||
{isUploading ? (
|
||||
<>
|
||||
<svg
|
||||
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>
|
||||
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
||||
{uploadProgress > 0
|
||||
? uploadProgress.toFixed(0) + "%"
|
||||
: "Uploading"}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ArrowUpTrayIcon className="w-3.5 h-3.5 inline mr-1" />
|
||||
<ArrowUpTrayIcon className="w-3.5 h-3.5" />
|
||||
Upload
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
@ -364,12 +354,7 @@ export function FileUpload() {
|
|||
{pyodideProgress.toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2.5 bg-gray-300 dark:bg-gray-600 rounded-full overflow-hidden shadow-inner relative">
|
||||
<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>
|
||||
<Progress value={pyodideProgress} className="h-2.5" />
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1.5 italic">
|
||||
{isLoading && !pyodideReady
|
||||
? "File dialog will open automatically when ready"
|
||||
|
|
@ -393,15 +378,22 @@ export function FileUpload() {
|
|||
}}
|
||||
>
|
||||
{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">
|
||||
Cannot upload while {getMachineStateCategory(machineStatus)}
|
||||
</div>
|
||||
<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)}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{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">
|
||||
<strong>Pattern too large:</strong> {boundsCheck.error}
|
||||
</div>
|
||||
<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}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
@ -417,14 +409,13 @@ export function FileUpload() {
|
|||
: "Starting..."}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2.5 bg-gray-300 dark:bg-gray-600 rounded-full overflow-hidden shadow-inner relative">
|
||||
<div
|
||||
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"
|
||||
style={{ width: `${uploadProgress}%` }}
|
||||
/>
|
||||
</div>
|
||||
<Progress
|
||||
value={uploadProgress}
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ export function PatternSummaryCard() {
|
|||
|
||||
const canDelete = canDeletePattern(machineStatus);
|
||||
return (
|
||||
<Card className="border-l-4 border-primary-600 dark:border-primary-500">
|
||||
<CardContent className="p-4">
|
||||
<Card className="p-0 border-l-4 border-primary-600 dark:border-primary-500">
|
||||
<CardContent className="p-4 rounded-lg">
|
||||
<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" />
|
||||
<div className="flex-1 min-w-0">
|
||||
|
|
|
|||
Loading…
Reference in a new issue