Refactor ConnectionForm and Checkbox component for improved functionality and UI

- Updated ConnectionForm to utilize a Checkbox component for managing the "keep server running" setting, enhancing user interaction.
- Refactored Checkbox component to use a button element for better accessibility and visual feedback.
- Streamlined import statements and improved code organization across multiple components for better readability.
This commit is contained in:
Jamie Pine
2026-01-27 17:23:13 -08:00
parent cb44377b09
commit 9f7a5a492e
5 changed files with 76 additions and 70 deletions
@@ -1,6 +1,6 @@
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import * as z from 'zod'; import * as z from 'zod';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
@@ -14,11 +14,10 @@ import {
FormMessage, FormMessage,
} from '@/components/ui/form'; } from '@/components/ui/form';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Checkbox } from '@/components/ui/checkbox';
import { useToast } from '@/components/ui/use-toast'; import { useToast } from '@/components/ui/use-toast';
import { useServerStore } from '@/stores/serverStore'; import { useServerStore } from '@/stores/serverStore';
import { setKeepServerRunning } from '@/lib/tauri'; import { setKeepServerRunning } from '@/lib/tauri';
import { Check } from 'lucide-react';
import { cn } from '@/lib/utils/cn';
const connectionSchema = z.object({ const connectionSchema = z.object({
serverUrl: z.string().url('Please enter a valid URL'), serverUrl: z.string().url('Please enter a valid URL'),
@@ -84,41 +83,36 @@ export function ConnectionForm() {
</Form> </Form>
<div className="mt-6 pt-6 border-t"> <div className="mt-6 pt-6 border-t">
<button <div className="flex items-start space-x-3">
type="button" <Checkbox
onClick={() => { id="keepServerRunning"
const newValue = !keepServerRunningOnClose; checked={keepServerRunningOnClose}
setKeepServerRunningOnClose(newValue); onCheckedChange={(checked: boolean) => {
setKeepServerRunning(newValue).catch((error) => { setKeepServerRunningOnClose(checked);
setKeepServerRunning(checked).catch((error) => {
console.error('Failed to sync setting to Rust:', error); console.error('Failed to sync setting to Rust:', error);
}); });
toast({ toast({
title: 'Setting updated', title: 'Setting updated',
description: newValue description: checked
? 'Server will continue running when app closes' ? 'Server will continue running when app closes'
: 'Server will stop when app closes', : 'Server will stop when app closes',
}); });
}} }}
className="flex items-start gap-3 text-left w-full" />
>
<div
className={cn(
'h-4 w-4 rounded border-2 flex items-center justify-center shrink-0 mt-0.5',
keepServerRunningOnClose ? 'bg-accent border-accent' : 'border-muted-foreground/30',
)}
>
{keepServerRunningOnClose && <Check className="h-3 w-3 text-accent-foreground" />}
</div>
<div className="space-y-1"> <div className="space-y-1">
<div className="text-sm font-medium leading-none"> <label
htmlFor="keepServerRunning"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
>
Keep server running when app closes Keep server running when app closes
</div> </label>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
When enabled, the server will continue running in the background after closing the When enabled, the server will continue running in the background after closing the
app. Disabled by default. app. Disabled by default.
</p> </p>
</div> </div>
</button> </div>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
@@ -1,4 +1,4 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Download, Loader2, Trash2 } from 'lucide-react'; import { Download, Loader2, Trash2 } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import { import {
@@ -1,9 +1,9 @@
import { useEffect, useState } from 'react';
import { Progress } from '@/components/ui/progress';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { useServerStore } from '@/stores/serverStore';
import type { ModelProgress as ModelProgressType } from '@/lib/api/types';
import { Loader2, XCircle } from 'lucide-react'; import { Loader2, XCircle } from 'lucide-react';
import { useEffect, useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Progress } from '@/components/ui/progress';
import type { ModelProgress as ModelProgressType } from '@/lib/api/types';
import { useServerStore } from '@/stores/serverStore';
interface ModelProgressProps { interface ModelProgressProps {
modelName: string; modelName: string;
@@ -63,7 +63,7 @@ export function ModelProgress({ modelName, displayName }: ModelProgressProps) {
const k = 1024; const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB']; const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k)); const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`; return `${(bytes / k ** i).toFixed(1)} ${sizes[i]}`;
}; };
const getStatusIcon = () => { const getStatusIcon = () => {
@@ -1,11 +1,11 @@
import { useState, useEffect } from 'react'; import { getVersion } from '@tauri-apps/api/app';
import { RefreshCw, Download, AlertCircle } from 'lucide-react'; import { RefreshCw, Download, AlertCircle } from 'lucide-react';
import { useEffect, useState } from 'react';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Progress } from '@/components/ui/progress'; import { Progress } from '@/components/ui/progress';
import { useAutoUpdater } from '@/hooks/useAutoUpdater'; import { useAutoUpdater } from '@/hooks/useAutoUpdater';
import { getVersion } from '@tauri-apps/api/app';
export function UpdateStatus() { export function UpdateStatus() {
const { status, checkForUpdates, downloadAndInstall, restartAndInstall } = useAutoUpdater(false); const { status, checkForUpdates, downloadAndInstall, restartAndInstall } = useAutoUpdater(false);
@@ -77,15 +77,16 @@ export function UpdateStatus() {
Downloading update... Downloading update...
</div> </div>
{status.downloadProgress !== undefined && ( {status.downloadProgress !== undefined && (
<span className="text-muted-foreground"> <span className="text-muted-foreground">{status.downloadProgress}%</span>
{status.downloadProgress}%
</span>
)} )}
</div> </div>
<Progress value={status.downloadProgress} /> <Progress value={status.downloadProgress} />
{status.downloadedBytes !== undefined && status.totalBytes !== undefined && status.totalBytes > 0 && ( {status.downloadedBytes !== undefined &&
status.totalBytes !== undefined &&
status.totalBytes > 0 && (
<div className="text-xs text-muted-foreground"> <div className="text-xs text-muted-foreground">
{(status.downloadedBytes / 1024 / 1024).toFixed(1)} MB / {(status.totalBytes / 1024 / 1024).toFixed(1)} MB {(status.downloadedBytes / 1024 / 1024).toFixed(1)} MB /{' '}
{(status.totalBytes / 1024 / 1024).toFixed(1)} MB
</div> </div>
)} )}
</div> </div>
@@ -96,11 +97,14 @@ export function UpdateStatus() {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div> <div>
<div className="font-semibold">Update Ready to Install</div> <div className="font-semibold">Update Ready to Install</div>
<div className="text-sm text-muted-foreground">Version {status.version} has been downloaded</div> <div className="text-sm text-muted-foreground">
Version {status.version} has been downloaded
</div>
</div> </div>
</div> </div>
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
The app needs to restart to complete the installation. You can do this now or later at your convenience. The app needs to restart to complete the installation. You can do this now or later at
your convenience.
</div> </div>
<Button onClick={restartAndInstall} className="w-full" size="sm"> <Button onClick={restartAndInstall} className="w-full" size="sm">
<RefreshCw className="h-4 w-4 mr-2" /> <RefreshCw className="h-4 w-4 mr-2" />
+27 -19
View File
@@ -1,33 +1,41 @@
import * as React from 'react'; import * as React from 'react';
import { Check } from 'lucide-react';
import { cn } from '@/lib/utils/cn'; import { cn } from '@/lib/utils/cn';
export interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> { export interface CheckboxProps {
checked?: boolean;
onCheckedChange?: (checked: boolean) => void; onCheckedChange?: (checked: boolean) => void;
disabled?: boolean;
className?: string;
id?: string;
} }
const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>( const Checkbox = React.forwardRef<HTMLButtonElement, CheckboxProps>(
({ className, onCheckedChange, ...props }, ref) => { ({ checked = false, onCheckedChange, disabled = false, className, id, ...props }, ref) => {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (onCheckedChange) {
onCheckedChange(e.target.checked);
}
// Call original onChange if provided
if (props.onChange) {
props.onChange(e);
}
};
return ( return (
<input <button
type="checkbox" type="button"
ref={ref}
id={id}
role="checkbox"
aria-checked={checked}
disabled={disabled}
onClick={() => {
if (!disabled && onCheckedChange) {
onCheckedChange(!checked);
}
}}
className={cn( className={cn(
'h-4 w-4 rounded border-gray-300 text-primary focus:ring-2 focus:ring-primary focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', 'h-4 w-4 rounded border-2 flex items-center justify-center shrink-0 transition-colors',
checked ? 'bg-accent border-accent' : 'border-muted-foreground/30',
disabled && 'opacity-50 cursor-not-allowed',
!disabled && 'cursor-pointer',
className, className,
)} )}
ref={ref}
onChange={handleChange}
{...props} {...props}
/> >
{checked && <Check className="h-3 w-3 text-accent-foreground" />}
</button>
); );
}, },
); );