refactor: migrate UsageScriptModal to shadcn/ui Dialog component

Migrate the usage script configuration modal from custom modal implementation to shadcn/ui Dialog component to maintain consistent styling across the entire application.

## Changes

### UsageScriptModal.tsx
- Replace custom modal structure (fixed positioning, backdrop) with Dialog component
- Remove X icon import (Dialog includes built-in close button)
- Add Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter imports
- Add Button component import for action buttons
- Update props interface to include isOpen boolean prop
- Restructure component layout:
  - Use DialogHeader with DialogTitle for header section
  - Apply -mx-6 px-6 pattern for full-width scrollable content
  - Use DialogFooter with flex-col sm:flex-row sm:justify-between layout
- Convert custom buttons to Button components:
  - Test/Format buttons: variant="outline" size="sm"
  - Cancel button: variant="ghost" size="sm"
  - Save button: variant="default" size="sm"
- Maintain all existing functionality (preset templates, JSON editor, validation, testing, formatting)

### App.tsx
- Update UsageScriptModal usage to pass isOpen prop
- Use Boolean(usageProvider) to control dialog open state

## Benefits
- **Consistent styling**: All dialogs now use the same shadcn/ui Dialog component
- **Better accessibility**: Automatic focus management, ESC key handling, ARIA attributes
- **Code maintainability**: Reduced custom modal boilerplate, easier to update styling globally
- **User experience**: Unified look and feel across settings, providers, MCP, and usage script dialogs

All TypeScript type checks and Prettier formatting checks pass.
This commit is contained in:
Jason
2025-10-16 16:32:50 +08:00
parent 92528e6a9f
commit cfefe6b52a
2 changed files with 37 additions and 37 deletions

View File

@@ -304,6 +304,7 @@ function App() {
<UsageScriptModal <UsageScriptModal
provider={usageProvider} provider={usageProvider}
appType={activeApp} appType={activeApp}
isOpen={Boolean(usageProvider)}
onClose={() => setUsageProvider(null)} onClose={() => setUsageProvider(null)}
onSave={(script) => { onSave={(script) => {
void handleSaveUsageScript(usageProvider, script); void handleSaveUsageScript(usageProvider, script);

View File

@@ -1,15 +1,24 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { X, Play, Wand2 } from "lucide-react"; import { Play, Wand2 } from "lucide-react";
import { Provider, UsageScript } from "../types"; import { Provider, UsageScript } from "../types";
import { usageApi, type AppType } from "@/lib/api"; import { usageApi, type AppType } from "@/lib/api";
import JsonEditor from "./JsonEditor"; import JsonEditor from "./JsonEditor";
import * as prettier from "prettier/standalone"; import * as prettier from "prettier/standalone";
import * as parserBabel from "prettier/parser-babel"; import * as parserBabel from "prettier/parser-babel";
import * as pluginEstree from "prettier/plugins/estree"; import * as pluginEstree from "prettier/plugins/estree";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
interface UsageScriptModalProps { interface UsageScriptModalProps {
provider: Provider; provider: Provider;
appType: AppType; appType: AppType;
isOpen: boolean;
onClose: () => void; onClose: () => void;
onSave: (script: UsageScript) => void; onSave: (script: UsageScript) => void;
onNotify?: ( onNotify?: (
@@ -79,6 +88,7 @@ const PRESET_TEMPLATES: Record<string, string> = {
const UsageScriptModal: React.FC<UsageScriptModalProps> = ({ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
provider, provider,
appType, appType,
isOpen,
onClose, onClose,
onSave, onSave,
onNotify, onNotify,
@@ -162,23 +172,14 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
}; };
return ( return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"> <Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
<div className="bg-white dark:bg-gray-900 rounded-lg shadow-xl w-full max-w-3xl max-h-[90vh] overflow-hidden flex flex-col"> <DialogContent className="max-w-3xl max-h-[90vh] flex flex-col">
{/* Header */} <DialogHeader>
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700"> <DialogTitle> - {provider.name}</DialogTitle>
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100"> </DialogHeader>
- {provider.name}
</h2>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
>
<X size={20} />
</button>
</div>
{/* Content */} {/* Content - Scrollable */}
<div className="flex-1 overflow-y-auto px-6 py-4 space-y-4"> <div className="flex-1 overflow-y-auto -mx-6 px-6 space-y-4">
{/* 启用开关 */} {/* 启用开关 */}
<label className="flex items-center gap-2 cursor-pointer"> <label className="flex items-center gap-2 cursor-pointer">
<input <input
@@ -338,44 +339,42 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
</div> </div>
{/* Footer */} {/* Footer */}
<div className="flex items-center justify-between px-6 py-4 border-t border-gray-200 dark:border-gray-700"> <DialogFooter className="flex-col sm:flex-row sm:justify-between gap-3 pt-4">
{/* Left side - Test and Format buttons */}
<div className="flex gap-2"> <div className="flex gap-2">
<button <Button
variant="outline"
size="sm"
onClick={handleTest} onClick={handleTest}
disabled={!script.enabled || testing} disabled={!script.enabled || testing}
className="inline-flex items-center gap-2 px-4 py-2 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
> >
<Play size={14} /> <Play size={14} />
{testing ? "测试中..." : "测试脚本"} {testing ? "测试中..." : "测试脚本"}
</button> </Button>
<button <Button
variant="outline"
size="sm"
onClick={handleFormat} onClick={handleFormat}
disabled={!script.enabled} disabled={!script.enabled}
className="inline-flex items-center gap-2 px-4 py-2 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
title="格式化代码 (Prettier)" title="格式化代码 (Prettier)"
> >
<Wand2 size={14} /> <Wand2 size={14} />
</button> </Button>
</div> </div>
{/* Right side - Cancel and Save buttons */}
<div className="flex gap-2"> <div className="flex gap-2">
<button <Button variant="ghost" size="sm" onClick={onClose}>
onClick={onClose}
className="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
>
</button> </Button>
<button <Button variant="default" size="sm" onClick={handleSave}>
onClick={handleSave}
className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors"
>
</button> </Button>
</div> </div>
</div> </DialogFooter>
</div> </DialogContent>
</div> </Dialog>
); );
}; };