Enhance layout

This commit is contained in:
decolua
2026-01-31 12:58:04 +07:00
parent 8c37b39eed
commit 8897df5036
23 changed files with 458 additions and 326 deletions

View File

@@ -191,23 +191,23 @@ export default function ClaudeToolCard({
};
return (
<Card className="overflow-hidden">
<Card padding="sm" className="overflow-hidden">
<div className="flex items-center justify-between hover:cursor-pointer" onClick={onToggle}>
<div className="flex items-center gap-4">
<div className="size-12 flex items-center justify-center">
<Image src="/providers/claude.png" alt={tool.name} width={40} height={40} className="size-12 object-contain rounded-xl max-w-[48px] max-h-[48px]" sizes="48px" onError={(e) => { e.target.style.display = "none"; }} />
<div className="flex items-center gap-3">
<div className="size-8 flex items-center justify-center shrink-0">
<Image src="/providers/claude.png" alt={tool.name} width={32} height={32} className="size-8 object-contain rounded-lg" sizes="32px" onError={(e) => { e.target.style.display = "none"; }} />
</div>
<div>
<div className="min-w-0">
<div className="flex items-center gap-2">
<h3 className="font-semibold text-lg">{tool.name}</h3>
{configStatus === "configured" && <span className="px-2 py-0.5 text-xs font-medium bg-green-500/10 text-green-600 dark:text-green-400 rounded-full">Connected</span>}
{configStatus === "not_configured" && <span className="px-2 py-0.5 text-xs font-medium bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 rounded-full">Not configured</span>}
{configStatus === "other" && <span className="px-2 py-0.5 text-xs font-medium bg-blue-500/10 text-blue-600 dark:text-blue-400 rounded-full">Other endpoint</span>}
<h3 className="font-medium text-sm">{tool.name}</h3>
{configStatus === "configured" && <span className="px-1.5 py-0.5 text-[10px] font-medium bg-green-500/10 text-green-600 dark:text-green-400 rounded-full">Connected</span>}
{configStatus === "not_configured" && <span className="px-1.5 py-0.5 text-[10px] font-medium bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 rounded-full">Not configured</span>}
{configStatus === "other" && <span className="px-1.5 py-0.5 text-[10px] font-medium bg-blue-500/10 text-blue-600 dark:text-blue-400 rounded-full">Other</span>}
</div>
<p className="text-sm text-text-muted">{tool.description}</p>
<p className="text-xs text-text-muted truncate">{tool.description}</p>
</div>
</div>
<span className={`material-symbols-outlined text-text-muted transition-transform ${isExpanded ? "rotate-180" : ""}`}>expand_more</span>
<span className={`material-symbols-outlined text-text-muted text-[20px] transition-transform ${isExpanded ? "rotate-180" : ""}`}>expand_more</span>
</div>
{isExpanded && (

View File

@@ -164,23 +164,23 @@ wire_api = "responses"
};
return (
<Card className="overflow-hidden">
<Card padding="sm" className="overflow-hidden">
<div className="flex items-center justify-between hover:cursor-pointer" onClick={onToggle}>
<div className="flex items-center gap-4">
<div className="size-12 flex items-center justify-center">
<Image src="/providers/codex.png" alt={tool.name} width={40} height={40} className="size-12 object-contain rounded-xl max-w-[48px] max-h-[48px]" sizes="48px" onError={(e) => { e.target.style.display = "none"; }} />
<div className="flex items-center gap-3">
<div className="size-8 flex items-center justify-center shrink-0">
<Image src="/providers/codex.png" alt={tool.name} width={32} height={32} className="size-8 object-contain rounded-lg" sizes="32px" onError={(e) => { e.target.style.display = "none"; }} />
</div>
<div>
<div className="min-w-0">
<div className="flex items-center gap-2">
<h3 className="font-semibold text-lg">{tool.name}</h3>
{configStatus === "configured" && <span className="px-2 py-0.5 text-xs font-medium bg-green-500/10 text-green-600 dark:text-green-400 rounded-full">Connected</span>}
{configStatus === "not_configured" && <span className="px-2 py-0.5 text-xs font-medium bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 rounded-full">Not configured</span>}
{configStatus === "other" && <span className="px-2 py-0.5 text-xs font-medium bg-blue-500/10 text-blue-600 dark:text-blue-400 rounded-full">Other endpoint</span>}
<h3 className="font-medium text-sm">{tool.name}</h3>
{configStatus === "configured" && <span className="px-1.5 py-0.5 text-[10px] font-medium bg-green-500/10 text-green-600 dark:text-green-400 rounded-full">Connected</span>}
{configStatus === "not_configured" && <span className="px-1.5 py-0.5 text-[10px] font-medium bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 rounded-full">Not configured</span>}
{configStatus === "other" && <span className="px-1.5 py-0.5 text-[10px] font-medium bg-blue-500/10 text-blue-600 dark:text-blue-400 rounded-full">Other</span>}
</div>
<p className="text-sm text-text-muted">{tool.description}</p>
<p className="text-xs text-text-muted truncate">{tool.description}</p>
</div>
</div>
<span className={`material-symbols-outlined text-text-muted transition-transform ${isExpanded ? "rotate-180" : ""}`}>expand_more</span>
<span className={`material-symbols-outlined text-text-muted text-[20px] transition-transform ${isExpanded ? "rotate-180" : ""}`}>expand_more</span>
</div>
{isExpanded && (

View File

@@ -233,43 +233,43 @@ export default function DefaultToolCard({ toolId, tool, isExpanded, onToggle, ba
<Image
src={tool.image}
alt={tool.name}
width={40}
height={40}
className="size-12 object-contain rounded-xl bg-gray-500 max-w-[48px] max-h-[48px]"
sizes="48px"
width={32}
height={32}
className="size-8 object-contain rounded-lg"
sizes="32px"
onError={(e) => { e.target.style.display = "none"; }}
/>
);
}
if (tool.icon) {
return <span className="material-symbols-outlined text-3xl" style={{ color: tool.color }}>{tool.icon}</span>;
return <span className="material-symbols-outlined text-xl" style={{ color: tool.color }}>{tool.icon}</span>;
}
return (
<Image
src={`/providers/${toolId}.png`}
alt={tool.name}
width={40}
height={40}
className="size-10 object-contain rounded-xl max-w-[40px] max-h-[40px]"
sizes="40px"
width={32}
height={32}
className="size-8 object-contain rounded-lg"
sizes="32px"
onError={(e) => { e.target.style.display = "none"; }}
/>
);
};
return (
<Card className="overflow-hidden">
<Card padding="sm" className="overflow-hidden">
<div className="flex items-center justify-between hover:cursor-pointer" onClick={onToggle}>
<div className="flex items-center gap-4">
<div className="size-12 rounded-xl flex items-center justify-center">
<div className="flex items-center gap-3">
<div className="size-8 rounded-lg flex items-center justify-center shrink-0">
{renderIcon()}
</div>
<div>
<h3 className="font-semibold text-lg">{tool.name}</h3>
<p className="text-sm text-text-muted">{tool.description}</p>
<div className="min-w-0">
<h3 className="font-medium text-sm">{tool.name}</h3>
<p className="text-xs text-text-muted truncate">{tool.description}</p>
</div>
</div>
<span className={`material-symbols-outlined text-text-muted transition-transform ${isExpanded ? "rotate-180" : ""}`}>expand_more</span>
<span className={`material-symbols-outlined text-text-muted text-[20px] transition-transform ${isExpanded ? "rotate-180" : ""}`}>expand_more</span>
</div>
{isExpanded && (

View File

@@ -120,12 +120,13 @@ export default function CombosPage() {
{combos.length === 0 ? (
<Card>
<div className="text-center py-12">
<span className="material-symbols-outlined text-5xl text-text-muted mb-3 block">
layers
</span>
<p className="text-text-muted mb-4">No combos yet</p>
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary/10 text-primary mb-4">
<span className="material-symbols-outlined text-[32px]">layers</span>
</div>
<p className="text-text-main font-medium mb-1">No combos yet</p>
<p className="text-sm text-text-muted mb-4">Create model combos with fallback support</p>
<Button icon="add" onClick={() => setShowCreateModal(true)}>
Create your first combo
Create Combo
</Button>
</div>
</Card>
@@ -168,62 +169,57 @@ export default function CombosPage() {
function ComboCard({ combo, copied, onCopy, onEdit, onDelete }) {
return (
<Card>
<div className="flex items-start justify-between">
<div className="flex-1">
{/* Name + Copy */}
<div className="flex items-center gap-2 mb-3">
<span className="material-symbols-outlined text-primary">layers</span>
<code className="text-lg font-semibold font-mono">{combo.name}</code>
<button
onClick={() => onCopy(combo.name, `combo-${combo.id}`)}
className="p-1 hover:bg-sidebar rounded text-text-muted hover:text-primary"
title="Copy combo name"
>
<span className="material-symbols-outlined text-sm">
{copied === `combo-${combo.id}` ? "check" : "content_copy"}
</span>
</button>
<Card padding="sm" className="group">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3 flex-1 min-w-0">
<div className="size-8 rounded-lg bg-primary/10 flex items-center justify-center shrink-0">
<span className="material-symbols-outlined text-primary text-[18px]">layers</span>
</div>
{/* Models list */}
<div className="flex flex-col gap-1.5">
{combo.models.length === 0 ? (
<p className="text-sm text-text-muted italic">No models added</p>
) : (
combo.models.map((model, index) => (
<div key={index} className="flex items-center gap-2">
<span className="text-xs text-text-muted w-5">{index + 1}.</span>
<code className="text-sm font-mono bg-sidebar px-2 py-0.5 rounded">
{model}
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<code className="text-sm font-medium font-mono truncate">{combo.name}</code>
<button
onClick={(e) => { e.stopPropagation(); onCopy(combo.name, `combo-${combo.id}`); }}
className="p-0.5 hover:bg-black/5 dark:hover:bg-white/5 rounded text-text-muted hover:text-primary transition-colors opacity-0 group-hover:opacity-100"
title="Copy combo name"
>
<span className="material-symbols-outlined text-[14px]">
{copied === `combo-${combo.id}` ? "check" : "content_copy"}
</span>
</button>
</div>
<div className="flex items-center gap-1 mt-0.5 flex-wrap">
{combo.models.length === 0 ? (
<span className="text-xs text-text-muted italic">No models</span>
) : (
combo.models.slice(0, 3).map((model, index) => (
<code key={index} className="text-[10px] font-mono bg-black/5 dark:bg-white/5 px-1.5 py-0.5 rounded text-text-muted">
{model.split("/").pop()}
</code>
{index === 0 && (
<span className="text-xs text-primary font-medium">Primary</span>
)}
{index > 0 && (
<span className="text-xs text-text-muted">Fallback</span>
)}
</div>
))
)}
))
)}
{combo.models.length > 3 && (
<span className="text-[10px] text-text-muted">+{combo.models.length - 3} more</span>
)}
</div>
</div>
</div>
{/* Actions */}
<div className="flex gap-1">
<div className="flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
<button
onClick={onEdit}
className="p-2 hover:bg-sidebar rounded text-text-muted hover:text-primary"
className="p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded text-text-muted hover:text-primary transition-colors"
title="Edit"
>
<span className="material-symbols-outlined text-lg">edit</span>
<span className="material-symbols-outlined text-[16px]">edit</span>
</button>
<button
onClick={onDelete}
className="p-2 hover:bg-red-50 rounded text-red-500"
className="p-1.5 hover:bg-red-500/10 rounded text-red-500 transition-colors"
title="Delete"
>
<span className="material-symbols-outlined text-lg">delete</span>
<span className="material-symbols-outlined text-[16px]">delete</span>
</button>
</div>
</div>
@@ -321,9 +317,8 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders }) {
isOpen={isOpen}
onClose={onClose}
title={isEdit ? "Edit Combo" : "Create Combo"}
size="md"
>
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-3">
{/* Name */}
<div>
<Input
@@ -333,87 +328,94 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders }) {
placeholder="my-combo"
error={nameError}
/>
<p className="text-xs text-text-muted mt-1">
<p className="text-[10px] text-text-muted mt-0.5">
Only letters, numbers, - and _ allowed
</p>
</div>
{/* Models */}
<div>
<div className="flex items-center justify-between mb-2">
<label className="text-sm font-medium">Models</label>
<Button
size="sm"
variant="secondary"
icon="add"
onClick={() => setShowModelSelect(true)}
>
Add Model
</Button>
</div>
<label className="text-sm font-medium mb-1.5 block">Models</label>
{models.length === 0 ? (
<div className="text-center py-6 border border-dashed border-border rounded-lg">
<p className="text-sm text-text-muted">No models added</p>
<p className="text-xs text-text-muted mt-1">Click &quot;Add Model&quot; to add</p>
<div className="text-center py-4 border border-dashed border-black/10 dark:border-white/10 rounded-lg bg-black/[0.01] dark:bg-white/[0.01]">
<span className="material-symbols-outlined text-text-muted text-xl mb-1">layers</span>
<p className="text-xs text-text-muted">No models added yet</p>
</div>
) : (
<div className="flex flex-col gap-2 max-h-[240px] overflow-y-auto">
<div className="flex flex-col gap-1 max-h-[200px] overflow-y-auto">
{models.map((model, index) => (
<div
key={index}
className="flex items-center gap-2"
className="group flex items-center gap-1.5 px-2 py-1 rounded-md bg-black/[0.02] dark:bg-white/[0.02] hover:bg-black/[0.04] dark:hover:bg-white/[0.04] transition-colors"
>
{/* Priority arrows */}
<div className="flex flex-col gap-0">
{/* Index badge */}
<span className="text-[10px] font-medium text-text-muted w-3 text-center shrink-0">{index + 1}</span>
{/* Model Input */}
<input
type="text"
value={model}
onChange={(e) => handleModelChange(index, e.target.value)}
placeholder="provider/model"
className="flex-1 min-w-0 px-1.5 py-0.5 text-xs font-mono bg-transparent border-0 focus:outline-none text-text-main placeholder:text-text-muted/50"
/>
{/* Priority arrows - horizontal, always visible */}
<div className="flex items-center gap-0.5">
<button
onClick={() => handleMoveUp(index)}
disabled={index === 0}
className={`p-0.5 rounded ${index === 0 ? "text-text-muted/30" : "hover:bg-surface text-text-muted hover:text-primary"}`}
className={`p-0.5 rounded ${index === 0 ? "text-text-muted/20 cursor-not-allowed" : "text-text-muted hover:text-primary hover:bg-black/5 dark:hover:bg-white/5"}`}
title="Move up"
>
<span className="material-symbols-outlined text-sm leading-none">keyboard_arrow_up</span>
<span className="material-symbols-outlined text-[12px]">arrow_upward</span>
</button>
<button
onClick={() => handleMoveDown(index)}
disabled={index === models.length - 1}
className={`p-0.5 rounded ${index === models.length - 1 ? "text-text-muted/30" : "hover:bg-surface text-text-muted hover:text-primary"}`}
className={`p-0.5 rounded ${index === models.length - 1 ? "text-text-muted/20 cursor-not-allowed" : "text-text-muted hover:text-primary hover:bg-black/5 dark:hover:bg-white/5"}`}
title="Move down"
>
<span className="material-symbols-outlined text-sm leading-none">keyboard_arrow_down</span>
<span className="material-symbols-outlined text-[12px]">arrow_downward</span>
</button>
</div>
{/* Model Input */}
<Input
value={model}
onChange={(e) => handleModelChange(index, e.target.value)}
placeholder="model-name"
className="flex-1"
/>
{/* Remove */}
{/* Remove - always visible */}
<button
onClick={() => handleRemoveModel(index)}
className="p-2 hover:bg-red-50 rounded text-red-500"
className="p-0.5 hover:bg-red-500/10 rounded text-text-muted hover:text-red-500 transition-all"
title="Remove"
>
<span className="material-symbols-outlined text-sm">close</span>
<span className="material-symbols-outlined text-[12px]">close</span>
</button>
</div>
))}
</div>
)}
{/* Add Model button - moved to bottom */}
<button
onClick={() => setShowModelSelect(true)}
className="w-full mt-2 py-2 border border-dashed border-black/10 dark:border-white/10 rounded-lg text-xs text-text-muted hover:text-primary hover:border-primary/30 transition-colors flex items-center justify-center gap-1"
>
<span className="material-symbols-outlined text-[16px]">add</span>
Add Model
</button>
</div>
{/* Actions */}
<div className="flex gap-2 pt-2">
<div className="flex gap-2 pt-1">
<Button onClick={onClose} variant="ghost" fullWidth size="sm">
Cancel
</Button>
<Button
onClick={handleSave}
fullWidth
size="sm"
disabled={!name.trim() || !!nameError || saving}
>
{saving ? "Saving..." : "Apply"}
</Button>
<Button onClick={onClose} variant="ghost" fullWidth>
Cancel
{saving ? "Saving..." : isEdit ? "Save" : "Create"}
</Button>
</div>
</div>

View File

@@ -293,58 +293,48 @@ export default function APIPageClient({ machineId }) {
</div>
{keys.length === 0 ? (
<div className="text-center py-8">
<span className="material-symbols-outlined text-4xl text-text-muted mb-2">
vpn_key
</span>
<p className="text-sm text-text-muted">No API keys yet</p>
<div className="text-center py-12">
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary/10 text-primary mb-4">
<span className="material-symbols-outlined text-[32px]">vpn_key</span>
</div>
<p className="text-text-main font-medium mb-1">No API keys yet</p>
<p className="text-sm text-text-muted mb-4">Create your first API key to get started</p>
<Button icon="add" onClick={() => setShowAddModal(true)}>
Create Key
</Button>
</div>
) : (
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-border">
<th className="text-left p-3 font-medium">Name</th>
<th className="text-left p-3 font-medium">Key</th>
<th className="text-left p-3 font-medium">Created</th>
<th className="text-left p-3 font-medium">Actions</th>
</tr>
</thead>
<tbody>
{keys.map((key) => (
<tr key={key.id} className="border-b border-border hover:bg-sidebar/30">
<td className="p-3 text-sm">{key.name}</td>
<td className="p-3">
<div className="flex items-center gap-2">
<span className="font-mono text-xs text-text-muted">
{key.key}
</span>
<Button
size="sm"
variant="ghost"
icon={copied === key.id ? "check" : "content_copy"}
onClick={() => copy(key.key, key.id)}
/>
</div>
</td>
<td className="p-3 text-sm text-text-muted">
{new Date(key.createdAt).toLocaleDateString()}
</td>
<td className="p-3">
<Button
size="sm"
variant="ghost"
icon="delete"
className="text-red-500"
onClick={() => handleDeleteKey(key.id)}
>
Delete
</Button>
</td>
</tr>
))}
</tbody>
</table>
<div className="flex flex-col">
{keys.map((key) => (
<div
key={key.id}
className="group flex items-center justify-between py-3 border-b border-black/[0.03] dark:border-white/[0.03] last:border-b-0"
>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium">{key.name}</p>
<div className="flex items-center gap-2 mt-1">
<code className="text-xs text-text-muted font-mono">{key.key}</code>
<button
onClick={() => copy(key.key, key.id)}
className="p-1 hover:bg-black/5 dark:hover:bg-white/5 rounded text-text-muted hover:text-primary opacity-0 group-hover:opacity-100 transition-all"
>
<span className="material-symbols-outlined text-[14px]">
{copied === key.id ? "check" : "content_copy"}
</span>
</button>
</div>
<p className="text-xs text-text-muted mt-1">
Created {new Date(key.createdAt).toLocaleDateString()}
</p>
</div>
<button
onClick={() => handleDeleteKey(key.id)}
className="p-2 hover:bg-red-500/10 rounded text-red-500 opacity-0 group-hover:opacity-100 transition-all"
>
<span className="material-symbols-outlined text-[18px]">delete</span>
</button>
</div>
))}
</div>
)}
</Card>

View File

@@ -3,6 +3,7 @@
import { useState, useEffect } from "react";
import { Card, Button, Badge, Toggle, Input } from "@/shared/components";
import { useTheme } from "@/shared/hooks/useTheme";
import { cn } from "@/shared/utils/cn";
import { APP_CONFIG } from "@/shared/constants/config";
export default function ProfilePage() {
@@ -117,7 +118,12 @@ export default function ProfilePage() {
{/* Routing Preferences */}
<Card>
<h3 className="text-lg font-semibold mb-4">Security</h3>
<div className="flex items-center gap-3 mb-4">
<div className="p-2 rounded-lg bg-primary/10 text-primary">
<span className="material-symbols-outlined text-[20px]">shield</span>
</div>
<h3 className="text-lg font-semibold">Security</h3>
</div>
<form onSubmit={handlePasswordChange} className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<label className="text-sm font-medium">Current Password</label>
@@ -168,7 +174,12 @@ export default function ProfilePage() {
{/* Routing Preferences */}
<Card>
<h3 className="text-lg font-semibold mb-4">Routing Strategy</h3>
<div className="flex items-center gap-3 mb-4">
<div className="p-2 rounded-lg bg-blue-500/10 text-blue-500">
<span className="material-symbols-outlined text-[20px]">route</span>
</div>
<h3 className="text-lg font-semibold">Routing Strategy</h3>
</div>
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between">
<div>
@@ -215,7 +226,12 @@ export default function ProfilePage() {
{/* Theme Preferences */}
<Card>
<h3 className="text-lg font-semibold mb-4">Appearance</h3>
<div className="flex items-center gap-3 mb-4">
<div className="p-2 rounded-lg bg-purple-500/10 text-purple-500">
<span className="material-symbols-outlined text-[20px]">palette</span>
</div>
<h3 className="text-lg font-semibold">Appearance</h3>
</div>
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between">
<div>
@@ -231,34 +247,38 @@ export default function ProfilePage() {
</div>
{/* Theme Options */}
<div className="flex gap-3 pt-4 border-t border-border">
{["light", "dark", "system"].map((option) => (
<button
key={option}
onClick={() => setTheme(option)}
className={`flex-1 flex flex-col items-center gap-2 p-4 rounded-lg border transition-all ${
theme === option
? "border-primary bg-primary/5"
: "border-border hover:border-primary/50"
}`}
>
<span className="material-symbols-outlined text-2xl">
{option === "light"
? "light_mode"
: option === "dark"
? "dark_mode"
: "contrast"}
</span>
<span className="text-sm font-medium capitalize">{option}</span>
</button>
))}
<div className="pt-4 border-t border-border">
<div className="inline-flex p-1 rounded-lg bg-black/5 dark:bg-white/5">
{["light", "dark", "system"].map((option) => (
<button
key={option}
onClick={() => setTheme(option)}
className={cn(
"flex items-center gap-2 px-4 py-2 rounded-md font-medium transition-all",
theme === option
? "bg-white dark:bg-white/10 text-text-main shadow-sm"
: "text-text-muted hover:text-text-main"
)}
>
<span className="material-symbols-outlined text-[20px]">
{option === "light" ? "light_mode" : option === "dark" ? "dark_mode" : "contrast"}
</span>
<span className="capitalize">{option}</span>
</button>
))}
</div>
</div>
</div>
</Card>
{/* Data Management */}
<Card>
<h3 className="text-lg font-semibold mb-4">Data</h3>
<div className="flex items-center gap-3 mb-4">
<div className="p-2 rounded-lg bg-green-500/10 text-green-500">
<span className="material-symbols-outlined text-[20px]">database</span>
</div>
<h3 className="text-lg font-semibold">Data</h3>
</div>
<div className="flex flex-col gap-3">
<div className="flex items-center justify-between p-4 rounded-lg bg-bg border border-border">
<div>

View File

@@ -303,14 +303,18 @@ export default function ProviderDetailPage() {
</div>
{connections.length === 0 ? (
<div className="text-center py-8">
<span className="material-symbols-outlined text-4xl text-text-muted mb-2">
{isOAuth ? "lock" : "key"}
</span>
<p className="text-sm text-text-muted">No connections yet</p>
<div className="text-center py-12">
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary/10 text-primary mb-4">
<span className="material-symbols-outlined text-[32px]">{isOAuth ? "lock" : "key"}</span>
</div>
<p className="text-text-main font-medium mb-1">No connections yet</p>
<p className="text-sm text-text-muted mb-4">Add your first connection to get started</p>
<Button icon="add" onClick={() => isOAuth ? setShowOAuthModal(true) : setShowAddApiKeyModal(true)}>
Add Connection
</Button>
</div>
) : (
<div className="flex flex-col gap-2">
<div className="flex flex-col divide-y divide-black/[0.03] dark:divide-white/[0.03]">
{connections
.sort((a, b) => (a.priority || 0) - (b.priority || 0))
.map((conn, index) => (
@@ -618,7 +622,7 @@ function ConnectionRow({ connection, isOAuth, isFirst, isLast, onMoveUp, onMoveD
};
return (
<div className={`flex items-center justify-between p-3 rounded-lg border border-border hover:bg-sidebar/50 ${connection.isActive === false ? 'opacity-60' : ''}`}>
<div className={`group flex items-center justify-between p-3 rounded-lg hover:bg-black/[0.02] dark:hover:bg-white/[0.02] transition-colors ${connection.isActive === false ? "opacity-60" : ""}`}>
<div className="flex items-center gap-3 flex-1 min-w-0">
{/* Priority arrows */}
<div className="flex flex-col">
@@ -666,12 +670,12 @@ function ConnectionRow({ connection, isOAuth, isFirst, isLast, onMoveUp, onMoveD
onChange={onToggleActive}
title={(connection.isActive ?? true) ? "Disable connection" : "Enable connection"}
/>
<div className="flex gap-1 ml-1">
<button onClick={onEdit} className="p-2 hover:bg-sidebar rounded">
<span className="material-symbols-outlined text-base">edit</span>
<div className="flex gap-1 ml-1 opacity-0 group-hover:opacity-100 transition-opacity">
<button onClick={onEdit} className="p-2 hover:bg-black/5 dark:hover:bg-white/5 rounded text-text-muted hover:text-primary">
<span className="material-symbols-outlined text-[18px]">edit</span>
</button>
<button onClick={onDelete} className="p-2 hover:bg-red-50 rounded text-red-500">
<span className="material-symbols-outlined text-base">delete</span>
<button onClick={onDelete} className="p-2 hover:bg-red-500/10 rounded text-red-500">
<span className="material-symbols-outlined text-[18px]">delete</span>
</button>
</div>
</div>

View File

@@ -134,8 +134,8 @@ function ProviderCard({ providerId, provider, stats }) {
const [imgError, setImgError] = useState(false);
return (
<Link href={`/dashboard/providers/${providerId}`}>
<Card padding="sm" className="h-full hover:border-primary/50 transition-colors cursor-pointer">
<Link href={`/dashboard/providers/${providerId}`} className="group">
<Card padding="sm" className="h-full hover:bg-black/[0.01] dark:hover:bg-white/[0.01] transition-colors cursor-pointer">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div
@@ -169,7 +169,7 @@ function ProviderCard({ providerId, provider, stats }) {
</div>
</div>
</div>
<span className="material-symbols-outlined text-text-muted">
<span className="material-symbols-outlined text-text-muted opacity-0 group-hover:opacity-100 transition-opacity">
chevron_right
</span>
</div>
@@ -199,8 +199,8 @@ function ApiKeyProviderCard({ providerId, provider, stats }) {
const { connected, error, errorCode, errorTime } = stats;
return (
<Link href={`/dashboard/providers/${providerId}`}>
<Card padding="sm" className="h-full hover:border-primary/50 transition-colors cursor-pointer">
<Link href={`/dashboard/providers/${providerId}`} className="group">
<Card padding="sm" className="h-full hover:bg-black/[0.01] dark:hover:bg-white/[0.01] transition-colors cursor-pointer">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div
@@ -222,7 +222,7 @@ function ApiKeyProviderCard({ providerId, provider, stats }) {
</div>
</div>
</div>
<span className="material-symbols-outlined text-text-muted">
<span className="material-symbols-outlined text-text-muted opacity-0 group-hover:opacity-100 transition-opacity">
chevron_right
</span>
</div>

View File

@@ -1,36 +1,21 @@
"use client";
import { useState, Suspense } from "react";
import { UsageStats, RequestLogger, CardSkeleton } from "@/shared/components";
import { UsageStats, RequestLogger, CardSkeleton, SegmentedControl } from "@/shared/components";
export default function UsagePage() {
const [activeTab, setActiveTab] = useState("overview");
return (
<div className="flex flex-col gap-6">
{/* Tabs */}
<div className="flex border-b border-border">
<button
onClick={() => setActiveTab("overview")}
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
activeTab === "overview"
? "border-primary text-primary"
: "border-transparent text-text-muted hover:text-text-primary"
}`}
>
Overview
</button>
<button
onClick={() => setActiveTab("logs")}
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
activeTab === "logs"
? "border-primary text-primary"
: "border-transparent text-text-muted hover:text-text-primary"
}`}
>
Logger
</button>
</div>
<SegmentedControl
options={[
{ value: "overview", label: "Overview" },
{ value: "logs", label: "Logger" },
]}
value={activeTab}
onChange={setActiveTab}
/>
{/* Content */}
{activeTab === "overview" ? (

View File

@@ -3,7 +3,7 @@
@custom-variant dark (&:where(.dark, .dark *));
/* Claude-inspired Color Palette */
/* macOS-inspired Color Palette with Terracotta Primary */
:root {
/* Primary - Warm Coral/Terracotta */
--color-primary: #D97757;
@@ -13,15 +13,15 @@
--color-bg: #FBF9F6;
--color-bg-alt: #F5F1ED;
--color-surface: #FFFFFF;
--color-sidebar: #F0EFEC;
--color-border: #E6E4DD;
--color-sidebar: rgba(246, 246, 246, 0.8);
--color-border: rgba(0, 0, 0, 0.1);
--color-text-main: #383733;
--color-text-muted: #75736E;
/* Shadows */
--shadow-soft: 0 2px 10px rgba(0, 0, 0, 0.03), 0 10px 25px rgba(0, 0, 0, 0.02);
--shadow-warm: 0 4px 20px -2px rgba(217, 119, 87, 0.15);
--shadow-elevated: 0 20px 40px -4px rgba(60, 50, 45, 0.08);
/* Shadows - subtle macOS style */
--shadow-soft: 0 1px 3px rgba(0, 0, 0, 0.02), 0 4px 12px rgba(0, 0, 0, 0.015);
--shadow-warm: 0 2px 12px -2px rgba(217, 119, 87, 0.12);
--shadow-elevated: 0 12px 28px -4px rgba(60, 50, 45, 0.06);
}
.dark {
@@ -29,15 +29,15 @@
--color-bg: #191918;
--color-bg-alt: #1F1F1E;
--color-surface: #242423;
--color-sidebar: #1F1F1E;
--color-border: #333331;
--color-sidebar: rgba(30, 30, 30, 0.8);
--color-border: rgba(255, 255, 255, 0.1);
--color-text-main: #ECEBE8;
--color-text-muted: #9E9D99;
/* Dark shadows */
--shadow-soft: 0 2px 10px rgba(0, 0, 0, 0.2), 0 10px 25px rgba(0, 0, 0, 0.15);
--shadow-warm: 0 4px 20px -2px rgba(217, 119, 87, 0.2);
--shadow-elevated: 0 20px 40px -4px rgba(0, 0, 0, 0.4);
/* Dark shadows - subtle macOS style */
--shadow-soft: 0 1px 3px rgba(0, 0, 0, 0.15), 0 4px 12px rgba(0, 0, 0, 0.1);
--shadow-warm: 0 2px 12px -2px rgba(217, 119, 87, 0.15);
--shadow-elevated: 0 12px 28px -4px rgba(0, 0, 0, 0.3);
}
@theme inline {
@@ -72,8 +72,8 @@
--shadow-warm: var(--shadow-warm);
--shadow-elevated: var(--shadow-elevated);
/* Font */
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
/* Font - macOS system fonts */
--font-sans: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'SF Pro Display', system-ui, sans-serif;
}
/* Base styles */
@@ -182,3 +182,38 @@ body {
.animate-border-glow {
animation: border-glow 2s ease-in-out infinite;
}
/* macOS Vibrancy/Blur Effect */
.bg-vibrancy {
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
background: rgba(255, 255, 255, 0.72);
}
.dark .bg-vibrancy {
background: rgba(30, 30, 30, 0.72);
}
/* macOS Traffic Lights */
.traffic-lights {
display: flex;
gap: 8px;
}
.traffic-light {
width: 12px;
height: 12px;
border-radius: 50%;
}
.traffic-light.red {
background: #FF5F56;
}
.traffic-light.yellow {
background: #FFBD2E;
}
.traffic-light.green {
background: #27C93F;
}

View File

@@ -3,12 +3,12 @@
import { cn } from "@/shared/utils/cn";
const variants = {
default: "bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300",
default: "bg-black/5 dark:bg-white/10 text-text-muted",
primary: "bg-primary/10 text-primary",
success: "bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-400 border border-green-100 dark:border-green-800/30",
warning: "bg-yellow-50 dark:bg-yellow-900/20 text-yellow-700 dark:text-yellow-500 border border-yellow-100 dark:border-yellow-800/30",
error: "bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-400 border border-red-100 dark:border-red-800/30",
info: "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-400 border border-blue-100 dark:border-blue-800/30",
success: "bg-green-500/10 text-green-600 dark:text-green-400",
warning: "bg-yellow-500/10 text-yellow-600 dark:text-yellow-400",
error: "bg-red-500/10 text-red-600 dark:text-red-400",
info: "bg-blue-500/10 text-blue-600 dark:text-blue-400",
};
const sizes = {

View File

@@ -3,17 +3,17 @@
import { cn } from "@/shared/utils/cn";
const variants = {
primary: "bg-primary text-white hover:bg-primary-hover shadow-warm",
secondary: "bg-surface border border-border text-text-main hover:bg-black/5 shadow-sm",
outline: "border border-border text-text-main hover:bg-black/5",
ghost: "text-text-muted hover:bg-black/5 hover:text-text-main",
primary: "bg-gradient-to-b from-primary to-primary-hover text-white shadow-sm",
secondary: "bg-white dark:bg-white/10 border border-black/10 dark:border-white/10 text-text-main hover:bg-black/5 dark:hover:bg-white/5",
outline: "border border-black/15 dark:border-white/15 text-text-main hover:bg-black/5",
ghost: "text-text-muted hover:bg-black/5 dark:hover:bg-white/5 hover:text-text-main",
danger: "bg-red-500 text-white hover:bg-red-600 shadow-sm",
};
const sizes = {
sm: "h-8 px-3 text-xs rounded-md",
md: "h-10 px-5 text-sm rounded-lg",
lg: "h-12 px-8 text-base rounded-xl",
sm: "h-7 px-3 text-xs rounded-md",
md: "h-9 px-4 text-sm rounded-lg",
lg: "h-11 px-6 text-sm rounded-lg",
};
export default function Button({
@@ -32,7 +32,7 @@ export default function Button({
<button
className={cn(
"inline-flex items-center justify-center gap-2 font-medium transition-all duration-200 cursor-pointer",
"active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed disabled:active:scale-100",
"active:scale-[0.99] disabled:opacity-50 disabled:cursor-not-allowed disabled:active:scale-100",
variants[variant],
sizes[size],
fullWidth && "w-full",

View File

@@ -24,9 +24,9 @@ export default function Card({
<div
className={cn(
"bg-surface",
"border border-border",
"rounded-xl shadow-soft",
hover && "hover:shadow-warm hover:border-primary/30 transition-all cursor-pointer",
"border border-black/5 dark:border-white/5",
"rounded-lg shadow-sm",
hover && "hover:shadow-md hover:border-primary/30 transition-all cursor-pointer",
paddings[padding],
className
)}
@@ -63,8 +63,8 @@ Card.Section = function CardSection({ children, className, ...props }) {
<div
className={cn(
"p-4 rounded-lg",
"bg-surface",
"border border-border",
"bg-black/[0.02] dark:bg-white/[0.02]",
"border border-black/5 dark:border-white/5",
className
)}
{...props}
@@ -79,8 +79,9 @@ Card.Row = function CardRow({ children, className, ...props }) {
return (
<div
className={cn(
"p-3 -mx-3 px-3 border-b border-border last:border-b-0 transition-colors",
"hover:bg-sidebar",
"p-3 -mx-3 px-3 transition-colors",
"border-b border-black/5 dark:border-white/5 last:border-b-0",
"hover:bg-black/[0.02] dark:hover:bg-white/[0.02]",
className
)}
{...props}
@@ -90,3 +91,31 @@ Card.Row = function CardRow({ children, className, ...props }) {
);
};
// Sub-component: List item with hover actions (macOS style)
Card.ListItem = function CardListItem({
children,
actions,
className,
...props
}) {
return (
<div
className={cn(
"group flex items-center justify-between p-3 -mx-3 px-3",
"border-b border-black/[0.03] dark:border-white/[0.03] last:border-b-0",
"hover:bg-black/[0.02] dark:hover:bg-white/[0.02]",
"transition-colors",
className
)}
{...props}
>
<div className="flex-1 min-w-0">{children}</div>
{actions && (
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
{actions}
</div>
)}
</div>
);
};

View File

@@ -55,7 +55,7 @@ export default function Header({ onMenuClick, showMenuButton = true }) {
};
return (
<header className="flex items-center justify-between px-8 py-5 border-b border-border bg-bg/80 backdrop-blur-md z-10 sticky top-0">
<header className="flex items-center justify-between px-8 py-5 border-b border-black/5 dark:border-white/5 bg-bg/80 backdrop-blur-xl z-10 sticky top-0">
{/* Mobile menu button */}
<div className="flex items-center gap-3 lg:hidden">
{showMenuButton && (

View File

@@ -38,17 +38,17 @@ export default function Input({
onChange={onChange}
disabled={disabled}
className={cn(
"w-full py-2.5 px-4 text-sm text-text-main",
"bg-surface border rounded-lg",
"w-full py-2 px-3 text-sm text-text-main",
"bg-white dark:bg-white/5 border border-black/10 dark:border-white/10 rounded-md",
"placeholder-text-muted/60",
"focus:ring-2 focus:ring-primary/20 focus:border-primary focus:outline-none",
"transition-all shadow-sm disabled:opacity-50 disabled:cursor-not-allowed",
"focus:ring-1 focus:ring-primary/30 focus:border-primary/50 focus:outline-none",
"transition-all shadow-inner disabled:opacity-50 disabled:cursor-not-allowed",
// iOS zoom fix
"text-[16px] sm:text-sm",
icon && "pl-10",
error
? "border-red-500 focus:border-red-500 focus:ring-red-500/20"
: "border-border",
: "",
inputClassName
)}
{...props}

View File

@@ -52,7 +52,7 @@ export default function Modal({
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
{/* Overlay */}
<div
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
className="absolute inset-0 bg-black/30 backdrop-blur-sm"
onClick={closeOnOverlay ? onClose : undefined}
/>
@@ -60,8 +60,8 @@ export default function Modal({
<div
className={cn(
"relative w-full bg-surface",
"border border-border",
"rounded-2xl shadow-elevated",
"border border-black/10 dark:border-white/10",
"rounded-xl shadow-2xl",
"animate-in fade-in zoom-in-95 duration-200",
sizes[size],
className
@@ -69,16 +69,23 @@ export default function Modal({
>
{/* Header */}
{(title || showCloseButton) && (
<div className="flex items-center justify-between p-6 border-b border-border">
{title && (
<h2 className="text-lg font-semibold text-text-main">
{title}
</h2>
)}
<div className="flex items-center justify-between p-6 border-b border-black/5 dark:border-white/5">
<div className="flex items-center">
<div className="flex items-center gap-2 mr-4">
<div className="w-3 h-3 rounded-full bg-[#FF5F56]" />
<div className="w-3 h-3 rounded-full bg-[#FFBD2E]" />
<div className="w-3 h-3 rounded-full bg-[#27C93F]" />
</div>
{title && (
<h2 className="text-lg font-semibold text-text-main">
{title}
</h2>
)}
</div>
{showCloseButton && (
<button
onClick={onClose}
className="p-1.5 rounded-lg text-text-muted hover:bg-black/5 transition-colors"
className="p-1.5 rounded-lg text-text-muted hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
>
<span className="material-symbols-outlined text-[20px]">close</span>
</button>
@@ -91,7 +98,7 @@ export default function Modal({
{/* Footer */}
{footer && (
<div className="flex items-center justify-end gap-3 p-6 border-t border-border">
<div className="flex items-center justify-end gap-3 p-6 border-t border-black/5 dark:border-white/5">
{footer}
</div>
)}

View File

@@ -0,0 +1,48 @@
"use client";
import { cn } from "@/shared/utils/cn";
export default function SegmentedControl({
options = [],
value,
onChange,
size = "md",
className,
}) {
const sizes = {
sm: "h-7 text-xs",
md: "h-9 text-sm",
lg: "h-11 text-base",
};
return (
<div
className={cn(
"inline-flex items-center p-1 rounded-lg",
"bg-black/5 dark:bg-white/5",
className
)}
>
{options.map((option) => (
<button
key={option.value}
onClick={() => onChange(option.value)}
className={cn(
"px-4 rounded-md font-medium transition-all",
sizes[size],
value === option.value
? "bg-white dark:bg-white/10 text-text-main shadow-sm"
: "text-text-muted hover:text-text-main"
)}
>
{option.icon && (
<span className="material-symbols-outlined text-[16px] mr-1.5">
{option.icon}
</span>
)}
{option.label}
</button>
))}
</div>
);
}

View File

@@ -30,14 +30,14 @@ export default function Select({
onChange={onChange}
disabled={disabled}
className={cn(
"w-full py-2.5 px-4 pr-10 text-sm text-text-main",
"bg-surface border rounded-lg appearance-none",
"focus:ring-2 focus:ring-primary/20 focus:border-primary focus:outline-none",
"transition-all shadow-sm disabled:opacity-50 disabled:cursor-not-allowed",
"w-full py-2 px-3 pr-10 text-sm text-text-main",
"bg-white dark:bg-white/5 border border-black/10 dark:border-white/10 rounded-md appearance-none",
"focus:ring-1 focus:ring-primary/30 focus:border-primary/50 focus:outline-none",
"transition-all disabled:opacity-50 disabled:cursor-not-allowed",
"text-[16px] sm:text-sm",
error
? "border-red-500 focus:border-red-500 focus:ring-red-500/20"
: "border-border",
: "",
selectClassName
)}
{...props}

View File

@@ -62,9 +62,16 @@ export default function Sidebar({ onClose }) {
return (
<>
<aside className="flex w-72 flex-col border-r border-border bg-sidebar transition-colors duration-300">
<aside className="flex w-72 flex-col border-r border-black/5 dark:border-white/5 bg-vibrancy backdrop-blur-xl transition-colors duration-300">
{/* Traffic lights */}
<div className="flex items-center gap-2 px-6 pt-5 pb-2">
<div className="w-3 h-3 rounded-full bg-[#FF5F56]" />
<div className="w-3 h-3 rounded-full bg-[#FFBD2E]" />
<div className="w-3 h-3 rounded-full bg-[#27C93F]" />
</div>
{/* Logo */}
<div className="p-8">
<div className="px-6 py-4">
<Link href="/dashboard" className="flex items-center gap-3">
<div className="flex items-center justify-center size-9 rounded bg-linear-to-br from-[#f97815] to-[#c2590a]">
<span className="material-symbols-outlined text-white text-[20px]">hub</span>
@@ -83,15 +90,15 @@ export default function Sidebar({ onClose }) {
href={item.href}
onClick={onClose}
className={cn(
"flex items-center gap-3 px-4 py-3 rounded-lg transition-all group",
"flex items-center gap-3 px-4 py-2 rounded-lg transition-all group",
isActive(item.href)
? "bg-surface text-primary shadow-sm border border-border"
? "bg-primary/10 text-primary"
: "text-text-muted hover:bg-surface/50 hover:text-text-main"
)}
>
<span
className={cn(
"material-symbols-outlined text-[20px]",
"material-symbols-outlined text-[18px]",
isActive(item.href) ? "fill-1" : "group-hover:text-primary transition-colors"
)}
>
@@ -103,8 +110,8 @@ export default function Sidebar({ onClose }) {
{/* Debug section (only show when ENABLE_REQUEST_LOGS=true) */}
{showDebug && (
<div className="pt-6 mt-2">
<p className="px-4 text-xs font-semibold text-text-muted/60 uppercase tracking-wider mb-3">
<div className="pt-4 mt-2">
<p className="px-4 text-xs font-semibold text-text-muted/60 uppercase tracking-wider mb-2">
Debug
</p>
{debugItems.map((item) => (
@@ -113,15 +120,15 @@ export default function Sidebar({ onClose }) {
href={item.href}
onClick={onClose}
className={cn(
"flex items-center gap-3 px-4 py-3 rounded-lg transition-all group",
"flex items-center gap-3 px-4 py-2 rounded-lg transition-all group",
isActive(item.href)
? "bg-surface text-primary shadow-sm border border-border"
? "bg-primary/10 text-primary"
: "text-text-muted hover:bg-surface/50 hover:text-text-main"
)}
>
<span
className={cn(
"material-symbols-outlined text-[20px]",
"material-symbols-outlined text-[18px]",
isActive(item.href) ? "fill-1" : "group-hover:text-primary transition-colors"
)}
>
@@ -134,8 +141,8 @@ export default function Sidebar({ onClose }) {
)}
{/* System section */}
<div className="pt-6 mt-2">
<p className="px-4 text-xs font-semibold text-text-muted/60 uppercase tracking-wider mb-3">
<div className="pt-4 mt-2">
<p className="px-4 text-xs font-semibold text-text-muted/60 uppercase tracking-wider mb-2">
System
</p>
{systemItems.map((item) => (
@@ -144,13 +151,18 @@ export default function Sidebar({ onClose }) {
href={item.href}
onClick={onClose}
className={cn(
"flex items-center gap-3 px-4 py-3 rounded-lg transition-all group",
"flex items-center gap-3 px-4 py-2 rounded-lg transition-all group",
isActive(item.href)
? "bg-surface text-primary shadow-sm border border-border"
? "bg-primary/10 text-primary"
: "text-text-muted hover:bg-surface/50 hover:text-text-main"
)}
>
<span className="material-symbols-outlined text-[20px] group-hover:text-primary transition-colors">
<span
className={cn(
"material-symbols-outlined text-[18px]",
isActive(item.href) ? "fill-1" : "group-hover:text-primary transition-colors"
)}
>
{item.icon}
</span>
<span className="text-sm font-medium">{item.label}</span>
@@ -160,11 +172,11 @@ export default function Sidebar({ onClose }) {
</nav>
{/* Footer section */}
<div className="p-4 border-t border-border">
<div className="p-3 border-t border-black/5 dark:border-white/5">
{/* Info message */}
<div className="flex items-start gap-3 p-3 rounded-xl bg-surface border border-border mb-3">
<div className="flex items-center justify-center size-8 rounded-lg bg-blue-500/10 text-blue-500 shrink-0 mt-0.5">
<span className="material-symbols-outlined text-[18px]">info</span>
<div className="flex items-start gap-2 p-2 rounded-lg bg-surface/50 mb-2">
<div className="flex items-center justify-center size-6 rounded-md bg-blue-500/10 text-blue-500 shrink-0 mt-0.5">
<span className="material-symbols-outlined text-[14px]">info</span>
</div>
<div className="flex flex-col">
<span className="text-xs font-medium text-text-main leading-relaxed">

View File

@@ -52,11 +52,10 @@ export default function Toggle({
className={cn(
"relative inline-flex shrink-0 cursor-pointer rounded-full",
"transition-colors duration-200 ease-in-out",
"focus:outline-none focus:ring-2 focus:ring-primary/20 focus:ring-offset-2",
"dark:focus:ring-offset-surface-dark",
"focus:outline-none focus:ring-1 focus:ring-primary/30",
checked
? "bg-primary"
: "bg-border",
: "bg-black/10 dark:bg-white/20",
sizes[size].track,
disabled && "cursor-not-allowed"
)}

View File

@@ -21,6 +21,7 @@ export { default as RequestLogger } from "./RequestLogger";
export { default as KiroAuthModal } from "./KiroAuthModal";
export { default as KiroOAuthWrapper } from "./KiroOAuthWrapper";
export { default as KiroSocialOAuthModal } from "./KiroSocialOAuthModal";
export { default as SegmentedControl } from "./SegmentedControl";
// Layouts
export * from "./layouts";

View File

@@ -12,7 +12,7 @@ export default function DashboardLayout({ children }) {
{/* Mobile sidebar overlay */}
{sidebarOpen && (
<div
className="fixed inset-0 z-40 bg-black/50 lg:hidden"
className="fixed inset-0 z-40 bg-black/20 lg:hidden"
onClick={() => setSidebarOpen(false)}
/>
)}

View File

@@ -16,8 +16,8 @@ export const COLORS = {
bg: "#FBF9F6",
bgAlt: "#F5F1ED",
surface: "#FFFFFF",
sidebar: "#F0EFEC",
border: "#E6E4DD",
sidebar: "rgba(246, 246, 246, 0.8)",
border: "rgba(0, 0, 0, 0.1)",
textMain: "#383733",
textMuted: "#75736E",
},
@@ -27,8 +27,8 @@ export const COLORS = {
bg: "#191918",
bgAlt: "#1F1F1E",
surface: "#242423",
sidebar: "#1F1F1E",
border: "#333331",
sidebar: "rgba(30, 30, 30, 0.8)",
border: "rgba(255, 255, 255, 0.1)",
textMain: "#ECEBE8",
textMuted: "#9E9D99",
},