mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 10:16:49 +00:00
feat(ui): enhance settings dialog with tabbed interface and scrollable areas
- Add Radix UI scroll-area and tabs components - Redesign settings dialog with tabbed layout for better organization - Add Card components for structured content display - Update accounts and profiles tables UI - Add scrollable areas for better content management - Improve overall dialog loading and styling
This commit is contained in:
@@ -10,8 +10,10 @@
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@tanstack/react-query": "^5.90.12",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
@@ -184,6 +186,8 @@
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="],
|
||||
|
||||
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
|
||||
|
||||
"@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw=="],
|
||||
@@ -226,10 +230,14 @@
|
||||
|
||||
"@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="],
|
||||
|
||||
"@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.10", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A=="],
|
||||
|
||||
"@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g=="],
|
||||
|
||||
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="],
|
||||
|
||||
"@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="],
|
||||
|
||||
"@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="],
|
||||
|
||||
"@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="],
|
||||
|
||||
@@ -21,8 +21,10 @@
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@tanstack/react-query": "^5.90.12",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
|
||||
@@ -29,11 +29,12 @@ export function AccountsTable({ data, defaultAccount }: AccountsTableProps) {
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: 'Name',
|
||||
size: 200,
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center gap-2">
|
||||
{row.original.name}
|
||||
<span className="font-medium">{row.original.name}</span>
|
||||
{row.original.name === defaultAccount && (
|
||||
<span className="text-xs bg-primary text-primary-foreground px-1.5 py-0.5 rounded">
|
||||
<span className="text-xs bg-primary/10 text-primary px-1.5 py-0.5 rounded border border-primary/20">
|
||||
default
|
||||
</span>
|
||||
)}
|
||||
@@ -43,39 +44,46 @@ export function AccountsTable({ data, defaultAccount }: AccountsTableProps) {
|
||||
{
|
||||
accessorKey: 'type',
|
||||
header: 'Type',
|
||||
cell: ({ row }) => row.original.type || 'oauth',
|
||||
size: 100,
|
||||
cell: ({ row }) => (
|
||||
<span className="capitalize text-muted-foreground">{row.original.type || 'oauth'}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'created',
|
||||
header: 'Created',
|
||||
size: 150,
|
||||
cell: ({ row }) => {
|
||||
const date = new Date(row.original.created);
|
||||
return date.toLocaleDateString();
|
||||
return <span className="text-muted-foreground">{date.toLocaleDateString()}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'last_used',
|
||||
header: 'Last Used',
|
||||
size: 150,
|
||||
cell: ({ row }) => {
|
||||
if (!row.original.last_used) return '-';
|
||||
if (!row.original.last_used) return <span className="text-muted-foreground/50">-</span>;
|
||||
const date = new Date(row.original.last_used);
|
||||
return date.toLocaleDateString();
|
||||
return <span className="text-muted-foreground">{date.toLocaleDateString()}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Actions',
|
||||
size: 100,
|
||||
cell: ({ row }) => {
|
||||
const isDefault = row.original.name === defaultAccount;
|
||||
return (
|
||||
<Button
|
||||
variant={isDefault ? 'outline' : 'default'}
|
||||
variant={isDefault ? 'secondary' : 'default'}
|
||||
size="sm"
|
||||
className="h-8 px-2 w-full"
|
||||
disabled={isDefault || setDefaultMutation.isPending}
|
||||
onClick={() => setDefaultMutation.mutate(row.original.name)}
|
||||
>
|
||||
<Check className="w-4 h-4 mr-1" />
|
||||
{isDefault ? 'Default' : 'Set Default'}
|
||||
<Check className={`w-3 h-3 mr-1.5 ${isDefault ? 'opacity-50' : ''}`} />
|
||||
{isDefault ? 'Active' : 'Set Default'}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
@@ -111,7 +119,7 @@ export function AccountsTable({ data, defaultAccount }: AccountsTableProps) {
|
||||
type: 'w-[100px]',
|
||||
created: 'w-[150px]',
|
||||
last_used: 'w-[150px]',
|
||||
actions: 'w-[120px]',
|
||||
actions: 'w-[100px]',
|
||||
}[header.id] || 'w-auto';
|
||||
|
||||
return (
|
||||
|
||||
@@ -20,17 +20,16 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { MoreHorizontal, Trash2, Edit, Settings } from 'lucide-react';
|
||||
import { MoreHorizontal, Trash2, Edit } from 'lucide-react';
|
||||
import { useDeleteProfile } from '@/hooks/use-profiles';
|
||||
import type { Profile } from '@/lib/api-client';
|
||||
|
||||
interface ProfilesTableProps {
|
||||
data: Profile[];
|
||||
onEdit?: (profile: Profile) => void;
|
||||
onEditSettings?: (profile: Profile) => void;
|
||||
}
|
||||
|
||||
export function ProfilesTable({ data, onEdit, onEditSettings }: ProfilesTableProps) {
|
||||
export function ProfilesTable({ data, onEditSettings }: ProfilesTableProps) {
|
||||
const deleteMutation = useDeleteProfile();
|
||||
|
||||
const columns: ColumnDef<Profile>[] = [
|
||||
@@ -64,22 +63,16 @@ export function ProfilesTable({ data, onEdit, onEditSettings }: ProfilesTablePro
|
||||
<MoreHorizontal className="w-4 h-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{onEdit && (
|
||||
<DropdownMenuItem onClick={() => onEdit(row.original)}>
|
||||
<Edit className="w-4 h-4 mr-2" />
|
||||
Edit Profile
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuContent align="end" className="bg-white dark:bg-zinc-950 border shadow-md">
|
||||
{onEditSettings && (
|
||||
<DropdownMenuItem onClick={() => onEditSettings(row.original)}>
|
||||
<Settings className="w-4 h-4 mr-2" />
|
||||
Edit Settings
|
||||
<Edit className="w-4 h-4 mr-2" />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
className="text-red-600"
|
||||
className="text-red-600 focus:text-red-600 focus:bg-red-100/50"
|
||||
onClick={() => deleteMutation.mutate(row.original.name)}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 mr-2" />
|
||||
|
||||
@@ -42,6 +42,10 @@ interface SettingsDialogProps {
|
||||
* Inner component that manages local edits state
|
||||
* Gets unmounted/remounted via key prop when dialog closes/opens
|
||||
*/
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
|
||||
function SettingsDialogContent({
|
||||
profileName,
|
||||
onClose,
|
||||
@@ -147,42 +151,94 @@ function SettingsDialogContent({
|
||||
return (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Environment Settings: {profileName}</DialogTitle>
|
||||
<DialogTitle>Edit Profile: {profileName}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Edit environment variables for this profile. Sensitive keys are masked.
|
||||
Configure environment variables and settings for this profile.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="w-6 h-6 animate-spin text-muted-foreground" />
|
||||
<span className="ml-2 text-muted-foreground">Loading settings...</span>
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-muted-foreground" />
|
||||
<span className="ml-3 text-muted-foreground">Loading settings...</span>
|
||||
</div>
|
||||
) : currentSettings?.env && Object.keys(currentSettings.env).length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
{Object.entries(currentSettings.env).map(([key, value]) => (
|
||||
<div key={key}>
|
||||
<Label>{key}</Label>
|
||||
{isSensitiveKey(key) ? (
|
||||
<MaskedInput value={value} onChange={(e) => updateEnvValue(key, e.target.value)} />
|
||||
) : (
|
||||
<Input
|
||||
value={value}
|
||||
onChange={(e) => updateEnvValue(key, e.target.value)}
|
||||
className="font-mono"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
) : (
|
||||
<div className="flex flex-col h-[60vh]">
|
||||
<Tabs defaultValue="env" className="flex-1 flex flex-col overflow-hidden">
|
||||
<TabsList className="w-full justify-start border-b rounded-none p-0 h-auto bg-transparent">
|
||||
<TabsTrigger
|
||||
value="env"
|
||||
className="rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent px-4 py-2"
|
||||
>
|
||||
Environment
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="general"
|
||||
className="rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent px-4 py-2"
|
||||
>
|
||||
General
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{data && (
|
||||
<div className="pt-4 text-xs text-muted-foreground border-t">
|
||||
<p>Path: {data.path}</p>
|
||||
<p>Last modified: {new Date(data.mtime).toLocaleString()}</p>
|
||||
</div>
|
||||
)}
|
||||
<TabsContent value="env" className="flex-1 overflow-hidden p-4 pt-4 m-0">
|
||||
<ScrollArea className="h-full pr-4">
|
||||
{currentSettings?.env && Object.keys(currentSettings.env).length > 0 ? (
|
||||
<div className="space-y-6">
|
||||
{Object.entries(currentSettings.env).map(([key, value]) => (
|
||||
<div key={key} className="space-y-2">
|
||||
<Label className="text-sm font-medium text-foreground">{key}</Label>
|
||||
{isSensitiveKey(key) ? (
|
||||
<MaskedInput
|
||||
value={value}
|
||||
onChange={(e) => updateEnvValue(key, e.target.value)}
|
||||
className="font-mono text-sm"
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
value={value}
|
||||
onChange={(e) => updateEnvValue(key, e.target.value)}
|
||||
className="font-mono text-sm"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-12 text-center text-muted-foreground bg-muted/30 rounded-lg border border-dashed">
|
||||
<p>No environment variables configured.</p>
|
||||
<p className="text-xs mt-1">Add variables in your settings.json file.</p>
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
</TabsContent>
|
||||
|
||||
<div className="flex justify-end gap-2 pt-4">
|
||||
<TabsContent value="general" className="p-4 m-0">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Profile Information</CardTitle>
|
||||
<CardDescription>Details about this configuration file.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{data && (
|
||||
<>
|
||||
<div className="grid grid-cols-3 gap-2 text-sm">
|
||||
<span className="font-medium text-muted-foreground">Path</span>
|
||||
<code className="col-span-2 bg-muted p-1 rounded text-xs break-all">
|
||||
{data.path}
|
||||
</code>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2 text-sm">
|
||||
<span className="font-medium text-muted-foreground">Last Modified</span>
|
||||
<span className="col-span-2">{new Date(data.mtime).toLocaleString()}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<div className="pt-4 mt-auto border-t flex justify-end gap-2">
|
||||
<Button type="button" variant="outline" onClick={onClose}>
|
||||
<X className="w-4 h-4 mr-2" /> Cancel
|
||||
</Button>
|
||||
@@ -193,16 +249,12 @@ function SettingsDialogContent({
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="w-4 h-4 mr-2" /> Save
|
||||
<Save className="w-4 h-4 mr-2" /> Save Changes
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-8 text-center text-muted-foreground">
|
||||
No environment variables configured for this profile.
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ConfirmDialog
|
||||
@@ -231,7 +283,7 @@ export function SettingsDialog({ open, onClose, profileName }: SettingsDialogPro
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
||||
<DialogContent className="sm:max-w-3xl max-h-[80vh] overflow-y-auto">
|
||||
{/* Key prop ensures fresh state on each open */}
|
||||
{open && profileName && (
|
||||
<SettingsDialogContent
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import * as React from 'react';
|
||||
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const ScrollArea = React.forwardRef<
|
||||
React.ComponentRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn('relative overflow-hidden', className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
));
|
||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
|
||||
|
||||
const ScrollBar = React.forwardRef<
|
||||
React.ComponentRef<typeof ScrollAreaPrimitive.Scrollbar>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Scrollbar>
|
||||
>(({ className, orientation = 'vertical', ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.Scrollbar
|
||||
ref={ref}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
'flex touch-none select-none transition-colors',
|
||||
orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent p-[1px]',
|
||||
orientation === 'horizontal' && 'h-2.5 flex-col border-t border-t-transparent p-[1px]',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Thumb className="relative flex-1 rounded-full bg-border" />
|
||||
</ScrollAreaPrimitive.Scrollbar>
|
||||
));
|
||||
ScrollBar.displayName = ScrollAreaPrimitive.Scrollbar.displayName;
|
||||
|
||||
export { ScrollArea, ScrollBar };
|
||||
@@ -0,0 +1,53 @@
|
||||
import * as React from 'react';
|
||||
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const Tabs = TabsPrimitive.Root;
|
||||
|
||||
const TabsList = React.forwardRef<
|
||||
React.ComponentRef<typeof TabsPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsList.displayName = TabsPrimitive.List.displayName;
|
||||
|
||||
const TabsTrigger = React.forwardRef<
|
||||
React.ComponentRef<typeof TabsPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
||||
|
||||
const TabsContent = React.forwardRef<
|
||||
React.ComponentRef<typeof TabsPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
||||
@@ -27,6 +27,9 @@
|
||||
--ring: oklch(0.2 0.02 40); /* Match primary */
|
||||
--radius: 0.5rem;
|
||||
|
||||
--popover: oklch(0.9635 0.0067 97.35); /* Match background */
|
||||
--popover-foreground: oklch(0.2 0.02 40); /* Match foreground */
|
||||
|
||||
/* Sidebar colors - Light */
|
||||
--sidebar: oklch(0.9635 0.0067 97.35); /* Pampas */
|
||||
--sidebar-foreground: oklch(0.2 0.02 40);
|
||||
@@ -63,6 +66,9 @@
|
||||
|
||||
--ring: oklch(0.9635 0.0067 97.35);
|
||||
|
||||
--popover: oklch(0.21 0.006 100); /* Match dark bg */
|
||||
--popover-foreground: oklch(0.9635 0.0067 97.35); /* Match dark fg */
|
||||
|
||||
/* Sidebar Dark Theme */
|
||||
--sidebar: oklch(0.21 0.006 100); /* Match bg */
|
||||
--sidebar-foreground: oklch(0.9635 0.0067 97.35);
|
||||
|
||||
+1
-10
@@ -19,11 +19,6 @@ export function ApiPage() {
|
||||
const [settingsProfileName, setSettingsProfileName] = useState<string | null>(null);
|
||||
const { data, isLoading } = useProfiles();
|
||||
|
||||
const handleEdit = (profile: Profile) => {
|
||||
setEditingProfile(profile);
|
||||
setDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleEditSettings = (profile: Profile) => {
|
||||
setSettingsProfileName(profile.name);
|
||||
setSettingsDialogOpen(true);
|
||||
@@ -57,11 +52,7 @@ export function ApiPage() {
|
||||
{isLoading ? (
|
||||
<div className="text-muted-foreground">Loading profiles...</div>
|
||||
) : (
|
||||
<ProfilesTable
|
||||
data={data?.profiles || []}
|
||||
onEdit={handleEdit}
|
||||
onEditSettings={handleEditSettings}
|
||||
/>
|
||||
<ProfilesTable data={data?.profiles || []} onEditSettings={handleEditSettings} />
|
||||
)}
|
||||
|
||||
<ProfileDialog open={dialogOpen} onClose={handleCloseDialog} profile={editingProfile} />
|
||||
|
||||
Reference in New Issue
Block a user