| author | |
| committer | |
| log | c51f252ff2007f7dbb0abd9a6b0af37476bbca9d |
| tree | 688952c9109aabe023f738f2b015f022c64b74ef |
| parent | 70a9a043eecc0da4d11f30416add232b4bf6e0f1 |
| signature | Signed by PGP key B5690EEEBB952194 |
* init components
* use them
* Fix select trigger styling in dark mode and add open state highlight
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>52 files changed, 4872 insertions(+), 918 deletions(-)
apps/desktop/scripts/smoke-test.mjs+2-2| ... | @@ -13,7 +13,7 @@ | ... | @@ -13,7 +13,7 @@ |
| 13 | * the first render). For production mode (no React DevTools message), we | 13 | * the first render). For production mode (no React DevTools message), we |
| 14 | * instead check that no fatal errors appeared. | 14 | * instead check that no fatal errors appeared. |
| 15 | */ | 15 | */ |
| 16 | import { spawn, execSync } from "node:child_process"; | 16 | import { execSync, spawn } from "node:child_process"; |
| 17 | import { dirname, resolve } from "node:path"; | 17 | import { dirname, resolve } from "node:path"; |
| 18 | import { fileURLToPath } from "node:url"; | 18 | import { fileURLToPath } from "node:url"; |
| 19 | 19 | ||
| ... | @@ -75,7 +75,7 @@ child.on("exit", () => { | ... | @@ -75,7 +75,7 @@ child.on("exit", () => { |
| 75 | for (const f of failures) { | 75 | for (const f of failures) { |
| 76 | console.error(` • ${f}`); | 76 | console.error(` • ${f}`); |
| 77 | } | 77 | } |
| 78 | console.error("\nFull output:\n" + output); | 78 | console.error(`\nFull output:\n${output}`); |
| 79 | process.exit(1); | 79 | process.exit(1); |
| 80 | } | 80 | } |
| 81 | 81 |
apps/renderer/components.json created+25| ... | @@ -0,0 +1,25 @@ | ||
| 1 | { | ||
| 2 | "$schema": "https://ui.shadcn.com/schema.json", | ||
| 3 | "style": "new-york", | ||
| 4 | "rsc": false, | ||
| 5 | "tsx": true, | ||
| 6 | "tailwind": { | ||
| 7 | "config": "", | ||
| 8 | "css": "src/index.css", | ||
| 9 | "baseColor": "zinc", | ||
| 10 | "cssVariables": true, | ||
| 11 | "prefix": "" | ||
| 12 | }, | ||
| 13 | "iconLibrary": "lucide", | ||
| 14 | "rtl": false, | ||
| 15 | "aliases": { | ||
| 16 | "components": "#/components", | ||
| 17 | "utils": "#/lib/utils", | ||
| 18 | "ui": "#/components/ui", | ||
| 19 | "lib": "#/lib", | ||
| 20 | "hooks": "#/hooks" | ||
| 21 | }, | ||
| 22 | "registries": { | ||
| 23 | "@coss": "https://coss.com/ui/r/{name}.json" | ||
| 24 | } | ||
| 25 | } | ||
apps/renderer/index.html+1-1| ... | @@ -5,7 +5,7 @@ | ... | @@ -5,7 +5,7 @@ |
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| 6 | <title>Electron Todo</title> | 6 | <title>Electron Todo</title> |
| 7 | </head> | 7 | </head> |
| 8 | <body class="bg-slate-100"> | 8 | <body> |
| 9 | <div id="root"></div> | 9 | <div id="root"></div> |
| 10 | <script type="module" src="/src/main.tsx"></script> | 10 | <script type="module" src="/src/main.tsx"></script> |
| 11 | </body> | 11 | </body> |
apps/renderer/package.json+5| ... | @@ -12,12 +12,17 @@ | ... | @@ -12,12 +12,17 @@ |
| 12 | }, | 12 | }, |
| 13 | "dependencies": { | 13 | "dependencies": { |
| 14 | "@acme/contracts": "workspace:*", | 14 | "@acme/contracts": "workspace:*", |
| 15 | "@base-ui/react": "^1.1.0", | ||
| 16 | "class-variance-authority": "^0.7.1", | ||
| 17 | "clsx": "^2.1.1", | ||
| 15 | "highlight.js": "^11.11.1", | 18 | "highlight.js": "^11.11.1", |
| 19 | "lucide-react": "^0.563.0", | ||
| 16 | "react": "^19.0.0", | 20 | "react": "^19.0.0", |
| 17 | "react-dom": "^19.0.0", | 21 | "react-dom": "^19.0.0", |
| 18 | "react-markdown": "^10.1.0", | 22 | "react-markdown": "^10.1.0", |
| 19 | "rehype-highlight": "^7.0.2", | 23 | "rehype-highlight": "^7.0.2", |
| 20 | "remark-gfm": "^4.0.1", | 24 | "remark-gfm": "^4.0.1", |
| 25 | "tailwind-merge": "^3.4.0", | ||
| 21 | "zod": "^3.24.1" | 26 | "zod": "^3.24.1" |
| 22 | }, | 27 | }, |
| 23 | "devDependencies": { | 28 | "devDependencies": { |
apps/renderer/src/App.tsx+3-3| ... | @@ -31,10 +31,10 @@ function Layout() { | ... | @@ -31,10 +31,10 @@ function Layout() { |
| 31 | 31 | ||
| 32 | if (!api) { | 32 | if (!api) { |
| 33 | return ( | 33 | return ( |
| 34 | <div className="flex h-screen flex-col bg-[#0c0c0c] text-[#e0e0e0]"> | 34 | <div className="flex h-screen flex-col bg-background text-foreground"> |
| 35 | <div className="drag-region h-[52px] shrink-0" /> | 35 | <div className="drag-region h-[52px] shrink-0" /> |
| 36 | <div className="flex flex-1 items-center justify-center"> | 36 | <div className="flex flex-1 items-center justify-center"> |
| 37 | <p className="text-sm text-[#a0a0a0]/60"> | 37 | <p className="text-sm text-muted-foreground"> |
| 38 | Native bridge unavailable. Launch through Electron. | 38 | Native bridge unavailable. Launch through Electron. |
| 39 | </p> | 39 | </p> |
| 40 | </div> | 40 | </div> |
| ... | @@ -43,7 +43,7 @@ function Layout() { | ... | @@ -43,7 +43,7 @@ function Layout() { |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | return ( | 45 | return ( |
| 46 | <div className="flex h-screen overflow-hidden bg-[#0c0c0c] text-[#e0e0e0]"> | 46 | <div className="flex h-screen overflow-hidden bg-background text-foreground"> |
| 47 | <EventRouter /> | 47 | <EventRouter /> |
| 48 | <Sidebar /> | 48 | <Sidebar /> |
| 49 | <ChatView /> | 49 | <ChatView /> |
apps/renderer/src/components/ChatMarkdown.tsx+1-1| ... | @@ -15,7 +15,7 @@ const markdownComponents: Components = { | ... | @@ -15,7 +15,7 @@ const markdownComponents: Components = { |
| 15 | 15 | ||
| 16 | export default function ChatMarkdown({ text }: ChatMarkdownProps) { | 16 | export default function ChatMarkdown({ text }: ChatMarkdownProps) { |
| 17 | return ( | 17 | return ( |
| 18 | <div className="chat-markdown text-sm leading-relaxed text-[#d0d0d0]"> | 18 | <div className="chat-markdown text-sm leading-relaxed text-foreground/80"> |
| 19 | <ReactMarkdown | 19 | <ReactMarkdown |
| 20 | remarkPlugins={[remarkGfm]} | 20 | remarkPlugins={[remarkGfm]} |
| 21 | rehypePlugins={[[rehypeHighlight, { detect: true, ignoreMissing: true }]]} | 21 | rehypePlugins={[[rehypeHighlight, { detect: true, ignoreMissing: true }]]} |
apps/renderer/src/components/ChatView.tsx+255-754| ... | @@ -1,7 +1,6 @@ | ... | @@ -1,7 +1,6 @@ |
| 1 | import type { ProviderApprovalDecision, ProviderEvent } from "@acme/contracts"; | 1 | import { ArrowUpIcon, LoaderCircleIcon, SquareIcon } from "lucide-react"; |
| 2 | import { | 2 | import { |
| 3 | type FormEvent, | 3 | type FormEvent, |
| 4 | Fragment, | ||
| 5 | type KeyboardEvent, | 4 | type KeyboardEvent, |
| 6 | useEffect, | 5 | useEffect, |
| 7 | useMemo, | 6 | useMemo, |
| ... | @@ -9,7 +8,6 @@ import { | ... | @@ -9,7 +8,6 @@ import { |
| 9 | useState, | 8 | useState, |
| 10 | } from "react"; | 9 | } from "react"; |
| 11 | 10 | ||
| 12 | import { EDITORS, type EditorId } from "@acme/contracts"; | ||
| 13 | import { | 11 | import { |
| 14 | DEFAULT_MODEL, | 12 | DEFAULT_MODEL, |
| 15 | DEFAULT_REASONING, | 13 | DEFAULT_REASONING, |
| ... | @@ -21,100 +19,44 @@ import { | ... | @@ -21,100 +19,44 @@ import { |
| 21 | derivePhase, | 19 | derivePhase, |
| 22 | deriveTimelineEntries, | 20 | deriveTimelineEntries, |
| 23 | deriveWorkLogEntries, | 21 | deriveWorkLogEntries, |
| 24 | formatDuration, | ||
| 25 | formatElapsed, | 22 | formatElapsed, |
| 26 | formatTimestamp, | 23 | formatTimestamp, |
| 27 | readNativeApi, | 24 | readNativeApi, |
| 28 | } from "../session-logic"; | 25 | } from "../session-logic"; |
| 29 | import { useStore } from "../store"; | 26 | import { useStore } from "../store"; |
| 30 | import ChatMarkdown from "./ChatMarkdown"; | 27 | import ChatMarkdown from "./ChatMarkdown"; |
| 28 | import { Button } from "./ui/button"; | ||
| 29 | import { | ||
| 30 | InputGroup, | ||
| 31 | InputGroupAddon, | ||
| 32 | InputGroupText, | ||
| 33 | InputGroupTextarea, | ||
| 34 | } from "./ui/input-group"; | ||
| 35 | import { | ||
| 36 | Select, | ||
| 37 | SelectItem, | ||
| 38 | SelectPopup, | ||
| 39 | SelectTrigger, | ||
| 40 | SelectValue, | ||
| 41 | } from "./ui/select"; | ||
| 31 | 42 | ||
| 32 | function formatMessageMeta(createdAt: string, duration: string | null): string { | 43 | function formatMessageMeta(createdAt: string, duration: string | null): string { |
| 33 | if (!duration) return formatTimestamp(createdAt); | 44 | if (!duration) return formatTimestamp(createdAt); |
| 34 | return `${formatTimestamp(createdAt)} • ${duration}`; | 45 | return `${formatTimestamp(createdAt)} • ${duration}`; |
| 35 | } | 46 | } |
| 36 | 47 | ||
| 37 | const FILE_MANAGER_LABEL = navigator.platform.includes("Mac") | 48 | function statusLabel(phase: string): string { |
| 38 | ? "Finder" | 49 | if (phase === "running") return "Thinking / working"; |
| 39 | : navigator.platform.includes("Win") | 50 | if (phase === "connecting") return "Connecting"; |
| 40 | ? "Explorer" | 51 | if (phase === "ready") return "Ready"; |
| 41 | : "Files"; | 52 | return "Disconnected"; |
| 42 | |||
| 43 | function editorLabel(editor: (typeof EDITORS)[number]): string { | ||
| 44 | return editor.command ? editor.label : FILE_MANAGER_LABEL; | ||
| 45 | } | 53 | } |
| 46 | 54 | ||
| 47 | const LAST_EDITOR_KEY = "codething:last-editor"; | ||
| 48 | |||
| 49 | function workToneClass(tone: "thinking" | "tool" | "info" | "error"): string { | 55 | function workToneClass(tone: "thinking" | "tool" | "info" | "error"): string { |
| 50 | if (tone === "error") return "text-rose-300/50"; | 56 | if (tone === "error") return "text-destructive/50"; |
| 51 | if (tone === "tool") return "text-[#8a8a8a]"; | 57 | if (tone === "tool") return "text-muted-foreground"; |
| 52 | if (tone === "thinking") return "text-[#707070]"; | 58 | if (tone === "thinking") return "text-muted-foreground/70"; |
| 53 | return "text-[#606060]"; | 59 | return "text-muted-foreground/60"; |
| 54 | } | ||
| 55 | |||
| 56 | interface PendingApprovalCard { | ||
| 57 | requestId: string; | ||
| 58 | requestKind: "command" | "file-change"; | ||
| 59 | createdAt: string; | ||
| 60 | detail?: string; | ||
| 61 | } | ||
| 62 | |||
| 63 | function asRecord(value: unknown): Record<string, unknown> | undefined { | ||
| 64 | if (!value || typeof value !== "object") return undefined; | ||
| 65 | return value as Record<string, unknown>; | ||
| 66 | } | ||
| 67 | |||
| 68 | function asString(value: unknown): string | undefined { | ||
| 69 | return typeof value === "string" ? value : undefined; | ||
| 70 | } | ||
| 71 | |||
| 72 | function approvalDetail(event: ProviderEvent): string | undefined { | ||
| 73 | const payload = asRecord(event.payload); | ||
| 74 | const command = asString(payload?.command); | ||
| 75 | if (command) return command; | ||
| 76 | return asString(payload?.reason); | ||
| 77 | } | ||
| 78 | |||
| 79 | function derivePendingApprovals( | ||
| 80 | events: ProviderEvent[], | ||
| 81 | ): PendingApprovalCard[] { | ||
| 82 | const pending = new Map<string, PendingApprovalCard>(); | ||
| 83 | const ordered = [...events].reverse(); | ||
| 84 | |||
| 85 | for (const event of ordered) { | ||
| 86 | if ( | ||
| 87 | event.method === "session/closed" || | ||
| 88 | event.method === "session/exited" | ||
| 89 | ) { | ||
| 90 | pending.clear(); | ||
| 91 | continue; | ||
| 92 | } | ||
| 93 | |||
| 94 | const requestId = | ||
| 95 | event.requestId ?? asString(asRecord(event.payload)?.requestId); | ||
| 96 | if (!requestId) continue; | ||
| 97 | |||
| 98 | if ( | ||
| 99 | event.kind === "request" && | ||
| 100 | (event.requestKind === "command" || event.requestKind === "file-change") | ||
| 101 | ) { | ||
| 102 | const detail = approvalDetail(event); | ||
| 103 | pending.set(requestId, { | ||
| 104 | requestId, | ||
| 105 | requestKind: event.requestKind, | ||
| 106 | createdAt: event.createdAt, | ||
| 107 | ...(detail ? { detail } : {}), | ||
| 108 | }); | ||
| 109 | continue; | ||
| 110 | } | ||
| 111 | |||
| 112 | if (event.method === "item/requestApproval/decision") { | ||
| 113 | pending.delete(requestId); | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | return Array.from(pending.values()); | ||
| 118 | } | 60 | } |
| 119 | 61 | ||
| 120 | export default function ChatView() { | 62 | export default function ChatView() { |
| ... | @@ -123,46 +65,30 @@ export default function ChatView() { | ... | @@ -123,46 +65,30 @@ export default function ChatView() { |
| 123 | const [prompt, setPrompt] = useState(""); | 65 | const [prompt, setPrompt] = useState(""); |
| 124 | const [isSending, setIsSending] = useState(false); | 66 | const [isSending, setIsSending] = useState(false); |
| 125 | const [isConnecting, setIsConnecting] = useState(false); | 67 | const [isConnecting, setIsConnecting] = useState(false); |
| 126 | const [isModelMenuOpen, setIsModelMenuOpen] = useState(false); | ||
| 127 | const [isEditorMenuOpen, setIsEditorMenuOpen] = useState(false); | ||
| 128 | const [lastEditor, setLastEditor] = useState<EditorId>(() => { | ||
| 129 | const stored = localStorage.getItem(LAST_EDITOR_KEY); | ||
| 130 | return EDITORS.some((e) => e.id === stored) | ||
| 131 | ? (stored as EditorId) | ||
| 132 | : EDITORS[0].id; | ||
| 133 | }); | ||
| 134 | const [selectedEffort, setSelectedEffort] = | 68 | const [selectedEffort, setSelectedEffort] = |
| 135 | useState<string>(DEFAULT_REASONING); | 69 | useState<string>(DEFAULT_REASONING); |
| 136 | const [isSwitchingRuntimeMode, setIsSwitchingRuntimeMode] = useState(false); | ||
| 137 | const [respondingRequestIds, setRespondingRequestIds] = useState<string[]>( | ||
| 138 | [], | ||
| 139 | ); | ||
| 140 | const [nowTick, setNowTick] = useState(() => Date.now()); | 70 | const [nowTick, setNowTick] = useState(() => Date.now()); |
| 141 | const messagesEndRef = useRef<HTMLDivElement>(null); | 71 | const messagesEndRef = useRef<HTMLDivElement>(null); |
| 142 | const textareaRef = useRef<HTMLTextAreaElement>(null); | ||
| 143 | const modelMenuRef = useRef<HTMLDivElement>(null); | ||
| 144 | const editorMenuRef = useRef<HTMLDivElement>(null); | ||
| 145 | 72 | ||
| 146 | const activeThread = state.threads.find((t) => t.id === state.activeThreadId); | 73 | const activeThread = state.threads.find((t) => t.id === state.activeThreadId); |
| 147 | const activeProject = state.projects.find((p) => p.id === activeThread?.projectId); | 74 | const activeProject = state.projects.find( |
| 75 | (p) => p.id === activeThread?.projectId, | ||
| 76 | ); | ||
| 148 | const selectedModel = resolveModelSlug( | 77 | const selectedModel = resolveModelSlug( |
| 149 | activeThread?.model ?? activeProject?.model ?? DEFAULT_MODEL, | 78 | activeThread?.model ?? activeProject?.model ?? DEFAULT_MODEL, |
| 150 | ); | 79 | ); |
| 151 | const phase = derivePhase(activeThread?.session ?? null); | 80 | const phase = derivePhase(activeThread?.session ?? null); |
| 152 | const isWorking = phase === "running" || isSending || isConnecting; | 81 | const isWorking = phase === "running" || isSending || isConnecting; |
| 82 | const activeTurnId = activeThread?.session?.activeTurnId; | ||
| 153 | const nowIso = new Date(nowTick).toISOString(); | 83 | const nowIso = new Date(nowTick).toISOString(); |
| 154 | const modelOptions = MODEL_OPTIONS; | 84 | const modelOptions = MODEL_OPTIONS; |
| 155 | const workLogEntries = useMemo( | 85 | const workLogEntries = useMemo( |
| 156 | () => deriveWorkLogEntries(activeThread?.events ?? [], undefined), | 86 | () => deriveWorkLogEntries(activeThread?.events ?? [], activeTurnId), |
| 157 | [activeThread?.events], | 87 | [activeThread?.events, activeTurnId], |
| 158 | ); | ||
| 159 | const pendingApprovals = useMemo( | ||
| 160 | () => derivePendingApprovals(activeThread?.events ?? []), | ||
| 161 | [activeThread?.events], | ||
| 162 | ); | 88 | ); |
| 163 | const assistantCompletionByItemId = useMemo(() => { | 89 | const assistantCompletionByItemId = useMemo(() => { |
| 164 | const map = new Map<string, string>(); | 90 | const map = new Map<string, string>(); |
| 165 | const ordered = [...(activeThread?.events ?? [])].toReversed(); | 91 | const ordered = [...(activeThread?.events ?? [])].reverse(); |
| 166 | for (const event of ordered) { | 92 | for (const event of ordered) { |
| 167 | if (event.method !== "item/completed") continue; | 93 | if (event.method !== "item/completed") continue; |
| 168 | if (!event.itemId) continue; | 94 | if (!event.itemId) continue; |
| ... | @@ -171,113 +97,27 @@ export default function ChatView() { | ... | @@ -171,113 +97,27 @@ export default function ChatView() { |
| 171 | return map; | 97 | return map; |
| 172 | }, [activeThread?.events]); | 98 | }, [activeThread?.events]); |
| 173 | const timelineEntries = useMemo( | 99 | const timelineEntries = useMemo( |
| 174 | () => deriveTimelineEntries(activeThread?.messages ?? [], workLogEntries), | 100 | () => |
| 175 | [activeThread?.messages, workLogEntries], | 101 | deriveTimelineEntries( |
| 102 | activeThread?.messages ?? [], | ||
| 103 | isWorking ? workLogEntries : [], | ||
| 104 | ), | ||
| 105 | [activeThread?.messages, isWorking, workLogEntries], | ||
| 176 | ); | 106 | ); |
| 177 | const completionSummary = useMemo(() => { | ||
| 178 | if (!activeThread?.latestTurnStartedAt) return null; | ||
| 179 | if (!activeThread.latestTurnCompletedAt) return null; | ||
| 180 | if (workLogEntries.length === 0) return null; | ||
| 181 | |||
| 182 | if ( | ||
| 183 | typeof activeThread.latestTurnDurationMs === "number" && | ||
| 184 | Number.isFinite(activeThread.latestTurnDurationMs) && | ||
| 185 | activeThread.latestTurnDurationMs >= 0 | ||
| 186 | ) { | ||
| 187 | return `Worked for ${formatDuration(activeThread.latestTurnDurationMs)}`; | ||
| 188 | } | ||
| 189 | |||
| 190 | const elapsed = formatElapsed( | ||
| 191 | activeThread.latestTurnStartedAt, | ||
| 192 | activeThread.latestTurnCompletedAt, | ||
| 193 | ); | ||
| 194 | return elapsed ? `Worked for ${elapsed}` : null; | ||
| 195 | }, [ | ||
| 196 | activeThread?.latestTurnStartedAt, | ||
| 197 | activeThread?.latestTurnCompletedAt, | ||
| 198 | activeThread?.latestTurnDurationMs, | ||
| 199 | workLogEntries.length, | ||
| 200 | ]); | ||
| 201 | const completionDividerBeforeEntryId = useMemo(() => { | ||
| 202 | if (!activeThread?.latestTurnStartedAt) return null; | ||
| 203 | if (!activeThread.latestTurnCompletedAt) return null; | ||
| 204 | if (workLogEntries.length === 0) return null; | ||
| 205 | |||
| 206 | const turnStartedAt = Date.parse(activeThread.latestTurnStartedAt); | ||
| 207 | if (Number.isNaN(turnStartedAt)) return null; | ||
| 208 | |||
| 209 | const entry = timelineEntries.find((timelineEntry) => { | ||
| 210 | if (timelineEntry.kind !== "message") return false; | ||
| 211 | if (timelineEntry.message.role !== "assistant") return false; | ||
| 212 | const messageAt = Date.parse(timelineEntry.message.createdAt); | ||
| 213 | return !Number.isNaN(messageAt) && messageAt >= turnStartedAt; | ||
| 214 | }); | ||
| 215 | return entry?.id ?? null; | ||
| 216 | }, [ | ||
| 217 | activeThread?.latestTurnStartedAt, | ||
| 218 | activeThread?.latestTurnCompletedAt, | ||
| 219 | timelineEntries, | ||
| 220 | workLogEntries.length, | ||
| 221 | ]); | ||
| 222 | const runtimeSessionConfig = | ||
| 223 | state.runtimeMode === "full-access" | ||
| 224 | ? ({ | ||
| 225 | approvalPolicy: "never", | ||
| 226 | sandboxMode: "danger-full-access", | ||
| 227 | } as const) | ||
| 228 | : ({ | ||
| 229 | approvalPolicy: "on-request", | ||
| 230 | sandboxMode: "workspace-write", | ||
| 231 | } as const); | ||
| 232 | |||
| 233 | const handleRuntimeModeChange = async ( | ||
| 234 | mode: "approval-required" | "full-access", | ||
| 235 | ) => { | ||
| 236 | if (mode === state.runtimeMode) return; | ||
| 237 | dispatch({ type: "SET_RUNTIME_MODE", mode }); | ||
| 238 | if (!api) return; | ||
| 239 | |||
| 240 | const sessionIds = state.threads | ||
| 241 | .map((t) => t.session) | ||
| 242 | .filter( | ||
| 243 | (s): s is NonNullable<typeof s> => | ||
| 244 | s !== null && s.status !== "closed", | ||
| 245 | ) | ||
| 246 | .map((s) => s.sessionId); | ||
| 247 | |||
| 248 | if (sessionIds.length === 0) return; | ||
| 249 | |||
| 250 | setIsSwitchingRuntimeMode(true); | ||
| 251 | try { | ||
| 252 | await Promise.all( | ||
| 253 | sessionIds.map((id) => | ||
| 254 | api.providers.stopSession({ sessionId: id }).catch(() => undefined), | ||
| 255 | ), | ||
| 256 | ); | ||
| 257 | } finally { | ||
| 258 | setIsSwitchingRuntimeMode(false); | ||
| 259 | } | ||
| 260 | }; | ||
| 261 | 107 | ||
| 262 | // Auto-scroll on new messages | 108 | // Auto-scroll on new messages |
| 263 | const messageCount = activeThread?.messages.length ?? 0; | 109 | const messageCount = activeThread?.messages.length ?? 0; |
| 264 | const workLogCount = workLogEntries.length; | 110 | const workLogCount = workLogEntries.length; |
| 111 | // biome-ignore lint/correctness/useExhaustiveDependencies: trigger on message count change | ||
| 265 | useEffect(() => { | 112 | useEffect(() => { |
| 266 | messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); | 113 | messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); |
| 267 | }, [messageCount]); | 114 | }, [messageCount]); |
| 115 | // biome-ignore lint/correctness/useExhaustiveDependencies: auto-scroll while active work-log events stream in | ||
| 268 | useEffect(() => { | 116 | useEffect(() => { |
| 269 | if (phase !== "running") return; | 117 | if (phase !== "running") return; |
| 270 | messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); | 118 | messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); |
| 271 | }, [phase, workLogCount]); | 119 | }, [phase, workLogCount]); |
| 272 | 120 | ||
| 273 | // Auto-resize textarea | ||
| 274 | useEffect(() => { | ||
| 275 | const ta = textareaRef.current; | ||
| 276 | if (!ta) return; | ||
| 277 | ta.style.height = "auto"; | ||
| 278 | ta.style.height = `${Math.min(ta.scrollHeight, 200)}px`; | ||
| 279 | }, [prompt]); | ||
| 280 | |||
| 281 | useEffect(() => { | 121 | useEffect(() => { |
| 282 | if (phase !== "running") return; | 122 | if (phase !== "running") return; |
| 283 | const timer = window.setInterval(() => { | 123 | const timer = window.setInterval(() => { |
| ... | @@ -288,63 +128,6 @@ export default function ChatView() { | ... | @@ -288,63 +128,6 @@ export default function ChatView() { |
| 288 | }; | 128 | }; |
| 289 | }, [phase]); | 129 | }, [phase]); |
| 290 | 130 | ||
| 291 | useEffect(() => { | ||
| 292 | if (!isModelMenuOpen) return; | ||
| 293 | |||
| 294 | const handleClickOutside = (event: MouseEvent) => { | ||
| 295 | if (!modelMenuRef.current) return; | ||
| 296 | if (event.target instanceof Node && !modelMenuRef.current.contains(event.target)) { | ||
| 297 | setIsModelMenuOpen(false); | ||
| 298 | } | ||
| 299 | }; | ||
| 300 | |||
| 301 | window.addEventListener("mousedown", handleClickOutside); | ||
| 302 | return () => { | ||
| 303 | window.removeEventListener("mousedown", handleClickOutside); | ||
| 304 | }; | ||
| 305 | }, [isModelMenuOpen]); | ||
| 306 | |||
| 307 | useEffect(() => { | ||
| 308 | if (!isEditorMenuOpen) return; | ||
| 309 | |||
| 310 | const handleClickOutside = (event: MouseEvent) => { | ||
| 311 | if (!editorMenuRef.current) return; | ||
| 312 | if ( | ||
| 313 | event.target instanceof Node && | ||
| 314 | !editorMenuRef.current.contains(event.target) | ||
| 315 | ) { | ||
| 316 | setIsEditorMenuOpen(false); | ||
| 317 | } | ||
| 318 | }; | ||
| 319 | |||
| 320 | window.addEventListener("mousedown", handleClickOutside); | ||
| 321 | return () => { | ||
| 322 | window.removeEventListener("mousedown", handleClickOutside); | ||
| 323 | }; | ||
| 324 | }, [isEditorMenuOpen]); | ||
| 325 | |||
| 326 | // Cmd+O / Ctrl+O to open in last-used editor | ||
| 327 | useEffect(() => { | ||
| 328 | const handler = (e: globalThis.KeyboardEvent) => { | ||
| 329 | if (e.key === "o" && (e.metaKey || e.ctrlKey) && !e.shiftKey) { | ||
| 330 | if (api && activeProject) { | ||
| 331 | e.preventDefault(); | ||
| 332 | void api.shell.openInEditor(activeProject.cwd, lastEditor); | ||
| 333 | } | ||
| 334 | } | ||
| 335 | }; | ||
| 336 | window.addEventListener("keydown", handler); | ||
| 337 | return () => window.removeEventListener("keydown", handler); | ||
| 338 | }, [api, activeProject, lastEditor]); | ||
| 339 | |||
| 340 | const openInEditor = (editorId: EditorId) => { | ||
| 341 | if (!api || !activeProject) return; | ||
| 342 | void api.shell.openInEditor(activeProject.cwd, editorId); | ||
| 343 | setLastEditor(editorId); | ||
| 344 | localStorage.setItem(LAST_EDITOR_KEY, editorId); | ||
| 345 | setIsEditorMenuOpen(false); | ||
| 346 | }; | ||
| 347 | |||
| 348 | const ensureSession = async (): Promise<string | null> => { | 131 | const ensureSession = async (): Promise<string | null> => { |
| 349 | if (!api || !activeThread || !activeProject) return null; | 132 | if (!api || !activeThread || !activeProject) return null; |
| 350 | if (activeThread.session && activeThread.session.status !== "closed") { | 133 | if (activeThread.session && activeThread.session.status !== "closed") { |
| ... | @@ -357,8 +140,6 @@ export default function ChatView() { | ... | @@ -357,8 +140,6 @@ export default function ChatView() { |
| 357 | provider: "codex", | 140 | provider: "codex", |
| 358 | cwd: activeProject.cwd || undefined, | 141 | cwd: activeProject.cwd || undefined, |
| 359 | model: selectedModel || undefined, | 142 | model: selectedModel || undefined, |
| 360 | approvalPolicy: runtimeSessionConfig.approvalPolicy, | ||
| 361 | sandboxMode: runtimeSessionConfig.sandboxMode, | ||
| 362 | }); | 143 | }); |
| 363 | dispatch({ | 144 | dispatch({ |
| 364 | type: "UPDATE_SESSION", | 145 | type: "UPDATE_SESSION", |
| ... | @@ -386,7 +167,8 @@ export default function ChatView() { | ... | @@ -386,7 +167,8 @@ export default function ChatView() { |
| 386 | 167 | ||
| 387 | // Auto-title from first message | 168 | // Auto-title from first message |
| 388 | if (activeThread.messages.length === 0) { | 169 | if (activeThread.messages.length === 0) { |
| 389 | const title = trimmed.length > 50 ? `${trimmed.slice(0, 50)}...` : trimmed; | 170 | const title = |
| 171 | trimmed.length > 50 ? `${trimmed.slice(0, 50)}...` : trimmed; | ||
| 390 | dispatch({ | 172 | dispatch({ |
| 391 | type: "SET_THREAD_TITLE", | 173 | type: "SET_THREAD_TITLE", |
| 392 | threadId: activeThread.id, | 174 | threadId: activeThread.id, |
| ... | @@ -437,37 +219,6 @@ export default function ChatView() { | ... | @@ -437,37 +219,6 @@ export default function ChatView() { |
| 437 | }); | 219 | }); |
| 438 | }; | 220 | }; |
| 439 | 221 | ||
| 440 | const onRespondToApproval = async ( | ||
| 441 | requestId: string, | ||
| 442 | decision: ProviderApprovalDecision, | ||
| 443 | ) => { | ||
| 444 | if (!api || !activeThread?.session) return; | ||
| 445 | |||
| 446 | setRespondingRequestIds((existing) => | ||
| 447 | existing.includes(requestId) ? existing : [...existing, requestId], | ||
| 448 | ); | ||
| 449 | try { | ||
| 450 | await api.providers.respondToRequest({ | ||
| 451 | sessionId: activeThread.session.sessionId, | ||
| 452 | requestId, | ||
| 453 | decision, | ||
| 454 | }); | ||
| 455 | } catch (err) { | ||
| 456 | dispatch({ | ||
| 457 | type: "SET_ERROR", | ||
| 458 | threadId: activeThread.id, | ||
| 459 | error: | ||
| 460 | err instanceof Error | ||
| 461 | ? err.message | ||
| 462 | : "Failed to submit approval decision.", | ||
| 463 | }); | ||
| 464 | } finally { | ||
| 465 | setRespondingRequestIds((existing) => | ||
| 466 | existing.filter((id) => id !== requestId), | ||
| 467 | ); | ||
| 468 | } | ||
| 469 | }; | ||
| 470 | |||
| 471 | const onModelSelect = (model: string) => { | 222 | const onModelSelect = (model: string) => { |
| 472 | if (!activeThread) return; | 223 | if (!activeThread) return; |
| 473 | dispatch({ | 224 | dispatch({ |
| ... | @@ -475,7 +226,6 @@ export default function ChatView() { | ... | @@ -475,7 +226,6 @@ export default function ChatView() { |
| 475 | threadId: activeThread.id, | 226 | threadId: activeThread.id, |
| 476 | model: resolveModelSlug(model), | 227 | model: resolveModelSlug(model), |
| 477 | }); | 228 | }); |
| 478 | setIsModelMenuOpen(false); | ||
| 479 | }; | 229 | }; |
| 480 | 230 | ||
| 481 | const onKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => { | 231 | const onKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => { |
| ... | @@ -488,11 +238,13 @@ export default function ChatView() { | ... | @@ -488,11 +238,13 @@ export default function ChatView() { |
| 488 | // Empty state: no active thread | 238 | // Empty state: no active thread |
| 489 | if (!activeThread) { | 239 | if (!activeThread) { |
| 490 | return ( | 240 | return ( |
| 491 | <div className="flex flex-1 flex-col bg-[#0c0c0c] text-[#a0a0a0]/40"> | 241 | <div className="flex flex-1 flex-col bg-background text-muted-foreground/60"> |
| 492 | <div className="drag-region h-[52px] shrink-0" /> | 242 | <div className="drag-region h-[52px] shrink-0" /> |
| 493 | <div className="flex flex-1 items-center justify-center"> | 243 | <div className="flex flex-1 items-center justify-center"> |
| 494 | <div className="text-center"> | 244 | <div className="text-center"> |
| 495 | <p className="text-sm">Select a thread or create a new one to get started.</p> | 245 | <p className="text-sm"> |
| 246 | Select a thread or create a new one to get started. | ||
| 247 | </p> | ||
| 496 | </div> | 248 | </div> |
| 497 | </div> | 249 | </div> |
| 498 | </div> | 250 | </div> |
| ... | @@ -500,55 +252,61 @@ export default function ChatView() { | ... | @@ -500,55 +252,61 @@ export default function ChatView() { |
| 500 | } | 252 | } |
| 501 | 253 | ||
| 502 | return ( | 254 | return ( |
| 503 | <div className="flex flex-1 flex-col bg-[#0c0c0c]"> | 255 | <div className="flex flex-1 flex-col bg-background"> |
| 504 | {/* Top bar */} | 256 | {/* Top bar */} |
| 505 | <header className="drag-region flex items-center justify-between border-b border-white/8 px-5 pt-[28px] pb-3"> | 257 | <header className="drag-region flex items-center justify-between border-b px-5 pt-[28px] pb-3"> |
| 506 | <div className="flex items-center gap-3"> | 258 | <div className="flex items-center gap-3"> |
| 507 | <h2 className="text-sm font-medium text-[#e0e0e0]"> | 259 | <h2 className="text-sm font-medium text-foreground"> |
| 508 | {activeThread.title} | 260 | {activeThread.title} |
| 509 | </h2> | 261 | </h2> |
| 262 | {activeProject && ( | ||
| 263 | <span className="rounded-full bg-accent px-2 py-0.5 text-[10px] text-muted-foreground/70"> | ||
| 264 | {activeProject.name} | ||
| 265 | </span> | ||
| 266 | )} | ||
| 510 | </div> | 267 | </div> |
| 511 | <div className="flex items-center gap-3"> | 268 | <div className="flex items-center gap-3"> |
| 512 | {/* Open in editor */} | 269 | {/* Status indicator */} |
| 513 | {activeProject && ( | 270 | <div |
| 514 | <div className="relative" ref={editorMenuRef}> | 271 | className={`inline-flex items-center gap-2 rounded-full border px-2.5 py-1 text-[10px] ${ |
| 515 | <button | 272 | phase === "running" |
| 516 | type="button" | 273 | ? "border-sky-400/35 bg-sky-500/8 text-sky-700 dark:text-sky-100" |
| 517 | className="rounded-md px-2 py-1 text-[10px] text-[#a0a0a0]/40 transition-colors duration-150 hover:text-[#a0a0a0]/60" | 274 | : phase === "connecting" |
| 518 | onClick={() => setIsEditorMenuOpen((v) => !v)} | 275 | ? "border-amber-400/35 bg-amber-500/8 text-amber-700 dark:text-amber-100" |
| 519 | > | 276 | : phase === "ready" |
| 520 | Open in&hellip; | 277 | ? "border-emerald-400/35 bg-emerald-500/8 text-emerald-700 dark:text-emerald-100" |
| 521 | </button> | 278 | : "border-border bg-secondary text-muted-foreground" |
| 522 | {isEditorMenuOpen && ( | 279 | }`} |
| 523 | <div className="absolute right-0 top-full z-50 mt-1 min-w-[120px] rounded-md border border-white/[0.08] bg-[#1b1b1d] py-1 shadow-xl"> | 280 | > |
| 524 | {EDITORS.map((editor) => ( | 281 | <span className="relative inline-flex h-2.5 w-2.5"> |
| 525 | <button | 282 | {(phase === "running" || phase === "connecting") && ( |
| 526 | key={editor.id} | 283 | <span |
| 527 | type="button" | 284 | className={`absolute inline-flex h-full w-full animate-ping rounded-full ${ |
| 528 | className="flex w-full items-center gap-2 px-3 py-1.5 text-left text-[11px] text-[#e0e0e0] hover:bg-white/[0.06]" | 285 | phase === "running" ? "bg-sky-300/70" : "bg-amber-300/70" |
| 529 | onClick={() => openInEditor(editor.id)} | 286 | }`} |
| 530 | > | 287 | /> |
| 531 | {editorLabel(editor)} | ||
| 532 | {editor.id === lastEditor && ( | ||
| 533 | <kbd className="ml-auto text-[9px] text-[#a0a0a0]/40"> | ||
| 534 | {navigator.platform.includes("Mac") | ||
| 535 | ? "\u2318O" | ||
| 536 | : "Ctrl+O"} | ||
| 537 | </kbd> | ||
| 538 | )} | ||
| 539 | </button> | ||
| 540 | ))} | ||
| 541 | </div> | ||
| 542 | )} | 288 | )} |
| 543 | </div> | 289 | <span |
| 544 | )} | 290 | className={`relative inline-flex h-2.5 w-2.5 rounded-full ${ |
| 291 | phase === "running" | ||
| 292 | ? "bg-sky-200" | ||
| 293 | : phase === "connecting" | ||
| 294 | ? "bg-amber-200" | ||
| 295 | : phase === "ready" | ||
| 296 | ? "bg-emerald-200" | ||
| 297 | : "bg-muted-foreground/40" | ||
| 298 | }`} | ||
| 299 | /> | ||
| 300 | </span> | ||
| 301 | <span>{statusLabel(phase)}</span> | ||
| 302 | </div> | ||
| 545 | {/* Diff toggle */} | 303 | {/* Diff toggle */} |
| 546 | <button | 304 | <button |
| 547 | type="button" | 305 | type="button" |
| 548 | className={`rounded-md px-2 py-1 text-[10px] transition-colors duration-150 ${ | 306 | className={`rounded-md px-2 py-1 text-[10px] transition-colors duration-150 ${ |
| 549 | state.diffOpen | 307 | state.diffOpen |
| 550 | ? "bg-white/10 text-white" | 308 | ? "bg-accent text-accent-foreground" |
| 551 | : "text-[#a0a0a0]/40 hover:text-[#a0a0a0]/60" | 309 | : "text-muted-foreground/60 hover:text-muted-foreground" |
| 552 | }`} | 310 | }`} |
| 553 | onClick={() => dispatch({ type: "TOGGLE_DIFF" })} | 311 | onClick={() => dispatch({ type: "TOGGLE_DIFF" })} |
| 554 | > | 312 | > |
| ... | @@ -559,192 +317,105 @@ export default function ChatView() { | ... | @@ -559,192 +317,105 @@ export default function ChatView() { |
| 559 | 317 | ||
| 560 | {/* Error banner */} | 318 | {/* Error banner */} |
| 561 | {activeThread.error && ( | 319 | {activeThread.error && ( |
| 562 | <div className="mx-4 mt-3 rounded-lg border border-rose-400/20 bg-rose-900/20 px-3 py-2 text-xs text-rose-200"> | 320 | <div className="mx-4 mt-3 rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-xs text-destructive-foreground"> |
| 563 | {activeThread.error} | 321 | {activeThread.error} |
| 564 | </div> | 322 | </div> |
| 565 | )} | 323 | )} |
| 566 | 324 | ||
| 567 | {pendingApprovals.length > 0 && ( | ||
| 568 | <div className="mx-4 mt-3 space-y-2"> | ||
| 569 | {pendingApprovals.map((approval) => { | ||
| 570 | const isResponding = respondingRequestIds.includes( | ||
| 571 | approval.requestId, | ||
| 572 | ); | ||
| 573 | return ( | ||
| 574 | <div | ||
| 575 | key={approval.requestId} | ||
| 576 | className="rounded-lg border border-amber-300/20 bg-amber-500/[0.07] px-3 py-2" | ||
| 577 | > | ||
| 578 | <p className="text-xs font-medium text-amber-100"> | ||
| 579 | {approval.requestKind === "command" | ||
| 580 | ? "Command approval requested" | ||
| 581 | : "File-change approval requested"} | ||
| 582 | </p> | ||
| 583 | {approval.detail && ( | ||
| 584 | <p | ||
| 585 | className="mt-1 truncate font-mono text-[11px] text-amber-100/75" | ||
| 586 | title={approval.detail} | ||
| 587 | > | ||
| 588 | {approval.detail} | ||
| 589 | </p> | ||
| 590 | )} | ||
| 591 | <div className="mt-2 flex flex-wrap gap-1.5"> | ||
| 592 | <button | ||
| 593 | type="button" | ||
| 594 | className="rounded-md border border-white/[0.15] bg-white/[0.08] px-2 py-1 text-[11px] text-[#e8e8e8] transition-colors duration-150 hover:bg-white/[0.13] disabled:cursor-not-allowed disabled:opacity-50" | ||
| 595 | disabled={isResponding} | ||
| 596 | onClick={() => | ||
| 597 | void onRespondToApproval(approval.requestId, "accept") | ||
| 598 | } | ||
| 599 | > | ||
| 600 | Approve once | ||
| 601 | </button> | ||
| 602 | <button | ||
| 603 | type="button" | ||
| 604 | className="rounded-md border border-sky-300/30 bg-sky-500/[0.15] px-2 py-1 text-[11px] text-sky-100 transition-colors duration-150 hover:bg-sky-500/[0.22] disabled:cursor-not-allowed disabled:opacity-50" | ||
| 605 | disabled={isResponding} | ||
| 606 | onClick={() => | ||
| 607 | void onRespondToApproval( | ||
| 608 | approval.requestId, | ||
| 609 | "acceptForSession", | ||
| 610 | ) | ||
| 611 | } | ||
| 612 | > | ||
| 613 | Always allow this session | ||
| 614 | </button> | ||
| 615 | <button | ||
| 616 | type="button" | ||
| 617 | className="rounded-md border border-white/[0.15] px-2 py-1 text-[11px] text-[#d8d8d8] transition-colors duration-150 hover:bg-white/[0.06] disabled:cursor-not-allowed disabled:opacity-50" | ||
| 618 | disabled={isResponding} | ||
| 619 | onClick={() => | ||
| 620 | void onRespondToApproval(approval.requestId, "decline") | ||
| 621 | } | ||
| 622 | > | ||
| 623 | Decline | ||
| 624 | </button> | ||
| 625 | <button | ||
| 626 | type="button" | ||
| 627 | className="rounded-md border border-rose-300/30 bg-rose-500/[0.12] px-2 py-1 text-[11px] text-rose-100 transition-colors duration-150 hover:bg-rose-500/[0.2] disabled:cursor-not-allowed disabled:opacity-50" | ||
| 628 | disabled={isResponding} | ||
| 629 | onClick={() => | ||
| 630 | void onRespondToApproval(approval.requestId, "cancel") | ||
| 631 | } | ||
| 632 | > | ||
| 633 | Cancel turn | ||
| 634 | </button> | ||
| 635 | </div> | ||
| 636 | </div> | ||
| 637 | ); | ||
| 638 | })} | ||
| 639 | </div> | ||
| 640 | )} | ||
| 641 | |||
| 642 | {/* Messages */} | 325 | {/* Messages */} |
| 643 | <div className="flex-1 overflow-y-auto px-5 py-4"> | 326 | <div className="flex-1 overflow-y-auto px-5 py-4"> |
| 644 | {activeThread.messages.length === 0 && !isWorking ? ( | 327 | {activeThread.messages.length === 0 && !isWorking ? ( |
| 645 | <div className="flex h-full items-center justify-center"> | 328 | <div className="flex h-full items-center justify-center"> |
| 646 | <p className="text-sm text-[#a0a0a0]/30">Send a message to start the conversation.</p> | 329 | <p className="text-sm text-muted-foreground/40"> |
| 330 | Send a message to start the conversation. | ||
| 331 | </p> | ||
| 647 | </div> | 332 | </div> |
| 648 | ) : ( | 333 | ) : ( |
| 649 | <div className="mx-auto max-w-3xl space-y-4"> | 334 | <div className="mx-auto max-w-3xl space-y-4"> |
| 650 | {timelineEntries.map((timelineEntry, index) => ( | 335 | {timelineEntries.map((timelineEntry) => |
| 651 | <Fragment key={timelineEntry.id}> | 336 | timelineEntry.kind === "work" ? ( |
| 652 | {timelineEntry.kind === "message" && | 337 | <div |
| 653 | timelineEntry.message.role === "assistant" && | 338 | key={timelineEntry.id} |
| 654 | (completionDividerBeforeEntryId === timelineEntry.id || | 339 | className="border-l-2 border-border/60 pl-4 py-1" |
| 655 | timelineEntries[index - 1]?.kind === "work") && ( | 340 | > |
| 656 | <div className="my-3 flex items-center gap-3"> | 341 | <p |
| 657 | <span className="h-px flex-1 bg-white/[0.1]" /> | 342 | className={`py-[2px] text-[12px] leading-relaxed ${workToneClass(timelineEntry.entry.tone)}`} |
| 658 | <span className="rounded-full border border-white/[0.12] bg-[#121212] px-2.5 py-1 text-[10px] uppercase tracking-[0.14em] text-[#9a9a9a]/80"> | 343 | > |
| 659 | {completionSummary | 344 | {timelineEntry.entry.detail ? ( |
| 660 | ? `Response • ${completionSummary}` | 345 | <> |
| 661 | : "Response"} | 346 | {timelineEntry.entry.label} |
| 662 | </span> | 347 | <span className="ml-1.5 font-mono text-[11px] opacity-60"> |
| 663 | <span className="h-px flex-1 bg-white/[0.1]" /> | 348 | {timelineEntry.entry.detail} |
| 349 | </span> | ||
| 350 | </> | ||
| 351 | ) : ( | ||
| 352 | timelineEntry.entry.label | ||
| 353 | )} | ||
| 354 | </p> | ||
| 355 | </div> | ||
| 356 | ) : ( | ||
| 357 | <div key={timelineEntry.id}> | ||
| 358 | {timelineEntry.message.role === "user" ? ( | ||
| 359 | <div className="flex justify-end"> | ||
| 360 | <div className="max-w-[80%] rounded-2xl rounded-br-sm border border-border bg-accent px-4 py-3"> | ||
| 361 | <pre className="whitespace-pre-wrap wrap-break-word font-mono text-sm leading-relaxed text-foreground"> | ||
| 362 | {timelineEntry.message.text} | ||
| 363 | </pre> | ||
| 364 | <p className="mt-1.5 text-right text-[10px] text-muted-foreground/40"> | ||
| 365 | {formatTimestamp(timelineEntry.message.createdAt)} | ||
| 366 | </p> | ||
| 367 | </div> | ||
| 664 | </div> | 368 | </div> |
| 665 | )} | 369 | ) : ( |
| 666 | {timelineEntry.kind === "work" ? ( | 370 | <div className="border-l-2 border-border pl-4"> |
| 667 | <div className="flex items-start gap-2 py-0.5 pl-1.5"> | 371 | <ChatMarkdown |
| 668 | <span className="mt-[7px] h-1.5 w-1.5 shrink-0 rounded-full bg-white/[0.18]" /> | 372 | text={ |
| 669 | <p | 373 | timelineEntry.message.text || |
| 670 | className={`py-[2px] text-[11px] leading-relaxed ${workToneClass(timelineEntry.entry.tone)}`} | 374 | (timelineEntry.message.streaming |
| 671 | > | 375 | ? "" |
| 672 | {timelineEntry.entry.detail ? ( | 376 | : "(empty response)") |
| 673 | <> | 377 | } |
| 674 | {timelineEntry.entry.label} | 378 | /> |
| 675 | <span | 379 | {timelineEntry.message.streaming && ( |
| 676 | className="ml-1.5 inline-block max-w-[70ch] truncate align-bottom font-mono text-[11px] opacity-60" | 380 | <div className="pt-1.5"> |
| 677 | title={timelineEntry.entry.detail} | 381 | <span className="inline-flex items-center gap-2 rounded-full border border-sky-400/25 bg-sky-500/8 px-2 py-0.5 text-[10px] text-sky-700/90 dark:text-sky-100/90"> |
| 678 | > | 382 | <span className="inline-flex gap-1"> |
| 679 | {timelineEntry.entry.detail} | 383 | <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-sky-500/80 dark:bg-sky-100/80" /> |
| 384 | <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-sky-500/80 dark:bg-sky-100/80 [animation-delay:150ms]" /> | ||
| 385 | <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-sky-500/80 dark:bg-sky-100/80 [animation-delay:300ms]" /> | ||
| 386 | </span> | ||
| 387 | <span>Thinking</span> | ||
| 680 | </span> | 388 | </span> |
| 681 | </> | 389 | </div> |
| 682 | ) : ( | ||
| 683 | timelineEntry.entry.label | ||
| 684 | )} | 390 | )} |
| 685 | </p> | 391 | <p className="mt-1.5 text-[10px] text-muted-foreground/40"> |
| 686 | </div> | 392 | {formatMessageMeta( |
| 687 | ) : timelineEntry.message.role === "user" ? ( | 393 | timelineEntry.message.createdAt, |
| 688 | <div className="flex justify-end"> | 394 | timelineEntry.message.streaming |
| 689 | <div className="max-w-[80%] rounded-2xl rounded-br-sm border border-white/[0.08] bg-white/[0.05] px-4 py-3"> | 395 | ? formatElapsed( |
| 690 | <pre className="whitespace-pre-wrap break-words font-mono text-sm leading-relaxed text-[#e0e0e0]"> | 396 | timelineEntry.message.createdAt, |
| 691 | {timelineEntry.message.text} | 397 | nowIso, |
| 692 | </pre> | 398 | ) |
| 693 | <p className="mt-1.5 text-right text-[10px] text-[#a0a0a0]/30"> | 399 | : formatElapsed( |
| 694 | {formatTimestamp(timelineEntry.message.createdAt)} | 400 | timelineEntry.message.createdAt, |
| 401 | assistantCompletionByItemId.get( | ||
| 402 | timelineEntry.message.id, | ||
| 403 | ), | ||
| 404 | ), | ||
| 405 | )} | ||
| 695 | </p> | 406 | </p> |
| 696 | </div> | 407 | </div> |
| 697 | </div> | 408 | )} |
| 698 | ) : ( | 409 | </div> |
| 699 | <div className="px-1 py-0.5"> | 410 | ), |
| 700 | <ChatMarkdown | 411 | )} |
| 701 | text={ | ||
| 702 | timelineEntry.message.text || | ||
| 703 | (timelineEntry.message.streaming | ||
| 704 | ? "" | ||
| 705 | : "(empty response)") | ||
| 706 | } | ||
| 707 | /> | ||
| 708 | {timelineEntry.message.streaming && ( | ||
| 709 | <div className="pt-1.5"> | ||
| 710 | <span className="inline-flex items-center gap-2 rounded-full border border-sky-400/25 bg-sky-500/[0.08] px-2 py-0.5 text-[10px] text-sky-100/90"> | ||
| 711 | <span className="inline-flex gap-1"> | ||
| 712 | <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-sky-100/80" /> | ||
| 713 | <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-sky-100/80 [animation-delay:150ms]" /> | ||
| 714 | <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-sky-100/80 [animation-delay:300ms]" /> | ||
| 715 | </span> | ||
| 716 | <span>Thinking</span> | ||
| 717 | </span> | ||
| 718 | </div> | ||
| 719 | )} | ||
| 720 | <p className="mt-1.5 text-[10px] text-[#a0a0a0]/30"> | ||
| 721 | {formatMessageMeta( | ||
| 722 | timelineEntry.message.createdAt, | ||
| 723 | timelineEntry.message.streaming | ||
| 724 | ? formatElapsed( | ||
| 725 | timelineEntry.message.createdAt, | ||
| 726 | nowIso, | ||
| 727 | ) | ||
| 728 | : formatElapsed( | ||
| 729 | timelineEntry.message.createdAt, | ||
| 730 | assistantCompletionByItemId.get( | ||
| 731 | timelineEntry.message.id, | ||
| 732 | ), | ||
| 733 | ), | ||
| 734 | )} | ||
| 735 | </p> | ||
| 736 | </div> | ||
| 737 | )} | ||
| 738 | </Fragment> | ||
| 739 | ))} | ||
| 740 | {isWorking && ( | 412 | {isWorking && ( |
| 741 | <div className="flex items-center gap-2 py-0.5 pl-1.5"> | 413 | <div className="border-l-2 border-border/60 pl-4 py-1"> |
| 742 | <span className="h-1.5 w-1.5 shrink-0 rounded-full bg-white/[0.18]" /> | ||
| 743 | <div className="flex items-center pt-1"> | 414 | <div className="flex items-center pt-1"> |
| 744 | <span className="inline-flex items-center gap-[3px]"> | 415 | <span className="inline-flex items-center gap-[3px]"> |
| 745 | <span className="h-1 w-1 rounded-full bg-white/20 animate-pulse" /> | 416 | <span className="h-1 w-1 rounded-full bg-foreground/20 animate-pulse" /> |
| 746 | <span className="h-1 w-1 rounded-full bg-white/20 animate-pulse [animation-delay:200ms]" /> | 417 | <span className="h-1 w-1 rounded-full bg-foreground/20 animate-pulse [animation-delay:200ms]" /> |
| 747 | <span className="h-1 w-1 rounded-full bg-white/20 animate-pulse [animation-delay:400ms]" /> | 418 | <span className="h-1 w-1 rounded-full bg-foreground/20 animate-pulse [animation-delay:400ms]" /> |
| 748 | </span> | 419 | </span> |
| 749 | </div> | 420 | </div> |
| 750 | </div> | 421 | </div> |
| ... | @@ -757,271 +428,101 @@ export default function ChatView() { | ... | @@ -757,271 +428,101 @@ export default function ChatView() { |
| 757 | {/* Input bar */} | 428 | {/* Input bar */} |
| 758 | <div className="px-5 pb-4 pt-2"> | 429 | <div className="px-5 pb-4 pt-2"> |
| 759 | <form onSubmit={onSend} className="mx-auto max-w-3xl"> | 430 | <form onSubmit={onSend} className="mx-auto max-w-3xl"> |
| 760 | <div className="group rounded-[20px] border border-white/8 bg-[#141416] transition-colors duration-200 focus-within:border-white/16"> | 431 | <InputGroup className="rounded-[20px] before:rounded-[19px] **:[textarea]:max-h-[200px]"> |
| 761 | {/* Textarea area */} | 432 | <InputGroupTextarea |
| 762 | <div className="px-4 pt-4 pb-2"> | 433 | value={prompt} |
| 763 | <textarea | 434 | onChange={(e) => setPrompt(e.target.value)} |
| 764 | ref={textareaRef} | 435 | onKeyDown={onKeyDown} |
| 765 | className="w-full resize-none bg-transparent text-[14px] leading-relaxed text-[#e0e0e0] placeholder:text-[#a0a0a0]/35 focus:outline-none" | 436 | placeholder={ |
| 766 | rows={2} | 437 | phase === "disconnected" |
| 767 | value={prompt} | 438 | ? "Ask for follow-up changes" |
| 768 | onChange={(e) => setPrompt(e.target.value)} | 439 | : "Ask anything..." |
| 769 | onKeyDown={onKeyDown} | 440 | } |
| 770 | placeholder={ | 441 | disabled={isSending || isConnecting} |
| 771 | phase === "disconnected" ? "Ask for follow-up changes" : "Ask anything..." | 442 | rows={2} |
| 772 | } | 443 | className="font-sans!" |
| 773 | disabled={isSending || isConnecting} | 444 | /> |
| 774 | /> | 445 | <InputGroupAddon align="block-end"> |
| 775 | </div> | 446 | {/* Model picker */} |
| 776 | 447 | <Select | |
| 777 | {/* Bottom toolbar */} | 448 | value={selectedModel} |
| 778 | <div className="flex items-center justify-between px-3 pb-3"> | 449 | onValueChange={(val) => val && onModelSelect(val)} |
| 779 | <div className="flex items-center gap-1"> | 450 | > |
| 780 | {/* Model picker */} | 451 | <SelectTrigger |
| 781 | <div className="relative" ref={modelMenuRef}> | 452 | size="sm" |
| 782 | <button | 453 | className="w-auto min-w-0 border-0 shadow-none bg-transparent dark:bg-transparent before:hidden data-[popup-open]:bg-accent dark:data-[popup-open]:bg-accent" |
| 783 | type="button" | 454 | > |
| 784 | className="inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/6 hover:text-[#d0d0d0]" | 455 | <SelectValue /> |
| 785 | onClick={() => setIsModelMenuOpen((open) => !open)} | 456 | </SelectTrigger> |
| 786 | > | 457 | <SelectPopup side="top" align="start"> |
| 787 | <span className="max-w-[180px] truncate">{selectedModel}</span> | 458 | {modelOptions.map((model) => ( |
| 788 | <svg | 459 | <SelectItem key={model} value={model}> |
| 789 | width="10" | 460 | {model} |
| 790 | height="10" | 461 | </SelectItem> |
| 791 | viewBox="0 0 10 10" | 462 | ))} |
| 792 | fill="none" | 463 | </SelectPopup> |
| 793 | className="opacity-50" | 464 | </Select> |
| 794 | aria-hidden="true" | ||
| 795 | > | ||
| 796 | <path | ||
| 797 | d="M2.5 4L5 6.5L7.5 4" | ||
| 798 | stroke="currentColor" | ||
| 799 | strokeWidth="1.2" | ||
| 800 | strokeLinecap="round" | ||
| 801 | strokeLinejoin="round" | ||
| 802 | /> | ||
| 803 | </svg> | ||
| 804 | </button> | ||
| 805 | {isModelMenuOpen && ( | ||
| 806 | <div className="absolute bottom-full left-0 z-20 mb-2 w-[320px] rounded-2xl border border-white/10 bg-[#1b1b1d]/95 p-2 shadow-[0_16px_40px_rgba(0,0,0,0.55)] backdrop-blur"> | ||
| 807 | <p className="px-2 py-1 text-[11px] text-[#a0a0a0]/70">Select model</p> | ||
| 808 | <div className="max-h-72 overflow-y-auto"> | ||
| 809 | {modelOptions.map((model) => { | ||
| 810 | const isSelected = model === selectedModel; | ||
| 811 | return ( | ||
| 812 | <button | ||
| 813 | key={model} | ||
| 814 | type="button" | ||
| 815 | className={`mb-0.5 flex w-full items-center justify-between gap-2 rounded-xl px-2 py-2 text-left font-mono text-sm transition-colors duration-150 ${ | ||
| 816 | isSelected | ||
| 817 | ? "bg-white/8 text-white" | ||
| 818 | : "text-[#d4d4d4] hover:bg-white/5" | ||
| 819 | }`} | ||
| 820 | onClick={() => onModelSelect(model)} | ||
| 821 | > | ||
| 822 | <span className="truncate">{model}</span> | ||
| 823 | <span | ||
| 824 | className={`pt-0.5 text-sm ${ | ||
| 825 | isSelected ? "text-white" : "text-transparent" | ||
| 826 | }`} | ||
| 827 | > | ||
| 828 | ✓ | ||
| 829 | </span> | ||
| 830 | </button> | ||
| 831 | ); | ||
| 832 | })} | ||
| 833 | </div> | ||
| 834 | </div> | ||
| 835 | )} | ||
| 836 | </div> | ||
| 837 | |||
| 838 | {/* Divider */} | ||
| 839 | <div className="mx-0.5 h-4 w-px bg-white/8" /> | ||
| 840 | 465 | ||
| 841 | {/* Reasoning effort */} | 466 | {/* Reasoning effort */} |
| 842 | <label | 467 | <Select |
| 843 | className="inline-flex cursor-pointer items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/6 hover:text-[#d0d0d0]" | 468 | value={selectedEffort} |
| 844 | htmlFor="reasoning-effort" | 469 | onValueChange={(val) => val && setSelectedEffort(val)} |
| 470 | > | ||
| 471 | <SelectTrigger | ||
| 472 | size="sm" | ||
| 473 | className="w-auto min-w-0 border-0 shadow-none bg-transparent dark:bg-transparent before:hidden data-[popup-open]:bg-accent dark:data-[popup-open]:bg-accent capitalize" | ||
| 845 | > | 474 | > |
| 846 | <span>{selectedEffort.charAt(0).toUpperCase() + selectedEffort.slice(1)}</span> | 475 | <SelectValue /> |
| 847 | <select | 476 | </SelectTrigger> |
| 848 | id="reasoning-effort" | 477 | <SelectPopup side="top" align="start"> |
| 849 | className="absolute opacity-0 w-0 h-0" | 478 | {REASONING_OPTIONS.map((effort) => ( |
| 850 | value={selectedEffort} | 479 | <SelectItem key={effort} value={effort}> |
| 851 | onChange={(event) => setSelectedEffort(event.target.value)} | 480 | {effort.charAt(0).toUpperCase() + effort.slice(1)} |
| 852 | > | 481 | {effort === DEFAULT_REASONING ? " (default)" : ""} |
| 853 | {REASONING_OPTIONS.map((effort) => ( | 482 | </SelectItem> |
| 854 | <option key={effort} value={effort} className="bg-[#1b1b1d]"> | 483 | ))} |
| 855 | {effort} | 484 | </SelectPopup> |
| 856 | {effort === DEFAULT_REASONING ? " (default)" : ""} | 485 | </Select> |
| 857 | </option> | ||
| 858 | ))} | ||
| 859 | </select> | ||
| 860 | <svg | ||
| 861 | width="10" | ||
| 862 | height="10" | ||
| 863 | viewBox="0 0 10 10" | ||
| 864 | fill="none" | ||
| 865 | className="opacity-50" | ||
| 866 | aria-hidden="true" | ||
| 867 | > | ||
| 868 | <path | ||
| 869 | d="M2.5 4L5 6.5L7.5 4" | ||
| 870 | stroke="currentColor" | ||
| 871 | strokeWidth="1.2" | ||
| 872 | strokeLinecap="round" | ||
| 873 | strokeLinejoin="round" | ||
| 874 | /> | ||
| 875 | </svg> | ||
| 876 | </label> | ||
| 877 | 486 | ||
| 878 | {/* Divider */} | 487 | {activeProject && ( |
| 879 | <div className="mx-0.5 h-4 w-px bg-white/[0.08]" /> | 488 | <InputGroupText className="ml-auto text-muted-foreground/40"> |
| 489 | {activeProject.name} | ||
| 490 | </InputGroupText> | ||
| 491 | )} | ||
| 880 | 492 | ||
| 881 | {/* Runtime mode toggle */} | 493 | {phase === "running" ? ( |
| 882 | <button | 494 | <Button |
| 883 | type="button" | 495 | variant="destructive" |
| 884 | className="inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/[0.06] hover:text-[#d0d0d0]" | 496 | size="icon-sm" |
| 885 | disabled={isSwitchingRuntimeMode} | 497 | className="rounded-full" |
| 886 | onClick={() => | 498 | onClick={() => void onInterrupt()} |
| 887 | void handleRuntimeModeChange( | 499 | aria-label="Stop generation" |
| 888 | state.runtimeMode === "full-access" | 500 | > |
| 889 | ? "approval-required" | 501 | <SquareIcon className="size-3" /> |
| 890 | : "full-access", | 502 | </Button> |
| 891 | ) | 503 | ) : ( |
| 892 | } | 504 | <Button |
| 893 | title={ | 505 | type="submit" |
| 894 | state.runtimeMode === "full-access" | 506 | size="icon-sm" |
| 895 | ? "Full access — click to require approvals" | 507 | className="rounded-full" |
| 896 | : "Approval required — click for full access" | 508 | disabled={isSending || isConnecting || !prompt.trim()} |
| 509 | aria-label={ | ||
| 510 | isConnecting | ||
| 511 | ? "Connecting" | ||
| 512 | : isSending | ||
| 513 | ? "Sending" | ||
| 514 | : "Send message" | ||
| 897 | } | 515 | } |
| 898 | > | 516 | > |
| 899 | {state.runtimeMode === "full-access" ? ( | 517 | {isConnecting || isSending ? ( |
| 900 | <svg | 518 | <LoaderCircleIcon className="animate-spin" /> |
| 901 | width="14" | ||
| 902 | height="14" | ||
| 903 | viewBox="0 0 14 14" | ||
| 904 | fill="none" | ||
| 905 | aria-hidden="true" | ||
| 906 | > | ||
| 907 | <rect | ||
| 908 | x="2" | ||
| 909 | y="5.5" | ||
| 910 | width="10" | ||
| 911 | height="7" | ||
| 912 | rx="1.5" | ||
| 913 | stroke="currentColor" | ||
| 914 | strokeWidth="1.3" | ||
| 915 | /> | ||
| 916 | <path | ||
| 917 | d="M9.5 5.5V4a2.5 2.5 0 0 0-5 0" | ||
| 918 | stroke="currentColor" | ||
| 919 | strokeWidth="1.3" | ||
| 920 | strokeLinecap="round" | ||
| 921 | /> | ||
| 922 | </svg> | ||
| 923 | ) : ( | 519 | ) : ( |
| 924 | <svg | 520 | <ArrowUpIcon /> |
| 925 | width="14" | ||
| 926 | height="14" | ||
| 927 | viewBox="0 0 14 14" | ||
| 928 | fill="none" | ||
| 929 | aria-hidden="true" | ||
| 930 | > | ||
| 931 | <rect | ||
| 932 | x="2" | ||
| 933 | y="5.5" | ||
| 934 | width="10" | ||
| 935 | height="7" | ||
| 936 | rx="1.5" | ||
| 937 | stroke="currentColor" | ||
| 938 | strokeWidth="1.3" | ||
| 939 | /> | ||
| 940 | <path | ||
| 941 | d="M4.5 5.5V4a2.5 2.5 0 0 1 5 0v1.5" | ||
| 942 | stroke="currentColor" | ||
| 943 | strokeWidth="1.3" | ||
| 944 | strokeLinecap="round" | ||
| 945 | /> | ||
| 946 | </svg> | ||
| 947 | )} | 521 | )} |
| 948 | <span> | 522 | </Button> |
| 949 | {state.runtimeMode === "full-access" | 523 | )} |
| 950 | ? "Full access" | 524 | </InputGroupAddon> |
| 951 | : "Supervised"} | 525 | </InputGroup> |
| 952 | </span> | ||
| 953 | </button> | ||
| 954 | </div> | ||
| 955 | |||
| 956 | {/* Right side: send / stop button */} | ||
| 957 | <div className="flex items-center gap-2"> | ||
| 958 | {phase === "running" ? ( | ||
| 959 | <button | ||
| 960 | type="button" | ||
| 961 | className="flex h-8 w-8 items-center justify-center rounded-full bg-rose-500/90 text-white transition-all duration-150 hover:bg-rose-500 hover:scale-105" | ||
| 962 | onClick={() => void onInterrupt()} | ||
| 963 | aria-label="Stop generation" | ||
| 964 | > | ||
| 965 | <svg | ||
| 966 | width="12" | ||
| 967 | height="12" | ||
| 968 | viewBox="0 0 12 12" | ||
| 969 | fill="currentColor" | ||
| 970 | aria-hidden="true" | ||
| 971 | > | ||
| 972 | <rect x="2" y="2" width="8" height="8" rx="1.5" /> | ||
| 973 | </svg> | ||
| 974 | </button> | ||
| 975 | ) : ( | ||
| 976 | <button | ||
| 977 | type="submit" | ||
| 978 | className="flex h-8 w-8 items-center justify-center rounded-full bg-white/90 text-[#0c0c0c] transition-all duration-150 hover:bg-white hover:scale-105 disabled:opacity-30 disabled:hover:scale-100" | ||
| 979 | disabled={isSending || isConnecting || !prompt.trim()} | ||
| 980 | aria-label={ | ||
| 981 | isConnecting ? "Connecting" : isSending ? "Sending" : "Send message" | ||
| 982 | } | ||
| 983 | > | ||
| 984 | {isConnecting || isSending ? ( | ||
| 985 | <svg | ||
| 986 | width="14" | ||
| 987 | height="14" | ||
| 988 | viewBox="0 0 14 14" | ||
| 989 | fill="none" | ||
| 990 | className="animate-spin" | ||
| 991 | aria-hidden="true" | ||
| 992 | > | ||
| 993 | <circle | ||
| 994 | cx="7" | ||
| 995 | cy="7" | ||
| 996 | r="5.5" | ||
| 997 | stroke="currentColor" | ||
| 998 | strokeWidth="1.5" | ||
| 999 | strokeLinecap="round" | ||
| 1000 | strokeDasharray="20 12" | ||
| 1001 | /> | ||
| 1002 | </svg> | ||
| 1003 | ) : ( | ||
| 1004 | <svg | ||
| 1005 | width="14" | ||
| 1006 | height="14" | ||
| 1007 | viewBox="0 0 14 14" | ||
| 1008 | fill="none" | ||
| 1009 | aria-hidden="true" | ||
| 1010 | > | ||
| 1011 | <path | ||
| 1012 | d="M7 11.5V2.5M7 2.5L3 6.5M7 2.5L11 6.5" | ||
| 1013 | stroke="currentColor" | ||
| 1014 | strokeWidth="1.8" | ||
| 1015 | strokeLinecap="round" | ||
| 1016 | strokeLinejoin="round" | ||
| 1017 | /> | ||
| 1018 | </svg> | ||
| 1019 | )} | ||
| 1020 | </button> | ||
| 1021 | )} | ||
| 1022 | </div> | ||
| 1023 | </div> | ||
| 1024 | </div> | ||
| 1025 | </form> | 526 | </form> |
| 1026 | </div> | 527 | </div> |
| 1027 | </div> | 528 | </div> |
apps/renderer/src/components/DiffPanel.tsx+18-10| ... | @@ -4,13 +4,15 @@ export default function DiffPanel() { | ... | @@ -4,13 +4,15 @@ export default function DiffPanel() { |
| 4 | const { dispatch } = useStore(); | 4 | const { dispatch } = useStore(); |
| 5 | 5 | ||
| 6 | return ( | 6 | return ( |
| 7 | <aside className="flex h-full w-[400px] shrink-0 flex-col border-l border-white/[0.08] bg-[#141414]"> | 7 | <aside className="flex h-full w-[400px] shrink-0 flex-col border-l border-border bg-card"> |
| 8 | {/* Header */} | 8 | {/* Header */} |
| 9 | <div className="drag-region flex items-center justify-between border-b border-white/[0.08] px-4 pt-[28px] pb-3"> | 9 | <div className="drag-region flex items-center justify-between border-b border-border px-4 pt-[28px] pb-3"> |
| 10 | <h3 className="text-xs font-medium text-[#e0e0e0]">Uncommitted changes</h3> | 10 | <h3 className="text-xs font-medium text-foreground"> |
| 11 | Uncommitted changes | ||
| 12 | </h3> | ||
| 11 | <button | 13 | <button |
| 12 | type="button" | 14 | type="button" |
| 13 | className="text-[#a0a0a0]/40 transition-colors duration-150 hover:text-[#a0a0a0]/70" | 15 | className="text-muted-foreground/60 transition-colors duration-150 hover:text-muted-foreground" |
| 14 | onClick={() => dispatch({ type: "TOGGLE_DIFF" })} | 16 | onClick={() => dispatch({ type: "TOGGLE_DIFF" })} |
| 15 | > | 17 | > |
| 16 | <span className="text-sm">&times;</span> | 18 | <span className="text-sm">&times;</span> |
| ... | @@ -20,30 +22,36 @@ export default function DiffPanel() { | ... | @@ -20,30 +22,36 @@ export default function DiffPanel() { |
| 20 | {/* Placeholder content */} | 22 | {/* Placeholder content */} |
| 21 | <div className="flex-1 overflow-y-auto p-4"> | 23 | <div className="flex-1 overflow-y-auto p-4"> |
| 22 | <div className="space-y-3"> | 24 | <div className="space-y-3"> |
| 23 | <div className="rounded-lg border border-white/[0.06] bg-white/[0.02] p-3"> | 25 | <div className="rounded-lg border border-border/80 bg-muted p-3"> |
| 24 | <div className="mb-2 flex items-center gap-2"> | 26 | <div className="mb-2 flex items-center gap-2"> |
| 25 | <span className="rounded bg-emerald-500/20 px-1.5 py-0.5 text-[9px] font-medium text-emerald-400"> | 27 | <span className="rounded bg-emerald-500/20 px-1.5 py-0.5 text-[9px] font-medium text-emerald-400"> |
| 26 | M | 28 | M |
| 27 | </span> | 29 | </span> |
| 28 | <span className="font-mono text-xs text-[#a0a0a0]/60">src/App.tsx</span> | 30 | <span className="font-mono text-xs text-muted-foreground/80"> |
| 31 | src/App.tsx | ||
| 32 | </span> | ||
| 29 | </div> | 33 | </div> |
| 30 | <div className="space-y-0.5 font-mono text-[11px]"> | 34 | <div className="space-y-0.5 font-mono text-[11px]"> |
| 31 | <div className="text-red-400/60">- const oldValue = "hello";</div> | 35 | <div className="text-red-400/60">- const oldValue = "hello";</div> |
| 32 | <div className="text-emerald-400/60">+ const newValue = "world";</div> | 36 | <div className="text-emerald-400/60"> |
| 37 | + const newValue = "world"; | ||
| 38 | </div> | ||
| 33 | </div> | 39 | </div> |
| 34 | </div> | 40 | </div> |
| 35 | 41 | ||
| 36 | <div className="rounded-lg border border-white/[0.06] bg-white/[0.02] p-3"> | 42 | <div className="rounded-lg border border-border/80 bg-muted p-3"> |
| 37 | <div className="flex items-center gap-2"> | 43 | <div className="flex items-center gap-2"> |
| 38 | <span className="rounded bg-blue-500/20 px-1.5 py-0.5 text-[9px] font-medium text-blue-400"> | 44 | <span className="rounded bg-blue-500/20 px-1.5 py-0.5 text-[9px] font-medium text-blue-400"> |
| 39 | A | 45 | A |
| 40 | </span> | 46 | </span> |
| 41 | <span className="font-mono text-xs text-[#a0a0a0]/60">src/utils.ts</span> | 47 | <span className="font-mono text-xs text-muted-foreground/80"> |
| 48 | src/utils.ts | ||
| 49 | </span> | ||
| 42 | </div> | 50 | </div> |
| 43 | </div> | 51 | </div> |
| 44 | </div> | 52 | </div> |
| 45 | 53 | ||
| 46 | <p className="mt-6 text-center text-[10px] text-[#a0a0a0]/25"> | 54 | <p className="mt-6 text-center text-[10px] text-muted-foreground/30"> |
| 47 | Diff integration coming soon | 55 | Diff integration coming soon |
| 48 | </p> | 56 | </p> |
| 49 | </div> | 57 | </div> |
apps/renderer/src/components/Sidebar.tsx+95-119| ... | @@ -1,10 +1,11 @@ | ... | @@ -1,10 +1,11 @@ |
| 1 | import { useCallback, useEffect, useMemo, useState } from "react"; | 1 | import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react"; |
| 2 | import { DEFAULT_MODEL } from "../model-logic"; | 2 | import { useMemo, useState } from "react"; |
| 3 | import { useTheme } from "../hooks/useTheme"; | ||
| 4 | import { DEFAULT_MODEL, MODEL_OPTIONS, resolveModelSlug } from "../model-logic"; | ||
| 3 | import { readNativeApi } from "../session-logic"; | 5 | import { readNativeApi } from "../session-logic"; |
| 4 | import { useStore } from "../store"; | 6 | import { useStore } from "../store"; |
| 5 | import type { Project } from "../types"; | 7 | import type { Project } from "../types"; |
| 6 | 8 | ||
| 7 | |||
| 8 | function formatRelativeTime(iso: string): string { | 9 | function formatRelativeTime(iso: string): string { |
| 9 | const diff = Date.now() - new Date(iso).getTime(); | 10 | const diff = Date.now() - new Date(iso).getTime(); |
| 10 | const minutes = Math.floor(diff / 60_000); | 11 | const minutes = Math.floor(diff / 60_000); |
| ... | @@ -15,58 +16,53 @@ function formatRelativeTime(iso: string): string { | ... | @@ -15,58 +16,53 @@ function formatRelativeTime(iso: string): string { |
| 15 | return `${Math.floor(hours / 24)}d ago`; | 16 | return `${Math.floor(hours / 24)}d ago`; |
| 16 | } | 17 | } |
| 17 | 18 | ||
| 18 | function threadStatusLabel( | 19 | const THEME_CYCLE = { system: "light", light: "dark", dark: "system" } as const; |
| 19 | status: "connecting" | "ready" | "running" | "error" | "closed" | undefined, | ||
| 20 | ): "Working" | "Connecting" | null { | ||
| 21 | if (status === "running") return "Working"; | ||
| 22 | if (status === "connecting") return "Connecting"; | ||
| 23 | return null; | ||
| 24 | } | ||
| 25 | 20 | ||
| 26 | export default function Sidebar() { | 21 | export default function Sidebar() { |
| 27 | const { state, dispatch } = useStore(); | 22 | const { state, dispatch } = useStore(); |
| 28 | const api = useMemo(() => readNativeApi(), []); | 23 | const api = useMemo(() => readNativeApi(), []); |
| 24 | const { theme, setTheme } = useTheme(); | ||
| 29 | const [addingProject, setAddingProject] = useState(false); | 25 | const [addingProject, setAddingProject] = useState(false); |
| 30 | const [newCwd, setNewCwd] = useState(""); | 26 | const [newCwd, setNewCwd] = useState(""); |
| 27 | const [newModel, setNewModel] = useState(DEFAULT_MODEL); | ||
| 31 | const [isPickingFolder, setIsPickingFolder] = useState(false); | 28 | const [isPickingFolder, setIsPickingFolder] = useState(false); |
| 32 | 29 | ||
| 33 | const handleAddProject = () => { | 30 | const handleAddProject = () => { |
| 34 | const cwd = newCwd.trim(); | 31 | const cwd = newCwd.trim(); |
| 35 | if (!cwd) return; | 32 | if (!cwd) return; |
| 36 | const name = cwd.split("/").filter(Boolean).pop() ?? "project"; | 33 | const name = cwd.split("/").filter(Boolean).pop() ?? "project"; |
| 34 | const normalizedModel = resolveModelSlug(newModel); | ||
| 37 | const project: Project = { | 35 | const project: Project = { |
| 38 | id: crypto.randomUUID(), | 36 | id: crypto.randomUUID(), |
| 39 | name, | 37 | name, |
| 40 | cwd, | 38 | cwd, |
| 41 | model: DEFAULT_MODEL, | 39 | model: normalizedModel, |
| 42 | expanded: true, | 40 | expanded: true, |
| 43 | }; | 41 | }; |
| 44 | dispatch({ type: "ADD_PROJECT", project }); | 42 | dispatch({ type: "ADD_PROJECT", project }); |
| 45 | setNewCwd(""); | 43 | setNewCwd(""); |
| 44 | setNewModel(DEFAULT_MODEL); | ||
| 46 | setAddingProject(false); | 45 | setAddingProject(false); |
| 47 | }; | 46 | }; |
| 48 | 47 | ||
| 49 | const handleNewThread = useCallback( | 48 | const handleNewThread = (projectId: string) => { |
| 50 | (projectId: string) => { | 49 | dispatch({ |
| 51 | dispatch({ | 50 | type: "ADD_THREAD", |
| 52 | type: "ADD_THREAD", | 51 | thread: { |
| 53 | thread: { | 52 | id: crypto.randomUUID(), |
| 54 | id: crypto.randomUUID(), | 53 | projectId, |
| 55 | projectId, | 54 | title: "New thread", |
| 56 | title: "New thread", | 55 | model: |
| 57 | model: | 56 | state.projects.find((p) => p.id === projectId)?.model ?? |
| 58 | state.projects.find((p) => p.id === projectId)?.model ?? | 57 | DEFAULT_MODEL, |
| 59 | DEFAULT_MODEL, | 58 | session: null, |
| 60 | session: null, | 59 | messages: [], |
| 61 | messages: [], | 60 | events: [], |
| 62 | events: [], | 61 | error: null, |
| 63 | error: null, | 62 | createdAt: new Date().toISOString(), |
| 64 | createdAt: new Date().toISOString(), | 63 | }, |
| 65 | }, | 64 | }); |
| 66 | }); | 65 | }; |
| 67 | }, | ||
| 68 | [dispatch, state.projects], | ||
| 69 | ); | ||
| 70 | 66 | ||
| 71 | const handlePickFolder = async () => { | 67 | const handlePickFolder = async () => { |
| 72 | if (!api || isPickingFolder) return; | 68 | if (!api || isPickingFolder) return; |
| ... | @@ -80,62 +76,40 @@ export default function Sidebar() { | ... | @@ -80,62 +76,40 @@ export default function Sidebar() { |
| 80 | } | 76 | } |
| 81 | }; | 77 | }; |
| 82 | 78 | ||
| 83 | useEffect(() => { | ||
| 84 | const onWindowKeyDown = (event: KeyboardEvent) => { | ||
| 85 | const isNewThreadShortcut = | ||
| 86 | event.metaKey && | ||
| 87 | event.shiftKey && | ||
| 88 | !event.ctrlKey && | ||
| 89 | !event.altKey && | ||
| 90 | event.key.toLowerCase() === "o"; | ||
| 91 | if (!isNewThreadShortcut) return; | ||
| 92 | |||
| 93 | const target = event.target; | ||
| 94 | if (target instanceof HTMLElement) { | ||
| 95 | const tagName = target.tagName; | ||
| 96 | if ( | ||
| 97 | target.isContentEditable || | ||
| 98 | tagName === "INPUT" || | ||
| 99 | tagName === "TEXTAREA" || | ||
| 100 | tagName === "SELECT" | ||
| 101 | ) { | ||
| 102 | return; | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | const activeThread = state.threads.find( | ||
| 107 | (t) => t.id === state.activeThreadId, | ||
| 108 | ); | ||
| 109 | const projectId = activeThread?.projectId ?? state.projects[0]?.id; | ||
| 110 | if (!projectId) return; | ||
| 111 | |||
| 112 | event.preventDefault(); | ||
| 113 | handleNewThread(projectId); | ||
| 114 | }; | ||
| 115 | |||
| 116 | window.addEventListener("keydown", onWindowKeyDown); | ||
| 117 | return () => { | ||
| 118 | window.removeEventListener("keydown", onWindowKeyDown); | ||
| 119 | }; | ||
| 120 | }, [handleNewThread, state.activeThreadId, state.projects, state.threads]); | ||
| 121 | |||
| 122 | return ( | 79 | return ( |
| 123 | <aside className="sidebar flex h-full w-[260px] shrink-0 flex-col border-r border-white/8 bg-[#141414]"> | 80 | <aside className="sidebar flex h-full w-[260px] shrink-0 flex-col border-r border-border bg-card"> |
| 124 | {/* Drag region / traffic light space */} | 81 | {/* Drag region / traffic light space */} |
| 125 | <div className="drag-region h-[52px] shrink-0" /> | 82 | <div className="drag-region h-[52px] shrink-0" /> |
| 126 | {/* Branding */} | 83 | {/* Branding */} |
| 127 | <div className="flex items-center gap-2 px-4 pb-4"> | 84 | <div className="flex items-center gap-2 px-4 pb-4"> |
| 128 | <div className="flex h-7 w-7 items-center justify-center rounded-lg bg-white text-xs font-bold text-[#0c0c0c]"> | 85 | <div className="flex h-7 w-7 items-center justify-center rounded-lg bg-primary text-xs font-bold text-primary-foreground"> |
| 129 | CT | 86 | CT |
| 130 | </div> | 87 | </div> |
| 131 | <span className="text-sm font-semibold tracking-tight text-[#e0e0e0]">CodeThing</span> | 88 | <span className="flex-1 text-sm font-semibold tracking-tight text-foreground"> |
| 89 | CodeThing | ||
| 90 | </span> | ||
| 91 | <button | ||
| 92 | type="button" | ||
| 93 | className="rounded-md p-1.5 text-muted-foreground/80 transition-colors duration-150 hover:bg-accent hover:text-muted-foreground" | ||
| 94 | onClick={() => setTheme(THEME_CYCLE[theme])} | ||
| 95 | aria-label={`Theme: ${theme}`} | ||
| 96 | title={`Theme: ${theme}`} | ||
| 97 | > | ||
| 98 | {theme === "system" ? ( | ||
| 99 | <MonitorIcon className="size-3.5" /> | ||
| 100 | ) : theme === "light" ? ( | ||
| 101 | <SunIcon className="size-3.5" /> | ||
| 102 | ) : ( | ||
| 103 | <MoonIcon className="size-3.5" /> | ||
| 104 | )} | ||
| 105 | </button> | ||
| 132 | </div> | 106 | </div> |
| 133 | 107 | ||
| 134 | {/* New thread (global) */} | 108 | {/* New thread (global) */} |
| 135 | <div className="px-3 pb-3"> | 109 | <div className="px-3 pb-3"> |
| 136 | <button | 110 | <button |
| 137 | type="button" | 111 | type="button" |
| 138 | className="flex w-full items-center gap-2 rounded-lg border border-white/8 bg-white/4 px-3 py-2 text-xs text-[#a0a0a0]/80 transition-colors duration-150 hover:bg-white/[0.07]" | 112 | className="flex w-full items-center gap-2 rounded-lg border border-border bg-secondary px-3 py-2 text-xs text-muted-foreground transition-colors duration-150 hover:bg-accent" |
| 139 | onClick={() => { | 113 | onClick={() => { |
| 140 | if (state.projects.length === 0) { | 114 | if (state.projects.length === 0) { |
| 141 | setAddingProject(true); | 115 | setAddingProject(true); |
| ... | @@ -145,7 +119,7 @@ export default function Sidebar() { | ... | @@ -145,7 +119,7 @@ export default function Sidebar() { |
| 145 | if (firstProject) handleNewThread(firstProject.id); | 119 | if (firstProject) handleNewThread(firstProject.id); |
| 146 | }} | 120 | }} |
| 147 | > | 121 | > |
| 148 | <span className="text-white">+</span> | 122 | <span className="text-foreground">+</span> |
| 149 | New thread | 123 | New thread |
| 150 | </button> | 124 | </button> |
| 151 | </div> | 125 | </div> |
| ... | @@ -153,46 +127,43 @@ export default function Sidebar() { | ... | @@ -153,46 +127,43 @@ export default function Sidebar() { |
| 153 | {/* Project list */} | 127 | {/* Project list */} |
| 154 | <nav className="flex-1 overflow-y-auto px-2"> | 128 | <nav className="flex-1 overflow-y-auto px-2"> |
| 155 | {state.projects.map((project) => { | 129 | {state.projects.map((project) => { |
| 156 | const threads = state.threads | 130 | const threads = state.threads.filter( |
| 157 | .filter((t) => t.projectId === project.id) | 131 | (t) => t.projectId === project.id, |
| 158 | .sort((a, b) => { | 132 | ); |
| 159 | const byDate = | ||
| 160 | new Date(b.createdAt).getTime() - | ||
| 161 | new Date(a.createdAt).getTime(); | ||
| 162 | if (byDate !== 0) return byDate; | ||
| 163 | return b.id.localeCompare(a.id); | ||
| 164 | }); | ||
| 165 | return ( | 133 | return ( |
| 166 | <div key={project.id} className="mb-1"> | 134 | <div key={project.id} className="mb-1"> |
| 167 | {/* Project header */} | 135 | {/* Project header */} |
| 168 | <button | 136 | <button |
| 169 | type="button" | 137 | type="button" |
| 170 | className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors duration-150 hover:bg-white/5" | 138 | className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors duration-150 hover:bg-accent" |
| 171 | onClick={() => dispatch({ type: "TOGGLE_PROJECT", projectId: project.id })} | 139 | onClick={() => |
| 140 | dispatch({ type: "TOGGLE_PROJECT", projectId: project.id }) | ||
| 141 | } | ||
| 172 | > | 142 | > |
| 173 | <span className="text-[10px] text-[#a0a0a0]/50"> | 143 | <span className="text-[10px] text-muted-foreground/70"> |
| 174 | {project.expanded ? "â–¼" : "â–¶"} | 144 | {project.expanded ? "â–¼" : "â–¶"} |
| 175 | </span> | 145 | </span> |
| 176 | <span className="flex-1 truncate text-xs font-medium text-[#e0e0e0]/90"> | 146 | <span className="flex-1 truncate text-xs font-medium text-foreground/90"> |
| 177 | {project.name} | 147 | {project.name} |
| 178 | </span> | 148 | </span> |
| 179 | <span className="text-[10px] text-[#a0a0a0]/40">{threads.length}</span> | 149 | <span className="text-[10px] text-muted-foreground/60"> |
| 150 | {threads.length} | ||
| 151 | </span> | ||
| 180 | </button> | 152 | </button> |
| 181 | 153 | ||
| 182 | {/* Threads */} | 154 | {/* Threads */} |
| 183 | {project.expanded && ( | 155 | {project.expanded && ( |
| 184 | <div className="ml-2 border-l border-white/6 pl-2"> | 156 | <div className="ml-2 border-l border-border/80 pl-2"> |
| 185 | {threads.map((thread) => { | 157 | {threads.map((thread) => { |
| 186 | const isActive = state.activeThreadId === thread.id; | 158 | const isActive = state.activeThreadId === thread.id; |
| 187 | const threadStatus = threadStatusLabel( | ||
| 188 | thread.session?.status, | ||
| 189 | ); | ||
| 190 | return ( | 159 | return ( |
| 191 | <button | 160 | <button |
| 192 | key={thread.id} | 161 | key={thread.id} |
| 193 | type="button" | 162 | type="button" |
| 194 | className={`flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors duration-150 ${ | 163 | className={`flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors duration-150 ${ |
| 195 | isActive ? "bg-white/10 text-white" : "text-[#a0a0a0]/70 hover:bg-white/4" | 164 | isActive |
| 165 | ? "bg-accent text-foreground" | ||
| 166 | : "text-muted-foreground hover:bg-secondary" | ||
| 196 | }`} | 167 | }`} |
| 197 | onClick={() => | 168 | onClick={() => |
| 198 | dispatch({ | 169 | dispatch({ |
| ... | @@ -201,20 +172,10 @@ export default function Sidebar() { | ... | @@ -201,20 +172,10 @@ export default function Sidebar() { |
| 201 | }) | 172 | }) |
| 202 | } | 173 | } |
| 203 | > | 174 | > |
| 204 | <div className="flex min-w-0 flex-1 items-center gap-1.5"> | 175 | <span className="flex-1 truncate text-xs"> |
| 205 | {threadStatus && ( | 176 | {thread.title} |
| 206 | <span className="inline-flex items-center gap-1 text-[10px] text-[#7ed8ff]/80"> | 177 | </span> |
| 207 | <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-sky-300/80" /> | 178 | <span className="shrink-0 text-[10px] text-muted-foreground/40"> |
| 208 | <span className="hidden xl:inline"> | ||
| 209 | {threadStatus} | ||
| 210 | </span> | ||
| 211 | </span> | ||
| 212 | )} | ||
| 213 | <span className="min-w-0 flex-1 truncate text-xs"> | ||
| 214 | {thread.title} | ||
| 215 | </span> | ||
| 216 | </div> | ||
| 217 | <span className="shrink-0 text-[10px] text-[#a0a0a0]/30"> | ||
| 218 | {formatRelativeTime(thread.createdAt)} | 179 | {formatRelativeTime(thread.createdAt)} |
| 219 | </span> | 180 | </span> |
| 220 | </button> | 181 | </button> |
| ... | @@ -224,7 +185,7 @@ export default function Sidebar() { | ... | @@ -224,7 +185,7 @@ export default function Sidebar() { |
| 224 | {/* New thread within project */} | 185 | {/* New thread within project */} |
| 225 | <button | 186 | <button |
| 226 | type="button" | 187 | type="button" |
| 227 | className="flex w-full items-center gap-1 px-2 py-1 text-[10px] text-[#a0a0a0]/40 transition-colors duration-150 hover:text-[#a0a0a0]/60" | 188 | className="flex w-full items-center gap-1 px-2 py-1 text-[10px] text-muted-foreground/60 transition-colors duration-150 hover:text-muted-foreground/80" |
| 228 | onClick={() => handleNewThread(project.id)} | 189 | onClick={() => handleNewThread(project.id)} |
| 229 | > | 190 | > |
| 230 | <span>+</span> New thread | 191 | <span>+</span> New thread |
| ... | @@ -236,7 +197,7 @@ export default function Sidebar() { | ... | @@ -236,7 +197,7 @@ export default function Sidebar() { |
| 236 | })} | 197 | })} |
| 237 | 198 | ||
| 238 | {state.projects.length === 0 && !addingProject && ( | 199 | {state.projects.length === 0 && !addingProject && ( |
| 239 | <div className="px-2 pt-4 text-center text-xs text-[#a0a0a0]/40"> | 200 | <div className="px-2 pt-4 text-center text-xs text-muted-foreground/60"> |
| 240 | No projects yet. | 201 | No projects yet. |
| 241 | <br /> | 202 | <br /> |
| 242 | Add one to get started. | 203 | Add one to get started. |
| ... | @@ -246,12 +207,12 @@ export default function Sidebar() { | ... | @@ -246,12 +207,12 @@ export default function Sidebar() { |
| 246 | 207 | ||
| 247 | {/* Add project form */} | 208 | {/* Add project form */} |
| 248 | {addingProject ? ( | 209 | {addingProject ? ( |
| 249 | <div className="border-t border-white/8 p-3"> | 210 | <div className="border-t border-border p-3"> |
| 250 | <p className="mb-2 text-[10px] font-medium uppercase tracking-wider text-[#a0a0a0]/50"> | 211 | <p className="mb-2 text-[10px] font-medium uppercase tracking-wider text-muted-foreground/70"> |
| 251 | Add project | 212 | Add project |
| 252 | </p> | 213 | </p> |
| 253 | <input | 214 | <input |
| 254 | className="mb-2 w-full rounded-md border border-white/10 bg-white/4 px-2 py-1.5 font-mono text-xs text-[#e0e0e0] placeholder:text-[#a0a0a0]/30 focus:border-white/30 focus:outline-none" | 215 | className="mb-2 w-full rounded-md border border-border bg-secondary px-2 py-1.5 font-mono text-xs text-foreground placeholder:text-muted-foreground/40 focus:border-ring focus:outline-none" |
| 255 | placeholder="/path/to/project" | 216 | placeholder="/path/to/project" |
| 256 | value={newCwd} | 217 | value={newCwd} |
| 257 | onChange={(e) => setNewCwd(e.target.value)} | 218 | onChange={(e) => setNewCwd(e.target.value)} |
| ... | @@ -263,24 +224,39 @@ export default function Sidebar() { | ... | @@ -263,24 +224,39 @@ export default function Sidebar() { |
| 263 | {api && ( | 224 | {api && ( |
| 264 | <button | 225 | <button |
| 265 | type="button" | 226 | type="button" |
| 266 | className="mb-2 flex w-full items-center justify-center rounded-md border border-white/10 px-2 py-1.5 text-xs text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/4 disabled:cursor-not-allowed disabled:opacity-60" | 227 | className="mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60" |
| 267 | onClick={() => void handlePickFolder()} | 228 | onClick={() => void handlePickFolder()} |
| 268 | disabled={isPickingFolder} | 229 | disabled={isPickingFolder} |
| 269 | > | 230 | > |
| 270 | {isPickingFolder ? "Picking folder..." : "Browse for folder"} | 231 | {isPickingFolder ? "Picking folder..." : "Browse for folder"} |
| 271 | </button> | 232 | </button> |
| 272 | )} | 233 | )} |
| 234 | <select | ||
| 235 | className="mb-2 w-full rounded-md border border-border bg-secondary px-2 py-1.5 font-mono text-xs text-foreground placeholder:text-muted-foreground/40 focus:border-ring focus:outline-none" | ||
| 236 | value={newModel} | ||
| 237 | onChange={(e) => setNewModel(e.target.value)} | ||
| 238 | onKeyDown={(e) => { | ||
| 239 | if (e.key === "Enter") handleAddProject(); | ||
| 240 | if (e.key === "Escape") setAddingProject(false); | ||
| 241 | }} | ||
| 242 | > | ||
| 243 | {MODEL_OPTIONS.map((model) => ( | ||
| 244 | <option key={model} value={model} className="bg-card"> | ||
| 245 | {model} | ||
| 246 | </option> | ||
| 247 | ))} | ||
| 248 | </select> | ||
| 273 | <div className="flex gap-2"> | 249 | <div className="flex gap-2"> |
| 274 | <button | 250 | <button |
| 275 | type="button" | 251 | type="button" |
| 276 | className="flex-1 rounded-md bg-white px-2 py-1 text-xs font-medium text-[#0c0c0c] transition-colors duration-150 hover:bg-white/90" | 252 | className="flex-1 rounded-md bg-primary px-2 py-1 text-xs font-medium text-primary-foreground transition-colors duration-150 hover:bg-primary/90" |
| 277 | onClick={handleAddProject} | 253 | onClick={handleAddProject} |
| 278 | > | 254 | > |
| 279 | Add | 255 | Add |
| 280 | </button> | 256 | </button> |
| 281 | <button | 257 | <button |
| 282 | type="button" | 258 | type="button" |
| 283 | className="flex-1 rounded-md border border-white/10 px-2 py-1 text-xs text-[#a0a0a0]/60 transition-colors duration-150 hover:bg-white/4" | 259 | className="flex-1 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground/80 transition-colors duration-150 hover:bg-secondary" |
| 284 | onClick={() => setAddingProject(false)} | 260 | onClick={() => setAddingProject(false)} |
| 285 | > | 261 | > |
| 286 | Cancel | 262 | Cancel |
| ... | @@ -288,10 +264,10 @@ export default function Sidebar() { | ... | @@ -288,10 +264,10 @@ export default function Sidebar() { |
| 288 | </div> | 264 | </div> |
| 289 | </div> | 265 | </div> |
| 290 | ) : ( | 266 | ) : ( |
| 291 | <div className="border-t border-white/8 p-3"> | 267 | <div className="border-t border-border p-3"> |
| 292 | <button | 268 | <button |
| 293 | type="button" | 269 | type="button" |
| 294 | className="flex w-full items-center justify-center gap-1 rounded-md border border-dashed border-white/12 py-2 text-xs text-[#a0a0a0]/50 transition-colors duration-150 hover:border-white/20 hover:text-[#a0a0a0]/70" | 270 | className="flex w-full items-center justify-center gap-1 rounded-md border border-dashed border-border py-2 text-xs text-muted-foreground/70 transition-colors duration-150 hover:border-ring hover:text-muted-foreground" |
| 295 | onClick={() => setAddingProject(true)} | 271 | onClick={() => setAddingProject(true)} |
| 296 | > | 272 | > |
| 297 | + Add project | 273 | + Add project |
apps/renderer/src/components/ui/accordion.tsx created+67| ... | @@ -0,0 +1,67 @@ | ||
| 1 | import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion"; | ||
| 2 | import { ChevronDownIcon } from "lucide-react"; | ||
| 3 | |||
| 4 | import { cn } from "#/lib/utils"; | ||
| 5 | |||
| 6 | function Accordion(props: AccordionPrimitive.Root.Props) { | ||
| 7 | return <AccordionPrimitive.Root data-slot="accordion" {...props} />; | ||
| 8 | } | ||
| 9 | |||
| 10 | function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) { | ||
| 11 | return ( | ||
| 12 | <AccordionPrimitive.Item | ||
| 13 | className={cn("border-b last:border-b-0", className)} | ||
| 14 | data-slot="accordion-item" | ||
| 15 | {...props} | ||
| 16 | /> | ||
| 17 | ); | ||
| 18 | } | ||
| 19 | |||
| 20 | function AccordionTrigger({ | ||
| 21 | className, | ||
| 22 | children, | ||
| 23 | ...props | ||
| 24 | }: AccordionPrimitive.Trigger.Props) { | ||
| 25 | return ( | ||
| 26 | <AccordionPrimitive.Header className="flex"> | ||
| 27 | <AccordionPrimitive.Trigger | ||
| 28 | className={cn( | ||
| 29 | "flex flex-1 cursor-pointer items-start justify-between gap-4 rounded-md py-4 text-left font-medium text-sm outline-none transition-all focus-visible:ring-[3px] focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-64 data-panel-open:*:data-[slot=accordion-indicator]:rotate-180", | ||
| 30 | className, | ||
| 31 | )} | ||
| 32 | data-slot="accordion-trigger" | ||
| 33 | {...props} | ||
| 34 | > | ||
| 35 | {children} | ||
| 36 | <ChevronDownIcon | ||
| 37 | className="pointer-events-none size-4 shrink-0 translate-y-0.5 opacity-80 transition-transform duration-200 ease-in-out" | ||
| 38 | data-slot="accordion-indicator" | ||
| 39 | /> | ||
| 40 | </AccordionPrimitive.Trigger> | ||
| 41 | </AccordionPrimitive.Header> | ||
| 42 | ); | ||
| 43 | } | ||
| 44 | |||
| 45 | function AccordionPanel({ | ||
| 46 | className, | ||
| 47 | children, | ||
| 48 | ...props | ||
| 49 | }: AccordionPrimitive.Panel.Props) { | ||
| 50 | return ( | ||
| 51 | <AccordionPrimitive.Panel | ||
| 52 | className="h-(--accordion-panel-height) overflow-hidden text-muted-foreground text-sm transition-[height] duration-200 ease-in-out data-ending-style:h-0 data-starting-style:h-0" | ||
| 53 | data-slot="accordion-panel" | ||
| 54 | {...props} | ||
| 55 | > | ||
| 56 | <div className={cn("pt-0 pb-4", className)}>{children}</div> | ||
| 57 | </AccordionPrimitive.Panel> | ||
| 58 | ); | ||
| 59 | } | ||
| 60 | |||
| 61 | export { | ||
| 62 | Accordion, | ||
| 63 | AccordionItem, | ||
| 64 | AccordionTrigger, | ||
| 65 | AccordionPanel, | ||
| 66 | AccordionPanel as AccordionContent, | ||
| 67 | }; | ||
apps/renderer/src/components/ui/alert-dialog.tsx created+167| ... | @@ -0,0 +1,167 @@ | ||
| 1 | import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"; | ||
| 2 | |||
| 3 | import { cn } from "#/lib/utils"; | ||
| 4 | |||
| 5 | const AlertDialogCreateHandle = AlertDialogPrimitive.createHandle; | ||
| 6 | |||
| 7 | const AlertDialog = AlertDialogPrimitive.Root; | ||
| 8 | |||
| 9 | const AlertDialogPortal = AlertDialogPrimitive.Portal; | ||
| 10 | |||
| 11 | function AlertDialogTrigger(props: AlertDialogPrimitive.Trigger.Props) { | ||
| 12 | return ( | ||
| 13 | <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} /> | ||
| 14 | ); | ||
| 15 | } | ||
| 16 | |||
| 17 | function AlertDialogBackdrop({ | ||
| 18 | className, | ||
| 19 | ...props | ||
| 20 | }: AlertDialogPrimitive.Backdrop.Props) { | ||
| 21 | return ( | ||
| 22 | <AlertDialogPrimitive.Backdrop | ||
| 23 | className={cn( | ||
| 24 | "fixed inset-0 z-50 bg-black/32 backdrop-blur-sm transition-all duration-200 ease-out data-ending-style:opacity-0 data-starting-style:opacity-0", | ||
| 25 | className, | ||
| 26 | )} | ||
| 27 | data-slot="alert-dialog-backdrop" | ||
| 28 | {...props} | ||
| 29 | /> | ||
| 30 | ); | ||
| 31 | } | ||
| 32 | |||
| 33 | function AlertDialogViewport({ | ||
| 34 | className, | ||
| 35 | ...props | ||
| 36 | }: AlertDialogPrimitive.Viewport.Props) { | ||
| 37 | return ( | ||
| 38 | <AlertDialogPrimitive.Viewport | ||
| 39 | className={cn( | ||
| 40 | "fixed inset-0 z-50 grid grid-rows-[1fr_auto_3fr] justify-items-center p-4", | ||
| 41 | className, | ||
| 42 | )} | ||
| 43 | data-slot="alert-dialog-viewport" | ||
| 44 | {...props} | ||
| 45 | /> | ||
| 46 | ); | ||
| 47 | } | ||
| 48 | |||
| 49 | function AlertDialogPopup({ | ||
| 50 | className, | ||
| 51 | bottomStickOnMobile = true, | ||
| 52 | ...props | ||
| 53 | }: AlertDialogPrimitive.Popup.Props & { | ||
| 54 | bottomStickOnMobile?: boolean; | ||
| 55 | }) { | ||
| 56 | return ( | ||
| 57 | <AlertDialogPortal> | ||
| 58 | <AlertDialogBackdrop /> | ||
| 59 | <AlertDialogViewport | ||
| 60 | className={cn( | ||
| 61 | bottomStickOnMobile && | ||
| 62 | "max-sm:grid-rows-[1fr_auto] max-sm:p-0 max-sm:pt-12", | ||
| 63 | )} | ||
| 64 | > | ||
| 65 | <AlertDialogPrimitive.Popup | ||
| 66 | className={cn( | ||
| 67 | "-translate-y-[calc(1.25rem*var(--nested-dialogs))] relative row-start-2 flex max-h-full min-h-0 w-full min-w-0 max-w-lg scale-[calc(1-0.1*var(--nested-dialogs))] flex-col rounded-2xl border bg-popover not-dark:bg-clip-padding text-popover-foreground opacity-[calc(1-0.1*var(--nested-dialogs))] shadow-lg/5 transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] data-nested:data-ending-style:translate-y-8 data-nested:data-starting-style:translate-y-8 data-nested-dialog-open:origin-top data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", | ||
| 68 | bottomStickOnMobile && | ||
| 69 | "max-sm:max-w-none max-sm:rounded-none max-sm:border-x-0 max-sm:border-t max-sm:border-b-0 max-sm:opacity-[calc(1-min(var(--nested-dialogs),1))] max-sm:data-ending-style:translate-y-4 max-sm:data-starting-style:translate-y-4 max-sm:before:hidden max-sm:before:rounded-none", | ||
| 70 | className, | ||
| 71 | )} | ||
| 72 | data-slot="alert-dialog-popup" | ||
| 73 | {...props} | ||
| 74 | /> | ||
| 75 | </AlertDialogViewport> | ||
| 76 | </AlertDialogPortal> | ||
| 77 | ); | ||
| 78 | } | ||
| 79 | |||
| 80 | function AlertDialogHeader({ | ||
| 81 | className, | ||
| 82 | ...props | ||
| 83 | }: React.ComponentProps<"div">) { | ||
| 84 | return ( | ||
| 85 | <div | ||
| 86 | className={cn( | ||
| 87 | "flex flex-col gap-2 p-6 text-center max-sm:pb-4 sm:text-left", | ||
| 88 | className, | ||
| 89 | )} | ||
| 90 | data-slot="alert-dialog-header" | ||
| 91 | {...props} | ||
| 92 | /> | ||
| 93 | ); | ||
| 94 | } | ||
| 95 | |||
| 96 | function AlertDialogFooter({ | ||
| 97 | className, | ||
| 98 | variant = "default", | ||
| 99 | ...props | ||
| 100 | }: React.ComponentProps<"div"> & { | ||
| 101 | variant?: "default" | "bare"; | ||
| 102 | }) { | ||
| 103 | return ( | ||
| 104 | <div | ||
| 105 | className={cn( | ||
| 106 | "flex flex-col-reverse gap-2 px-6 sm:flex-row sm:justify-end sm:rounded-b-[calc(var(--radius-2xl)-1px)]", | ||
| 107 | variant === "default" && "border-t bg-muted/72 py-4", | ||
| 108 | variant === "bare" && "pb-6", | ||
| 109 | className, | ||
| 110 | )} | ||
| 111 | data-slot="alert-dialog-footer" | ||
| 112 | {...props} | ||
| 113 | /> | ||
| 114 | ); | ||
| 115 | } | ||
| 116 | |||
| 117 | function AlertDialogTitle({ | ||
| 118 | className, | ||
| 119 | ...props | ||
| 120 | }: AlertDialogPrimitive.Title.Props) { | ||
| 121 | return ( | ||
| 122 | <AlertDialogPrimitive.Title | ||
| 123 | className={cn( | ||
| 124 | "font-heading font-semibold text-xl leading-none", | ||
| 125 | className, | ||
| 126 | )} | ||
| 127 | data-slot="alert-dialog-title" | ||
| 128 | {...props} | ||
| 129 | /> | ||
| 130 | ); | ||
| 131 | } | ||
| 132 | |||
| 133 | function AlertDialogDescription({ | ||
| 134 | className, | ||
| 135 | ...props | ||
| 136 | }: AlertDialogPrimitive.Description.Props) { | ||
| 137 | return ( | ||
| 138 | <AlertDialogPrimitive.Description | ||
| 139 | className={cn("text-muted-foreground text-sm", className)} | ||
| 140 | data-slot="alert-dialog-description" | ||
| 141 | {...props} | ||
| 142 | /> | ||
| 143 | ); | ||
| 144 | } | ||
| 145 | |||
| 146 | function AlertDialogClose(props: AlertDialogPrimitive.Close.Props) { | ||
| 147 | return ( | ||
| 148 | <AlertDialogPrimitive.Close data-slot="alert-dialog-close" {...props} /> | ||
| 149 | ); | ||
| 150 | } | ||
| 151 | |||
| 152 | export { | ||
| 153 | AlertDialogCreateHandle, | ||
| 154 | AlertDialog, | ||
| 155 | AlertDialogPortal, | ||
| 156 | AlertDialogBackdrop, | ||
| 157 | AlertDialogBackdrop as AlertDialogOverlay, | ||
| 158 | AlertDialogTrigger, | ||
| 159 | AlertDialogPopup, | ||
| 160 | AlertDialogPopup as AlertDialogContent, | ||
| 161 | AlertDialogHeader, | ||
| 162 | AlertDialogFooter, | ||
| 163 | AlertDialogTitle, | ||
| 164 | AlertDialogDescription, | ||
| 165 | AlertDialogClose, | ||
| 166 | AlertDialogViewport, | ||
| 167 | }; | ||
apps/renderer/src/components/ui/alert.tsx created+80| ... | @@ -0,0 +1,80 @@ | ||
| 1 | import { type VariantProps, cva } from "class-variance-authority"; | ||
| 2 | import type * as React from "react"; | ||
| 3 | |||
| 4 | import { cn } from "#/lib/utils"; | ||
| 5 | |||
| 6 | const alertVariants = cva( | ||
| 7 | "relative grid w-full items-start gap-x-2 gap-y-0.5 rounded-xl border px-3.5 py-3 text-card-foreground text-sm has-[>svg]:has-data-[slot=alert-action]:grid-cols-[calc(var(--spacing)*4)_1fr_auto] has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-data-[slot=alert-action]:grid-cols-[1fr_auto] has-[>svg]:gap-x-2 [&>svg]:h-lh [&>svg]:w-4", | ||
| 8 | { | ||
| 9 | defaultVariants: { | ||
| 10 | variant: "default", | ||
| 11 | }, | ||
| 12 | variants: { | ||
| 13 | variant: { | ||
| 14 | default: | ||
| 15 | "bg-transparent dark:bg-input/32 [&>svg]:text-muted-foreground", | ||
| 16 | error: | ||
| 17 | "border-destructive/32 bg-destructive/4 [&>svg]:text-destructive", | ||
| 18 | info: "border-info/32 bg-info/4 [&>svg]:text-info", | ||
| 19 | success: "border-success/32 bg-success/4 [&>svg]:text-success", | ||
| 20 | warning: "border-warning/32 bg-warning/4 [&>svg]:text-warning", | ||
| 21 | }, | ||
| 22 | }, | ||
| 23 | }, | ||
| 24 | ); | ||
| 25 | |||
| 26 | function Alert({ | ||
| 27 | className, | ||
| 28 | variant, | ||
| 29 | ...props | ||
| 30 | }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) { | ||
| 31 | return ( | ||
| 32 | <div | ||
| 33 | className={cn(alertVariants({ variant }), className)} | ||
| 34 | data-slot="alert" | ||
| 35 | role="alert" | ||
| 36 | {...props} | ||
| 37 | /> | ||
| 38 | ); | ||
| 39 | } | ||
| 40 | |||
| 41 | function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { | ||
| 42 | return ( | ||
| 43 | <div | ||
| 44 | className={cn("font-medium [svg~&]:col-start-2", className)} | ||
| 45 | data-slot="alert-title" | ||
| 46 | {...props} | ||
| 47 | /> | ||
| 48 | ); | ||
| 49 | } | ||
| 50 | |||
| 51 | function AlertDescription({ | ||
| 52 | className, | ||
| 53 | ...props | ||
| 54 | }: React.ComponentProps<"div">) { | ||
| 55 | return ( | ||
| 56 | <div | ||
| 57 | className={cn( | ||
| 58 | "flex flex-col gap-2.5 text-muted-foreground [svg~&]:col-start-2", | ||
| 59 | className, | ||
| 60 | )} | ||
| 61 | data-slot="alert-description" | ||
| 62 | {...props} | ||
| 63 | /> | ||
| 64 | ); | ||
| 65 | } | ||
| 66 | |||
| 67 | function AlertAction({ className, ...props }: React.ComponentProps<"div">) { | ||
| 68 | return ( | ||
| 69 | <div | ||
| 70 | className={cn( | ||
| 71 | "flex gap-1 max-sm:col-start-2 max-sm:mt-2 sm:row-start-1 sm:row-end-3 sm:self-center sm:[[data-slot=alert-description]~&]:col-start-2 sm:[[data-slot=alert-title]~&]:col-start-2 sm:[svg~&]:col-start-2 sm:[svg~[data-slot=alert-description]~&]:col-start-3 sm:[svg~[data-slot=alert-title]~&]:col-start-3", | ||
| 72 | className, | ||
| 73 | )} | ||
| 74 | data-slot="alert-action" | ||
| 75 | {...props} | ||
| 76 | /> | ||
| 77 | ); | ||
| 78 | } | ||
| 79 | |||
| 80 | export { Alert, AlertTitle, AlertDescription, AlertAction }; | ||
apps/renderer/src/components/ui/autocomplete.tsx created+308| ... | @@ -0,0 +1,308 @@ | ||
| 1 | import { Autocomplete as AutocompletePrimitive } from "@base-ui/react/autocomplete"; | ||
| 2 | import { ChevronsUpDownIcon, XIcon } from "lucide-react"; | ||
| 3 | |||
| 4 | import { Input } from "#/components/ui/input"; | ||
| 5 | import { ScrollArea } from "#/components/ui/scroll-area"; | ||
| 6 | import { cn } from "#/lib/utils"; | ||
| 7 | |||
| 8 | const Autocomplete = AutocompletePrimitive.Root; | ||
| 9 | |||
| 10 | function AutocompleteInput({ | ||
| 11 | className, | ||
| 12 | showTrigger = false, | ||
| 13 | showClear = false, | ||
| 14 | startAddon, | ||
| 15 | size, | ||
| 16 | ...props | ||
| 17 | }: Omit<AutocompletePrimitive.Input.Props, "size"> & { | ||
| 18 | showTrigger?: boolean; | ||
| 19 | showClear?: boolean; | ||
| 20 | startAddon?: React.ReactNode; | ||
| 21 | size?: "sm" | "default" | "lg" | number; | ||
| 22 | ref?: React.Ref<HTMLInputElement>; | ||
| 23 | }) { | ||
| 24 | const sizeValue = (size ?? "default") as "sm" | "default" | "lg" | number; | ||
| 25 | |||
| 26 | return ( | ||
| 27 | <div className="relative not-has-[>*.w-full]:w-fit w-full text-foreground has-disabled:opacity-64"> | ||
| 28 | {startAddon && ( | ||
| 29 | <div | ||
| 30 | aria-hidden="true" | ||
| 31 | className="[&_svg]:-mx-0.5 pointer-events-none absolute inset-y-0 start-px z-10 flex items-center ps-[calc(--spacing(3)-1px)] opacity-80 has-[+[data-size=sm]]:ps-[calc(--spacing(2.5)-1px)] [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4" | ||
| 32 | data-slot="autocomplete-start-addon" | ||
| 33 | > | ||
| 34 | {startAddon} | ||
| 35 | </div> | ||
| 36 | )} | ||
| 37 | <AutocompletePrimitive.Input | ||
| 38 | className={cn( | ||
| 39 | startAddon && | ||
| 40 | "data-[size=sm]:*:data-[slot=autocomplete-input]:ps-[calc(--spacing(7.5)-1px)] *:data-[slot=autocomplete-input]:ps-[calc(--spacing(8.5)-1px)] sm:data-[size=sm]:*:data-[slot=autocomplete-input]:ps-[calc(--spacing(7)-1px)] sm:*:data-[slot=autocomplete-input]:ps-[calc(--spacing(8)-1px)]", | ||
| 41 | sizeValue === "sm" | ||
| 42 | ? "has-[+[data-slot=autocomplete-trigger],+[data-slot=autocomplete-clear]]:*:data-[slot=autocomplete-input]:pe-6.5" | ||
| 43 | : "has-[+[data-slot=autocomplete-trigger],+[data-slot=autocomplete-clear]]:*:data-[slot=autocomplete-input]:pe-7", | ||
| 44 | className, | ||
| 45 | )} | ||
| 46 | data-slot="autocomplete-input" | ||
| 47 | render={<Input nativeInput size={sizeValue} />} | ||
| 48 | {...props} | ||
| 49 | /> | ||
| 50 | {showTrigger && ( | ||
| 51 | <AutocompleteTrigger | ||
| 52 | className={cn( | ||
| 53 | "-translate-y-1/2 absolute top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-colors pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 has-[+[data-slot=autocomplete-clear]]:hidden sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", | ||
| 54 | sizeValue === "sm" ? "end-0" : "end-0.5", | ||
| 55 | )} | ||
| 56 | > | ||
| 57 | <ChevronsUpDownIcon /> | ||
| 58 | </AutocompleteTrigger> | ||
| 59 | )} | ||
| 60 | {showClear && ( | ||
| 61 | <AutocompleteClear | ||
| 62 | className={cn( | ||
| 63 | "-translate-y-1/2 absolute top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-colors pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 has-[+[data-slot=autocomplete-clear]]:hidden sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", | ||
| 64 | sizeValue === "sm" ? "end-0" : "end-0.5", | ||
| 65 | )} | ||
| 66 | > | ||
| 67 | <XIcon /> | ||
| 68 | </AutocompleteClear> | ||
| 69 | )} | ||
| 70 | </div> | ||
| 71 | ); | ||
| 72 | } | ||
| 73 | |||
| 74 | function AutocompletePopup({ | ||
| 75 | className, | ||
| 76 | children, | ||
| 77 | side = "bottom", | ||
| 78 | sideOffset = 4, | ||
| 79 | alignOffset, | ||
| 80 | align = "start", | ||
| 81 | ...props | ||
| 82 | }: AutocompletePrimitive.Popup.Props & { | ||
| 83 | align?: AutocompletePrimitive.Positioner.Props["align"]; | ||
| 84 | sideOffset?: AutocompletePrimitive.Positioner.Props["sideOffset"]; | ||
| 85 | alignOffset?: AutocompletePrimitive.Positioner.Props["alignOffset"]; | ||
| 86 | side?: AutocompletePrimitive.Positioner.Props["side"]; | ||
| 87 | }) { | ||
| 88 | return ( | ||
| 89 | <AutocompletePrimitive.Portal> | ||
| 90 | <AutocompletePrimitive.Positioner | ||
| 91 | align={align} | ||
| 92 | alignOffset={alignOffset} | ||
| 93 | className="z-50 select-none" | ||
| 94 | data-slot="autocomplete-positioner" | ||
| 95 | side={side} | ||
| 96 | sideOffset={sideOffset} | ||
| 97 | > | ||
| 98 | <span | ||
| 99 | className={cn( | ||
| 100 | "relative flex max-h-full min-w-(--anchor-width) max-w-(--available-width) origin-(--transform-origin) rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 transition-[scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", | ||
| 101 | className, | ||
| 102 | )} | ||
| 103 | > | ||
| 104 | <AutocompletePrimitive.Popup | ||
| 105 | className="flex max-h-[min(var(--available-height),23rem)] flex-1 flex-col text-foreground" | ||
| 106 | data-slot="autocomplete-popup" | ||
| 107 | {...props} | ||
| 108 | > | ||
| 109 | {children} | ||
| 110 | </AutocompletePrimitive.Popup> | ||
| 111 | </span> | ||
| 112 | </AutocompletePrimitive.Positioner> | ||
| 113 | </AutocompletePrimitive.Portal> | ||
| 114 | ); | ||
| 115 | } | ||
| 116 | |||
| 117 | function AutocompleteItem({ | ||
| 118 | className, | ||
| 119 | children, | ||
| 120 | ...props | ||
| 121 | }: AutocompletePrimitive.Item.Props) { | ||
| 122 | return ( | ||
| 123 | <AutocompletePrimitive.Item | ||
| 124 | className={cn( | ||
| 125 | "flex min-h-8 cursor-default select-none items-center rounded-sm px-2 py-1 text-base outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm", | ||
| 126 | className, | ||
| 127 | )} | ||
| 128 | data-slot="autocomplete-item" | ||
| 129 | {...props} | ||
| 130 | > | ||
| 131 | {children} | ||
| 132 | </AutocompletePrimitive.Item> | ||
| 133 | ); | ||
| 134 | } | ||
| 135 | |||
| 136 | function AutocompleteSeparator({ | ||
| 137 | className, | ||
| 138 | ...props | ||
| 139 | }: AutocompletePrimitive.Separator.Props) { | ||
| 140 | return ( | ||
| 141 | <AutocompletePrimitive.Separator | ||
| 142 | className={cn("mx-2 my-1 h-px bg-border last:hidden", className)} | ||
| 143 | data-slot="autocomplete-separator" | ||
| 144 | {...props} | ||
| 145 | /> | ||
| 146 | ); | ||
| 147 | } | ||
| 148 | |||
| 149 | function AutocompleteGroup({ | ||
| 150 | className, | ||
| 151 | ...props | ||
| 152 | }: AutocompletePrimitive.Group.Props) { | ||
| 153 | return ( | ||
| 154 | <AutocompletePrimitive.Group | ||
| 155 | className={cn("[[role=group]+&]:mt-1.5", className)} | ||
| 156 | data-slot="autocomplete-group" | ||
| 157 | {...props} | ||
| 158 | /> | ||
| 159 | ); | ||
| 160 | } | ||
| 161 | |||
| 162 | function AutocompleteGroupLabel({ | ||
| 163 | className, | ||
| 164 | ...props | ||
| 165 | }: AutocompletePrimitive.GroupLabel.Props) { | ||
| 166 | return ( | ||
| 167 | <AutocompletePrimitive.GroupLabel | ||
| 168 | className={cn( | ||
| 169 | "px-2 py-1.5 font-medium text-muted-foreground text-xs", | ||
| 170 | className, | ||
| 171 | )} | ||
| 172 | data-slot="autocomplete-group-label" | ||
| 173 | {...props} | ||
| 174 | /> | ||
| 175 | ); | ||
| 176 | } | ||
| 177 | |||
| 178 | function AutocompleteEmpty({ | ||
| 179 | className, | ||
| 180 | ...props | ||
| 181 | }: AutocompletePrimitive.Empty.Props) { | ||
| 182 | return ( | ||
| 183 | <AutocompletePrimitive.Empty | ||
| 184 | className={cn( | ||
| 185 | "not-empty:p-2 text-center text-base text-muted-foreground sm:text-sm", | ||
| 186 | className, | ||
| 187 | )} | ||
| 188 | data-slot="autocomplete-empty" | ||
| 189 | {...props} | ||
| 190 | /> | ||
| 191 | ); | ||
| 192 | } | ||
| 193 | |||
| 194 | function AutocompleteRow({ | ||
| 195 | className, | ||
| 196 | ...props | ||
| 197 | }: AutocompletePrimitive.Row.Props) { | ||
| 198 | return ( | ||
| 199 | <AutocompletePrimitive.Row | ||
| 200 | className={className} | ||
| 201 | data-slot="autocomplete-row" | ||
| 202 | {...props} | ||
| 203 | /> | ||
| 204 | ); | ||
| 205 | } | ||
| 206 | |||
| 207 | function AutocompleteValue({ ...props }: AutocompletePrimitive.Value.Props) { | ||
| 208 | return ( | ||
| 209 | <AutocompletePrimitive.Value data-slot="autocomplete-value" {...props} /> | ||
| 210 | ); | ||
| 211 | } | ||
| 212 | |||
| 213 | function AutocompleteList({ | ||
| 214 | className, | ||
| 215 | ...props | ||
| 216 | }: AutocompletePrimitive.List.Props) { | ||
| 217 | return ( | ||
| 218 | <ScrollArea scrollbarGutter scrollFade> | ||
| 219 | <AutocompletePrimitive.List | ||
| 220 | className={cn( | ||
| 221 | "not-empty:scroll-py-1 not-empty:p-1 in-data-has-overflow-y:pe-3", | ||
| 222 | className, | ||
| 223 | )} | ||
| 224 | data-slot="autocomplete-list" | ||
| 225 | {...props} | ||
| 226 | /> | ||
| 227 | </ScrollArea> | ||
| 228 | ); | ||
| 229 | } | ||
| 230 | |||
| 231 | function AutocompleteClear({ | ||
| 232 | className, | ||
| 233 | ...props | ||
| 234 | }: AutocompletePrimitive.Clear.Props) { | ||
| 235 | return ( | ||
| 236 | <AutocompletePrimitive.Clear | ||
| 237 | className={cn( | ||
| 238 | "-translate-y-1/2 absolute end-0.5 top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-[color,background-color,box-shadow,opacity] pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", | ||
| 239 | className, | ||
| 240 | )} | ||
| 241 | data-slot="autocomplete-clear" | ||
| 242 | {...props} | ||
| 243 | > | ||
| 244 | <XIcon /> | ||
| 245 | </AutocompletePrimitive.Clear> | ||
| 246 | ); | ||
| 247 | } | ||
| 248 | |||
| 249 | function AutocompleteStatus({ | ||
| 250 | className, | ||
| 251 | ...props | ||
| 252 | }: AutocompletePrimitive.Status.Props) { | ||
| 253 | return ( | ||
| 254 | <AutocompletePrimitive.Status | ||
| 255 | className={cn( | ||
| 256 | "px-3 py-2 font-medium text-muted-foreground text-xs empty:m-0 empty:p-0", | ||
| 257 | className, | ||
| 258 | )} | ||
| 259 | data-slot="autocomplete-status" | ||
| 260 | {...props} | ||
| 261 | /> | ||
| 262 | ); | ||
| 263 | } | ||
| 264 | |||
| 265 | function AutocompleteCollection({ | ||
| 266 | ...props | ||
| 267 | }: AutocompletePrimitive.Collection.Props) { | ||
| 268 | return ( | ||
| 269 | <AutocompletePrimitive.Collection | ||
| 270 | data-slot="autocomplete-collection" | ||
| 271 | {...props} | ||
| 272 | /> | ||
| 273 | ); | ||
| 274 | } | ||
| 275 | |||
| 276 | function AutocompleteTrigger({ | ||
| 277 | className, | ||
| 278 | ...props | ||
| 279 | }: AutocompletePrimitive.Trigger.Props) { | ||
| 280 | return ( | ||
| 281 | <AutocompletePrimitive.Trigger | ||
| 282 | className={className} | ||
| 283 | data-slot="autocomplete-trigger" | ||
| 284 | {...props} | ||
| 285 | /> | ||
| 286 | ); | ||
| 287 | } | ||
| 288 | |||
| 289 | const useAutocompleteFilter = AutocompletePrimitive.useFilter; | ||
| 290 | |||
| 291 | export { | ||
| 292 | Autocomplete, | ||
| 293 | AutocompleteInput, | ||
| 294 | AutocompleteTrigger, | ||
| 295 | AutocompletePopup, | ||
| 296 | AutocompleteItem, | ||
| 297 | AutocompleteSeparator, | ||
| 298 | AutocompleteGroup, | ||
| 299 | AutocompleteGroupLabel, | ||
| 300 | AutocompleteEmpty, | ||
| 301 | AutocompleteValue, | ||
| 302 | AutocompleteList, | ||
| 303 | AutocompleteClear, | ||
| 304 | AutocompleteStatus, | ||
| 305 | AutocompleteRow, | ||
| 306 | AutocompleteCollection, | ||
| 307 | useAutocompleteFilter, | ||
| 308 | }; | ||
apps/renderer/src/components/ui/avatar.tsx created+44| ... | @@ -0,0 +1,44 @@ | ||
| 1 | import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"; | ||
| 2 | |||
| 3 | import { cn } from "#/lib/utils"; | ||
| 4 | |||
| 5 | function Avatar({ className, ...props }: AvatarPrimitive.Root.Props) { | ||
| 6 | return ( | ||
| 7 | <AvatarPrimitive.Root | ||
| 8 | className={cn( | ||
| 9 | "inline-flex size-8 shrink-0 select-none items-center justify-center overflow-hidden rounded-full bg-background align-middle font-medium text-xs", | ||
| 10 | className, | ||
| 11 | )} | ||
| 12 | data-slot="avatar" | ||
| 13 | {...props} | ||
| 14 | /> | ||
| 15 | ); | ||
| 16 | } | ||
| 17 | |||
| 18 | function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) { | ||
| 19 | return ( | ||
| 20 | <AvatarPrimitive.Image | ||
| 21 | className={cn("size-full object-cover", className)} | ||
| 22 | data-slot="avatar-image" | ||
| 23 | {...props} | ||
| 24 | /> | ||
| 25 | ); | ||
| 26 | } | ||
| 27 | |||
| 28 | function AvatarFallback({ | ||
| 29 | className, | ||
| 30 | ...props | ||
| 31 | }: AvatarPrimitive.Fallback.Props) { | ||
| 32 | return ( | ||
| 33 | <AvatarPrimitive.Fallback | ||
| 34 | className={cn( | ||
| 35 | "flex size-full items-center justify-center rounded-full bg-muted", | ||
| 36 | className, | ||
| 37 | )} | ||
| 38 | data-slot="avatar-fallback" | ||
| 39 | {...props} | ||
| 40 | /> | ||
| 41 | ); | ||
| 42 | } | ||
| 43 | |||
| 44 | export { Avatar, AvatarImage, AvatarFallback }; | ||
apps/renderer/src/components/ui/badge.tsx created+58| ... | @@ -0,0 +1,58 @@ | ||
| 1 | import { mergeProps } from "@base-ui/react/merge-props"; | ||
| 2 | import { useRender } from "@base-ui/react/use-render"; | ||
| 3 | import { type VariantProps, cva } from "class-variance-authority"; | ||
| 4 | |||
| 5 | import { cn } from "#/lib/utils"; | ||
| 6 | |||
| 7 | const badgeVariants = cva( | ||
| 8 | "relative inline-flex shrink-0 items-center justify-center gap-1 whitespace-nowrap rounded-sm border border-transparent font-medium outline-none transition-shadow focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-3.5 sm:[&_svg:not([class*='size-'])]:size-3 [&_svg]:pointer-events-none [&_svg]:shrink-0 [button,a&]:cursor-pointer [button,a&]:pointer-coarse:after:absolute [button,a&]:pointer-coarse:after:size-full [button,a&]:pointer-coarse:after:min-h-11 [button,a&]:pointer-coarse:after:min-w-11", | ||
| 9 | { | ||
| 10 | defaultVariants: { | ||
| 11 | size: "default", | ||
| 12 | variant: "default", | ||
| 13 | }, | ||
| 14 | variants: { | ||
| 15 | size: { | ||
| 16 | default: | ||
| 17 | "h-5.5 min-w-5.5 px-[calc(--spacing(1)-1px)] text-sm sm:h-4.5 sm:min-w-4.5 sm:text-xs", | ||
| 18 | lg: "h-6.5 min-w-6.5 px-[calc(--spacing(1.5)-1px)] text-base sm:h-5.5 sm:min-w-5.5 sm:text-sm", | ||
| 19 | sm: "h-5 min-w-5 rounded-[calc(var(--radius-sm)-2px)] px-[calc(--spacing(1)-1px)] text-xs sm:h-4 sm:min-w-4 sm:text-[.625rem]", | ||
| 20 | }, | ||
| 21 | variant: { | ||
| 22 | default: | ||
| 23 | "bg-primary text-primary-foreground [button,a&]:hover:bg-primary/90", | ||
| 24 | destructive: | ||
| 25 | "bg-destructive text-white [button,a&]:hover:bg-destructive/90", | ||
| 26 | error: | ||
| 27 | "bg-destructive/8 text-destructive-foreground dark:bg-destructive/16", | ||
| 28 | info: "bg-info/8 text-info-foreground dark:bg-info/16", | ||
| 29 | outline: | ||
| 30 | "border-input bg-background text-foreground dark:bg-input/32 [button,a&]:hover:bg-accent/50 dark:[button,a&]:hover:bg-input/48", | ||
| 31 | secondary: | ||
| 32 | "bg-secondary text-secondary-foreground [button,a&]:hover:bg-secondary/90", | ||
| 33 | success: "bg-success/8 text-success-foreground dark:bg-success/16", | ||
| 34 | warning: "bg-warning/8 text-warning-foreground dark:bg-warning/16", | ||
| 35 | }, | ||
| 36 | }, | ||
| 37 | }, | ||
| 38 | ); | ||
| 39 | |||
| 40 | interface BadgeProps extends useRender.ComponentProps<"span"> { | ||
| 41 | variant?: VariantProps<typeof badgeVariants>["variant"]; | ||
| 42 | size?: VariantProps<typeof badgeVariants>["size"]; | ||
| 43 | } | ||
| 44 | |||
| 45 | function Badge({ className, variant, size, render, ...props }: BadgeProps) { | ||
| 46 | const defaultProps = { | ||
| 47 | className: cn(badgeVariants({ className, size, variant })), | ||
| 48 | "data-slot": "badge", | ||
| 49 | }; | ||
| 50 | |||
| 51 | return useRender({ | ||
| 52 | defaultTagName: "span", | ||
| 53 | props: mergeProps<"span">(defaultProps, props), | ||
| 54 | render, | ||
| 55 | }); | ||
| 56 | } | ||
| 57 | |||
| 58 | export { Badge, badgeVariants }; | ||
apps/renderer/src/components/ui/button.tsx created+71| ... | @@ -0,0 +1,71 @@ | ||
| 1 | import { mergeProps } from "@base-ui/react/merge-props"; | ||
| 2 | import { useRender } from "@base-ui/react/use-render"; | ||
| 3 | import { type VariantProps, cva } from "class-variance-authority"; | ||
| 4 | import type * as React from "react"; | ||
| 5 | |||
| 6 | import { cn } from "#/lib/utils"; | ||
| 7 | |||
| 8 | const buttonVariants = cva( | ||
| 9 | "[&_svg]:-mx-0.5 relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg border font-medium text-base outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 sm:text-sm [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", | ||
| 10 | { | ||
| 11 | defaultVariants: { | ||
| 12 | size: "default", | ||
| 13 | variant: "default", | ||
| 14 | }, | ||
| 15 | variants: { | ||
| 16 | size: { | ||
| 17 | default: "h-9 px-[calc(--spacing(3)-1px)] sm:h-8", | ||
| 18 | icon: "size-9 sm:size-8", | ||
| 19 | "icon-lg": "size-10 sm:size-9", | ||
| 20 | "icon-sm": "size-8 sm:size-7", | ||
| 21 | "icon-xl": | ||
| 22 | "size-11 sm:size-10 [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5", | ||
| 23 | "icon-xs": | ||
| 24 | "size-7 rounded-md before:rounded-[calc(var(--radius-md)-1px)] sm:size-6 not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-4 sm:not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-3.5", | ||
| 25 | lg: "h-10 px-[calc(--spacing(3.5)-1px)] sm:h-9", | ||
| 26 | sm: "h-8 gap-1.5 px-[calc(--spacing(2.5)-1px)] sm:h-7", | ||
| 27 | xl: "h-11 px-[calc(--spacing(4)-1px)] text-lg sm:h-10 sm:text-base [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5", | ||
| 28 | xs: "h-7 gap-1 rounded-md px-[calc(--spacing(2)-1px)] text-sm before:rounded-[calc(var(--radius-md)-1px)] sm:h-6 sm:text-xs [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5", | ||
| 29 | }, | ||
| 30 | variant: { | ||
| 31 | default: | ||
| 32 | "not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-primary bg-primary text-primary-foreground shadow-primary/24 shadow-xs [:active,[data-pressed]]:inset-shadow-[0_1px_--theme(--color-black/8%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:bg-primary/90", | ||
| 33 | destructive: | ||
| 34 | "not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-destructive bg-destructive text-white shadow-destructive/24 shadow-xs [:active,[data-pressed]]:inset-shadow-[0_1px_--theme(--color-black/8%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:bg-destructive/90", | ||
| 35 | "destructive-outline": | ||
| 36 | "border-input bg-popover not-dark:bg-clip-padding text-destructive-foreground shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:border-destructive/32 [:hover,[data-pressed]]:bg-destructive/4", | ||
| 37 | ghost: | ||
| 38 | "border-transparent text-foreground data-pressed:bg-accent [:hover,[data-pressed]]:bg-accent", | ||
| 39 | link: "border-transparent underline-offset-4 [:hover,[data-pressed]]:underline", | ||
| 40 | outline: | ||
| 41 | "border-input bg-popover not-dark:bg-clip-padding text-foreground shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:bg-accent/50 dark:[:hover,[data-pressed]]:bg-input/64", | ||
| 42 | secondary: | ||
| 43 | "border-transparent bg-secondary text-secondary-foreground [:active,[data-pressed]]:bg-secondary/80 [:hover,[data-pressed]]:bg-secondary/90", | ||
| 44 | }, | ||
| 45 | }, | ||
| 46 | }, | ||
| 47 | ); | ||
| 48 | |||
| 49 | interface ButtonProps extends useRender.ComponentProps<"button"> { | ||
| 50 | variant?: VariantProps<typeof buttonVariants>["variant"]; | ||
| 51 | size?: VariantProps<typeof buttonVariants>["size"]; | ||
| 52 | } | ||
| 53 | |||
| 54 | function Button({ className, variant, size, render, ...props }: ButtonProps) { | ||
| 55 | const typeValue: React.ButtonHTMLAttributes<HTMLButtonElement>["type"] = | ||
| 56 | render ? undefined : "button"; | ||
| 57 | |||
| 58 | const defaultProps = { | ||
| 59 | className: cn(buttonVariants({ className, size, variant })), | ||
| 60 | "data-slot": "button", | ||
| 61 | type: typeValue, | ||
| 62 | }; | ||
| 63 | |||
| 64 | return useRender({ | ||
| 65 | defaultTagName: "button", | ||
| 66 | props: mergeProps<"button">(defaultProps, props), | ||
| 67 | render, | ||
| 68 | }); | ||
| 69 | } | ||
| 70 | |||
| 71 | export { Button, buttonVariants }; | ||
apps/renderer/src/components/ui/card.tsx created+242| ... | @@ -0,0 +1,242 @@ | ||
| 1 | import { mergeProps } from "@base-ui/react/merge-props"; | ||
| 2 | import { useRender } from "@base-ui/react/use-render"; | ||
| 3 | |||
| 4 | import { cn } from "#/lib/utils"; | ||
| 5 | |||
| 6 | function Card({ | ||
| 7 | className, | ||
| 8 | render, | ||
| 9 | ...props | ||
| 10 | }: useRender.ComponentProps<"div">) { | ||
| 11 | const defaultProps = { | ||
| 12 | className: cn( | ||
| 13 | "relative flex flex-col rounded-2xl border bg-card not-dark:bg-clip-padding text-card-foreground shadow-xs/5 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", | ||
| 14 | className, | ||
| 15 | ), | ||
| 16 | "data-slot": "card", | ||
| 17 | }; | ||
| 18 | |||
| 19 | return useRender({ | ||
| 20 | defaultTagName: "div", | ||
| 21 | props: mergeProps<"div">(defaultProps, props), | ||
| 22 | render, | ||
| 23 | }); | ||
| 24 | } | ||
| 25 | |||
| 26 | function CardFrame({ | ||
| 27 | className, | ||
| 28 | render, | ||
| 29 | ...props | ||
| 30 | }: useRender.ComponentProps<"div">) { | ||
| 31 | const defaultProps = { | ||
| 32 | className: cn( | ||
| 33 | "flex flex-col relative rounded-2xl border bg-card before:bg-muted/72 not-dark:bg-clip-padding text-card-foreground shadow-xs/5 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)] *:data-[slot=card]:-m-px *:not-last:data-[slot=card]:rounded-b-xl *:not-last:data-[slot=card]:before:rounded-b-[calc(var(--radius-xl)-1px)] *:not-first:data-[slot=card]:rounded-t-xl *:not-first:data-[slot=card]:before:rounded-t-[calc(var(--radius-xl)-1px)] *:data-[slot=card]:[clip-path:inset(-1rem_1px)] *:data-[slot=card]:first:[clip-path:inset(1px_1px_-1rem_1px_round_calc(var(--radius-2xl)-1px))] *:data-[slot=card]:last:[clip-path:inset(-1rem_1px_1px_1px_round_calc(var(--radius-2xl)-1px))] *:data-[slot=card]:shadow-none *:data-[slot=card]:before:hidden *:data-[slot=card]:bg-clip-padding", | ||
| 34 | className, | ||
| 35 | ), | ||
| 36 | "data-slot": "card-frame", | ||
| 37 | }; | ||
| 38 | |||
| 39 | return useRender({ | ||
| 40 | defaultTagName: "div", | ||
| 41 | props: mergeProps<"div">(defaultProps, props), | ||
| 42 | render, | ||
| 43 | }); | ||
| 44 | } | ||
| 45 | |||
| 46 | function CardFrameHeader({ | ||
| 47 | className, | ||
| 48 | render, | ||
| 49 | ...props | ||
| 50 | }: useRender.ComponentProps<"div">) { | ||
| 51 | const defaultProps = { | ||
| 52 | className: cn("relative flex flex-col px-6 py-4", className), | ||
| 53 | "data-slot": "card-frame-header", | ||
| 54 | }; | ||
| 55 | |||
| 56 | return useRender({ | ||
| 57 | defaultTagName: "div", | ||
| 58 | props: mergeProps<"div">(defaultProps, props), | ||
| 59 | render, | ||
| 60 | }); | ||
| 61 | } | ||
| 62 | |||
| 63 | function CardFrameTitle({ | ||
| 64 | className, | ||
| 65 | render, | ||
| 66 | ...props | ||
| 67 | }: useRender.ComponentProps<"div">) { | ||
| 68 | const defaultProps = { | ||
| 69 | className: cn("font-semibold text-sm", className), | ||
| 70 | "data-slot": "card-frame-title", | ||
| 71 | }; | ||
| 72 | |||
| 73 | return useRender({ | ||
| 74 | defaultTagName: "div", | ||
| 75 | props: mergeProps<"div">(defaultProps, props), | ||
| 76 | render, | ||
| 77 | }); | ||
| 78 | } | ||
| 79 | |||
| 80 | function CardFrameDescription({ | ||
| 81 | className, | ||
| 82 | render, | ||
| 83 | ...props | ||
| 84 | }: useRender.ComponentProps<"div">) { | ||
| 85 | const defaultProps = { | ||
| 86 | className: cn("text-muted-foreground text-sm", className), | ||
| 87 | "data-slot": "card-frame-description", | ||
| 88 | }; | ||
| 89 | |||
| 90 | return useRender({ | ||
| 91 | defaultTagName: "div", | ||
| 92 | props: mergeProps<"div">(defaultProps, props), | ||
| 93 | render, | ||
| 94 | }); | ||
| 95 | } | ||
| 96 | |||
| 97 | function CardFrameFooter({ | ||
| 98 | className, | ||
| 99 | render, | ||
| 100 | ...props | ||
| 101 | }: useRender.ComponentProps<"div">) { | ||
| 102 | const defaultProps = { | ||
| 103 | className: cn("px-6 py-4", className), | ||
| 104 | "data-slot": "card-frame-footer", | ||
| 105 | }; | ||
| 106 | |||
| 107 | return useRender({ | ||
| 108 | defaultTagName: "div", | ||
| 109 | props: mergeProps<"div">(defaultProps, props), | ||
| 110 | render, | ||
| 111 | }); | ||
| 112 | } | ||
| 113 | |||
| 114 | function CardHeader({ | ||
| 115 | className, | ||
| 116 | render, | ||
| 117 | ...props | ||
| 118 | }: useRender.ComponentProps<"div">) { | ||
| 119 | const defaultProps = { | ||
| 120 | className: cn( | ||
| 121 | "grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 p-6 in-[[data-slot=card]:has(>[data-slot=card-panel])]:pb-4 has-data-[slot=card-action]:grid-cols-[1fr_auto]", | ||
| 122 | className, | ||
| 123 | ), | ||
| 124 | "data-slot": "card-header", | ||
| 125 | }; | ||
| 126 | |||
| 127 | return useRender({ | ||
| 128 | defaultTagName: "div", | ||
| 129 | props: mergeProps<"div">(defaultProps, props), | ||
| 130 | render, | ||
| 131 | }); | ||
| 132 | } | ||
| 133 | |||
| 134 | function CardTitle({ | ||
| 135 | className, | ||
| 136 | render, | ||
| 137 | ...props | ||
| 138 | }: useRender.ComponentProps<"div">) { | ||
| 139 | const defaultProps = { | ||
| 140 | className: cn("font-semibold text-lg leading-none", className), | ||
| 141 | "data-slot": "card-title", | ||
| 142 | }; | ||
| 143 | |||
| 144 | return useRender({ | ||
| 145 | defaultTagName: "div", | ||
| 146 | props: mergeProps<"div">(defaultProps, props), | ||
| 147 | render, | ||
| 148 | }); | ||
| 149 | } | ||
| 150 | |||
| 151 | function CardDescription({ | ||
| 152 | className, | ||
| 153 | render, | ||
| 154 | ...props | ||
| 155 | }: useRender.ComponentProps<"div">) { | ||
| 156 | const defaultProps = { | ||
| 157 | className: cn("text-muted-foreground text-sm", className), | ||
| 158 | "data-slot": "card-description", | ||
| 159 | }; | ||
| 160 | |||
| 161 | return useRender({ | ||
| 162 | defaultTagName: "div", | ||
| 163 | props: mergeProps<"div">(defaultProps, props), | ||
| 164 | render, | ||
| 165 | }); | ||
| 166 | } | ||
| 167 | |||
| 168 | function CardAction({ | ||
| 169 | className, | ||
| 170 | render, | ||
| 171 | ...props | ||
| 172 | }: useRender.ComponentProps<"div">) { | ||
| 173 | const defaultProps = { | ||
| 174 | className: cn( | ||
| 175 | "col-start-2 row-span-2 row-start-1 self-start justify-self-end inline-flex", | ||
| 176 | className, | ||
| 177 | ), | ||
| 178 | "data-slot": "card-action", | ||
| 179 | }; | ||
| 180 | |||
| 181 | return useRender({ | ||
| 182 | defaultTagName: "div", | ||
| 183 | props: mergeProps<"div">(defaultProps, props), | ||
| 184 | render, | ||
| 185 | }); | ||
| 186 | } | ||
| 187 | |||
| 188 | function CardPanel({ | ||
| 189 | className, | ||
| 190 | render, | ||
| 191 | ...props | ||
| 192 | }: useRender.ComponentProps<"div">) { | ||
| 193 | const defaultProps = { | ||
| 194 | className: cn( | ||
| 195 | "flex-1 p-6 in-[[data-slot=card]:has(>[data-slot=card-header]:not(.border-b))]:pt-0 in-[[data-slot=card]:has(>[data-slot=card-footer]:not(.border-t))]:pb-0", | ||
| 196 | className, | ||
| 197 | ), | ||
| 198 | "data-slot": "card-panel", | ||
| 199 | }; | ||
| 200 | |||
| 201 | return useRender({ | ||
| 202 | defaultTagName: "div", | ||
| 203 | props: mergeProps<"div">(defaultProps, props), | ||
| 204 | render, | ||
| 205 | }); | ||
| 206 | } | ||
| 207 | |||
| 208 | function CardFooter({ | ||
| 209 | className, | ||
| 210 | render, | ||
| 211 | ...props | ||
| 212 | }: useRender.ComponentProps<"div">) { | ||
| 213 | const defaultProps = { | ||
| 214 | className: cn( | ||
| 215 | "flex items-center p-6 in-[[data-slot=card]:has(>[data-slot=card-panel])]:pt-4", | ||
| 216 | className, | ||
| 217 | ), | ||
| 218 | "data-slot": "card-footer", | ||
| 219 | }; | ||
| 220 | |||
| 221 | return useRender({ | ||
| 222 | defaultTagName: "div", | ||
| 223 | props: mergeProps<"div">(defaultProps, props), | ||
| 224 | render, | ||
| 225 | }); | ||
| 226 | } | ||
| 227 | |||
| 228 | export { | ||
| 229 | Card, | ||
| 230 | CardFrame, | ||
| 231 | CardFrameHeader, | ||
| 232 | CardFrameTitle, | ||
| 233 | CardFrameDescription, | ||
| 234 | CardFrameFooter, | ||
| 235 | CardAction, | ||
| 236 | CardDescription, | ||
| 237 | CardFooter, | ||
| 238 | CardHeader, | ||
| 239 | CardPanel, | ||
| 240 | CardPanel as CardContent, | ||
| 241 | CardTitle, | ||
| 242 | }; | ||
apps/renderer/src/components/ui/checkbox-group.tsx created+14| ... | @@ -0,0 +1,14 @@ | ||
| 1 | import { CheckboxGroup as CheckboxGroupPrimitive } from "@base-ui/react/checkbox-group"; | ||
| 2 | |||
| 3 | import { cn } from "#/lib/utils"; | ||
| 4 | |||
| 5 | function CheckboxGroup({ className, ...props }: CheckboxGroupPrimitive.Props) { | ||
| 6 | return ( | ||
| 7 | <CheckboxGroupPrimitive | ||
| 8 | className={cn("flex flex-col items-start gap-3", className)} | ||
| 9 | {...props} | ||
| 10 | /> | ||
| 11 | ); | ||
| 12 | } | ||
| 13 | |||
| 14 | export { CheckboxGroup }; | ||
apps/renderer/src/components/ui/checkbox.tsx created+58| ... | @@ -0,0 +1,58 @@ | ||
| 1 | import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox"; | ||
| 2 | |||
| 3 | import { cn } from "#/lib/utils"; | ||
| 4 | |||
| 5 | function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) { | ||
| 6 | return ( | ||
| 7 | <CheckboxPrimitive.Root | ||
| 8 | className={cn( | ||
| 9 | "relative inline-flex size-4.5 shrink-0 items-center justify-center rounded-[4px] border border-input bg-background not-dark:bg-clip-padding shadow-xs/5 outline-none ring-ring transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[3px] not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-offset-background aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:ring-destructive/48 data-disabled:opacity-64 sm:size-4 dark:not-data-checked:bg-input/32 dark:aria-invalid:ring-destructive/24 dark:not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)] [[data-disabled],[data-checked],[aria-invalid]]:shadow-none", | ||
| 10 | className, | ||
| 11 | )} | ||
| 12 | data-slot="checkbox" | ||
| 13 | {...props} | ||
| 14 | > | ||
| 15 | <CheckboxPrimitive.Indicator | ||
| 16 | className="-inset-px absolute flex items-center justify-center rounded-[4px] text-primary-foreground data-unchecked:hidden data-checked:bg-primary data-indeterminate:text-foreground" | ||
| 17 | data-slot="checkbox-indicator" | ||
| 18 | render={(props, state) => ( | ||
| 19 | <span {...props}> | ||
| 20 | {state.indeterminate ? ( | ||
| 21 | <svg | ||
| 22 | className="size-3.5 sm:size-3" | ||
| 23 | fill="none" | ||
| 24 | height="24" | ||
| 25 | stroke="currentColor" | ||
| 26 | strokeLinecap="round" | ||
| 27 | strokeLinejoin="round" | ||
| 28 | strokeWidth="3" | ||
| 29 | viewBox="0 0 24 24" | ||
| 30 | width="24" | ||
| 31 | xmlns="http://www.w3.org/2000/svg" | ||
| 32 | > | ||
| 33 | <path d="M5.252 12h13.496" /> | ||
| 34 | </svg> | ||
| 35 | ) : ( | ||
| 36 | <svg | ||
| 37 | className="size-3.5 sm:size-3" | ||
| 38 | fill="none" | ||
| 39 | height="24" | ||
| 40 | stroke="currentColor" | ||
| 41 | strokeLinecap="round" | ||
| 42 | strokeLinejoin="round" | ||
| 43 | strokeWidth="3" | ||
| 44 | viewBox="0 0 24 24" | ||
| 45 | width="24" | ||
| 46 | xmlns="http://www.w3.org/2000/svg" | ||
| 47 | > | ||
| 48 | <path d="M5.252 12.7 10.2 18.63 18.748 5.37" /> | ||
| 49 | </svg> | ||
| 50 | )} | ||
| 51 | </span> | ||
| 52 | )} | ||
| 53 | /> | ||
| 54 | </CheckboxPrimitive.Root> | ||
| 55 | ); | ||
| 56 | } | ||
| 57 | |||
| 58 | export { Checkbox }; | ||
apps/renderer/src/components/ui/collapsible.tsx created+43| ... | @@ -0,0 +1,43 @@ | ||
| 1 | import { Collapsible as CollapsiblePrimitive } from "@base-ui/react/collapsible"; | ||
| 2 | |||
| 3 | import { cn } from "#/lib/utils"; | ||
| 4 | |||
| 5 | function Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) { | ||
| 6 | return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />; | ||
| 7 | } | ||
| 8 | |||
| 9 | function CollapsibleTrigger({ | ||
| 10 | className, | ||
| 11 | ...props | ||
| 12 | }: CollapsiblePrimitive.Trigger.Props) { | ||
| 13 | return ( | ||
| 14 | <CollapsiblePrimitive.Trigger | ||
| 15 | className={cn("cursor-pointer", className)} | ||
| 16 | data-slot="collapsible-trigger" | ||
| 17 | {...props} | ||
| 18 | /> | ||
| 19 | ); | ||
| 20 | } | ||
| 21 | |||
| 22 | function CollapsiblePanel({ | ||
| 23 | className, | ||
| 24 | ...props | ||
| 25 | }: CollapsiblePrimitive.Panel.Props) { | ||
| 26 | return ( | ||
| 27 | <CollapsiblePrimitive.Panel | ||
| 28 | className={cn( | ||
| 29 | "h-(--collapsible-panel-height) overflow-hidden transition-[height] duration-200 data-ending-style:h-0 data-starting-style:h-0", | ||
| 30 | className, | ||
| 31 | )} | ||
| 32 | data-slot="collapsible-panel" | ||
| 33 | {...props} | ||
| 34 | /> | ||
| 35 | ); | ||
| 36 | } | ||
| 37 | |||
| 38 | export { | ||
| 39 | Collapsible, | ||
| 40 | CollapsibleTrigger, | ||
| 41 | CollapsiblePanel, | ||
| 42 | CollapsiblePanel as CollapsibleContent, | ||
| 43 | }; | ||
apps/renderer/src/components/ui/combobox.tsx created+422| ... | @@ -0,0 +1,422 @@ | ||
| 1 | import { Combobox as ComboboxPrimitive } from "@base-ui/react/combobox"; | ||
| 2 | import { ChevronsUpDownIcon, XIcon } from "lucide-react"; | ||
| 3 | import * as React from "react"; | ||
| 4 | |||
| 5 | import { Input } from "#/components/ui/input"; | ||
| 6 | import { ScrollArea } from "#/components/ui/scroll-area"; | ||
| 7 | import { cn } from "#/lib/utils"; | ||
| 8 | |||
| 9 | const ComboboxContext = React.createContext<{ | ||
| 10 | chipsRef: React.RefObject<Element | null> | null; | ||
| 11 | multiple: boolean; | ||
| 12 | }>({ | ||
| 13 | chipsRef: null, | ||
| 14 | multiple: false, | ||
| 15 | }); | ||
| 16 | |||
| 17 | function Combobox<Value, Multiple extends boolean | undefined = false>( | ||
| 18 | props: ComboboxPrimitive.Root.Props<Value, Multiple>, | ||
| 19 | ): React.JSX.Element { | ||
| 20 | const chipsRef = React.useRef<Element | null>(null); | ||
| 21 | return ( | ||
| 22 | <ComboboxContext.Provider value={{ chipsRef, multiple: !!props.multiple }}> | ||
| 23 | <ComboboxPrimitive.Root {...props} /> | ||
| 24 | </ComboboxContext.Provider> | ||
| 25 | ); | ||
| 26 | } | ||
| 27 | |||
| 28 | function ComboboxChipsInput({ | ||
| 29 | className, | ||
| 30 | size, | ||
| 31 | ...props | ||
| 32 | }: Omit<ComboboxPrimitive.Input.Props, "size"> & { | ||
| 33 | size?: "sm" | "default" | "lg" | number; | ||
| 34 | ref?: React.Ref<HTMLInputElement>; | ||
| 35 | }) { | ||
| 36 | const sizeValue = (size ?? "default") as "sm" | "default" | "lg" | number; | ||
| 37 | |||
| 38 | return ( | ||
| 39 | <ComboboxPrimitive.Input | ||
| 40 | className={cn( | ||
| 41 | "min-w-12 flex-1 text-base outline-none sm:text-sm [[data-slot=combobox-chip]+&]:ps-0.5", | ||
| 42 | sizeValue === "sm" ? "ps-1.5" : "ps-2", | ||
| 43 | className, | ||
| 44 | )} | ||
| 45 | data-size={typeof sizeValue === "string" ? sizeValue : undefined} | ||
| 46 | data-slot="combobox-chips-input" | ||
| 47 | size={typeof sizeValue === "number" ? sizeValue : undefined} | ||
| 48 | {...props} | ||
| 49 | /> | ||
| 50 | ); | ||
| 51 | } | ||
| 52 | |||
| 53 | function ComboboxInput({ | ||
| 54 | className, | ||
| 55 | showTrigger = true, | ||
| 56 | showClear = false, | ||
| 57 | startAddon, | ||
| 58 | size, | ||
| 59 | ...props | ||
| 60 | }: Omit<ComboboxPrimitive.Input.Props, "size"> & { | ||
| 61 | showTrigger?: boolean; | ||
| 62 | showClear?: boolean; | ||
| 63 | startAddon?: React.ReactNode; | ||
| 64 | size?: "sm" | "default" | "lg" | number; | ||
| 65 | ref?: React.Ref<HTMLInputElement>; | ||
| 66 | }) { | ||
| 67 | const sizeValue = (size ?? "default") as "sm" | "default" | "lg" | number; | ||
| 68 | |||
| 69 | return ( | ||
| 70 | <div className="relative not-has-[>*.w-full]:w-fit w-full text-foreground has-disabled:opacity-64"> | ||
| 71 | {startAddon && ( | ||
| 72 | <div | ||
| 73 | aria-hidden="true" | ||
| 74 | className="[&_svg]:-mx-0.5 pointer-events-none absolute inset-y-0 start-px z-10 flex items-center ps-[calc(--spacing(3)-1px)] opacity-80 has-[+[data-size=sm]]:ps-[calc(--spacing(2.5)-1px)] [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4" | ||
| 75 | data-slot="combobox-start-addon" | ||
| 76 | > | ||
| 77 | {startAddon} | ||
| 78 | </div> | ||
| 79 | )} | ||
| 80 | <ComboboxPrimitive.Input | ||
| 81 | className={cn( | ||
| 82 | startAddon && | ||
| 83 | "data-[size=sm]:*:data-[slot=combobox-input]:ps-[calc(--spacing(7.5)-1px)] *:data-[slot=combobox-input]:ps-[calc(--spacing(8.5)-1px)] sm:data-[size=sm]:*:data-[slot=combobox-input]:ps-[calc(--spacing(7)-1px)] sm:*:data-[slot=combobox-input]:ps-[calc(--spacing(8)-1px)]", | ||
| 84 | sizeValue === "sm" | ||
| 85 | ? "has-[+[data-slot=combobox-trigger],+[data-slot=combobox-clear]]:*:data-[slot=combobox-input]:pe-6.5" | ||
| 86 | : "has-[+[data-slot=combobox-trigger],+[data-slot=combobox-clear]]:*:data-[slot=combobox-input]:pe-7", | ||
| 87 | className, | ||
| 88 | )} | ||
| 89 | data-slot="combobox-input" | ||
| 90 | render={ | ||
| 91 | <Input | ||
| 92 | className="has-disabled:opacity-100" | ||
| 93 | nativeInput | ||
| 94 | size={sizeValue} | ||
| 95 | /> | ||
| 96 | } | ||
| 97 | {...props} | ||
| 98 | /> | ||
| 99 | {showTrigger && ( | ||
| 100 | <ComboboxTrigger | ||
| 101 | className={cn( | ||
| 102 | "-translate-y-1/2 absolute top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-opacity pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 has-[+[data-slot=combobox-clear]]:hidden sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", | ||
| 103 | sizeValue === "sm" ? "end-0" : "end-0.5", | ||
| 104 | )} | ||
| 105 | > | ||
| 106 | <ChevronsUpDownIcon /> | ||
| 107 | </ComboboxTrigger> | ||
| 108 | )} | ||
| 109 | {showClear && ( | ||
| 110 | <ComboboxClear | ||
| 111 | className={cn( | ||
| 112 | "-translate-y-1/2 absolute top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-opacity pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 has-[+[data-slot=combobox-clear]]:hidden sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", | ||
| 113 | sizeValue === "sm" ? "end-0" : "end-0.5", | ||
| 114 | )} | ||
| 115 | > | ||
| 116 | <XIcon /> | ||
| 117 | </ComboboxClear> | ||
| 118 | )} | ||
| 119 | </div> | ||
| 120 | ); | ||
| 121 | } | ||
| 122 | |||
| 123 | function ComboboxTrigger({ | ||
| 124 | className, | ||
| 125 | ...props | ||
| 126 | }: ComboboxPrimitive.Trigger.Props) { | ||
| 127 | return ( | ||
| 128 | <ComboboxPrimitive.Trigger | ||
| 129 | className={className} | ||
| 130 | data-slot="combobox-trigger" | ||
| 131 | {...props} | ||
| 132 | /> | ||
| 133 | ); | ||
| 134 | } | ||
| 135 | |||
| 136 | function ComboboxPopup({ | ||
| 137 | className, | ||
| 138 | children, | ||
| 139 | side = "bottom", | ||
| 140 | sideOffset = 4, | ||
| 141 | alignOffset, | ||
| 142 | align = "start", | ||
| 143 | ...props | ||
| 144 | }: ComboboxPrimitive.Popup.Props & { | ||
| 145 | align?: ComboboxPrimitive.Positioner.Props["align"]; | ||
| 146 | sideOffset?: ComboboxPrimitive.Positioner.Props["sideOffset"]; | ||
| 147 | alignOffset?: ComboboxPrimitive.Positioner.Props["alignOffset"]; | ||
| 148 | side?: ComboboxPrimitive.Positioner.Props["side"]; | ||
| 149 | }) { | ||
| 150 | const { chipsRef } = React.useContext(ComboboxContext); | ||
| 151 | |||
| 152 | return ( | ||
| 153 | <ComboboxPrimitive.Portal> | ||
| 154 | <ComboboxPrimitive.Positioner | ||
| 155 | align={align} | ||
| 156 | alignOffset={alignOffset} | ||
| 157 | anchor={chipsRef} | ||
| 158 | className="z-50 select-none" | ||
| 159 | data-slot="combobox-positioner" | ||
| 160 | side={side} | ||
| 161 | sideOffset={sideOffset} | ||
| 162 | > | ||
| 163 | <span | ||
| 164 | className={cn( | ||
| 165 | "relative flex max-h-full min-w-(--anchor-width) max-w-(--available-width) origin-(--transform-origin) rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 transition-[scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", | ||
| 166 | className, | ||
| 167 | )} | ||
| 168 | > | ||
| 169 | <ComboboxPrimitive.Popup | ||
| 170 | className="flex max-h-[min(var(--available-height),23rem)] flex-1 flex-col text-foreground" | ||
| 171 | data-slot="combobox-popup" | ||
| 172 | {...props} | ||
| 173 | > | ||
| 174 | {children} | ||
| 175 | </ComboboxPrimitive.Popup> | ||
| 176 | </span> | ||
| 177 | </ComboboxPrimitive.Positioner> | ||
| 178 | </ComboboxPrimitive.Portal> | ||
| 179 | ); | ||
| 180 | } | ||
| 181 | |||
| 182 | function ComboboxItem({ | ||
| 183 | className, | ||
| 184 | children, | ||
| 185 | ...props | ||
| 186 | }: ComboboxPrimitive.Item.Props) { | ||
| 187 | return ( | ||
| 188 | <ComboboxPrimitive.Item | ||
| 189 | className={cn( | ||
| 190 | "grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default grid-cols-[1rem_1fr] items-center gap-2 rounded-sm py-1 ps-2 pe-4 text-base outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", | ||
| 191 | className, | ||
| 192 | )} | ||
| 193 | data-slot="combobox-item" | ||
| 194 | {...props} | ||
| 195 | > | ||
| 196 | <ComboboxPrimitive.ItemIndicator className="col-start-1"> | ||
| 197 | <svg | ||
| 198 | fill="none" | ||
| 199 | height="24" | ||
| 200 | stroke="currentColor" | ||
| 201 | strokeLinecap="round" | ||
| 202 | strokeLinejoin="round" | ||
| 203 | strokeWidth="2" | ||
| 204 | viewBox="0 0 24 24" | ||
| 205 | width="24" | ||
| 206 | xmlns="http://www.w3.org/1500/svg" | ||
| 207 | > | ||
| 208 | <path d="M5.252 12.7 10.2 18.63 18.748 5.37" /> | ||
| 209 | </svg> | ||
| 210 | </ComboboxPrimitive.ItemIndicator> | ||
| 211 | <div className="col-start-2">{children}</div> | ||
| 212 | </ComboboxPrimitive.Item> | ||
| 213 | ); | ||
| 214 | } | ||
| 215 | |||
| 216 | function ComboboxSeparator({ | ||
| 217 | className, | ||
| 218 | ...props | ||
| 219 | }: ComboboxPrimitive.Separator.Props) { | ||
| 220 | return ( | ||
| 221 | <ComboboxPrimitive.Separator | ||
| 222 | className={cn("mx-2 my-1 h-px bg-border last:hidden", className)} | ||
| 223 | data-slot="combobox-separator" | ||
| 224 | {...props} | ||
| 225 | /> | ||
| 226 | ); | ||
| 227 | } | ||
| 228 | |||
| 229 | function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) { | ||
| 230 | return ( | ||
| 231 | <ComboboxPrimitive.Group | ||
| 232 | className={cn("[[role=group]+&]:mt-1.5", className)} | ||
| 233 | data-slot="combobox-group" | ||
| 234 | {...props} | ||
| 235 | /> | ||
| 236 | ); | ||
| 237 | } | ||
| 238 | |||
| 239 | function ComboboxGroupLabel({ | ||
| 240 | className, | ||
| 241 | ...props | ||
| 242 | }: ComboboxPrimitive.GroupLabel.Props) { | ||
| 243 | return ( | ||
| 244 | <ComboboxPrimitive.GroupLabel | ||
| 245 | className={cn( | ||
| 246 | "px-2 py-1.5 font-medium text-muted-foreground text-xs", | ||
| 247 | className, | ||
| 248 | )} | ||
| 249 | data-slot="combobox-group-label" | ||
| 250 | {...props} | ||
| 251 | /> | ||
| 252 | ); | ||
| 253 | } | ||
| 254 | |||
| 255 | function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) { | ||
| 256 | return ( | ||
| 257 | <ComboboxPrimitive.Empty | ||
| 258 | className={cn( | ||
| 259 | "not-empty:p-2 text-center text-base text-muted-foreground sm:text-sm", | ||
| 260 | className, | ||
| 261 | )} | ||
| 262 | data-slot="combobox-empty" | ||
| 263 | {...props} | ||
| 264 | /> | ||
| 265 | ); | ||
| 266 | } | ||
| 267 | |||
| 268 | function ComboboxRow({ className, ...props }: ComboboxPrimitive.Row.Props) { | ||
| 269 | return ( | ||
| 270 | <ComboboxPrimitive.Row | ||
| 271 | className={className} | ||
| 272 | data-slot="combobox-row" | ||
| 273 | {...props} | ||
| 274 | /> | ||
| 275 | ); | ||
| 276 | } | ||
| 277 | |||
| 278 | function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) { | ||
| 279 | return <ComboboxPrimitive.Value data-slot="combobox-value" {...props} />; | ||
| 280 | } | ||
| 281 | |||
| 282 | function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) { | ||
| 283 | return ( | ||
| 284 | <ScrollArea scrollbarGutter scrollFade> | ||
| 285 | <ComboboxPrimitive.List | ||
| 286 | className={cn( | ||
| 287 | "not-empty:scroll-py-1 not-empty:px-1 not-empty:py-1 in-data-has-overflow-y:pe-3", | ||
| 288 | className, | ||
| 289 | )} | ||
| 290 | data-slot="combobox-list" | ||
| 291 | {...props} | ||
| 292 | /> | ||
| 293 | </ScrollArea> | ||
| 294 | ); | ||
| 295 | } | ||
| 296 | |||
| 297 | function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) { | ||
| 298 | return ( | ||
| 299 | <ComboboxPrimitive.Clear | ||
| 300 | className={className} | ||
| 301 | data-slot="combobox-clear" | ||
| 302 | {...props} | ||
| 303 | /> | ||
| 304 | ); | ||
| 305 | } | ||
| 306 | |||
| 307 | function ComboboxStatus({ | ||
| 308 | className, | ||
| 309 | ...props | ||
| 310 | }: ComboboxPrimitive.Status.Props) { | ||
| 311 | return ( | ||
| 312 | <ComboboxPrimitive.Status | ||
| 313 | className={cn( | ||
| 314 | "px-3 py-2 font-medium text-muted-foreground text-xs empty:m-0 empty:p-0", | ||
| 315 | className, | ||
| 316 | )} | ||
| 317 | data-slot="combobox-status" | ||
| 318 | {...props} | ||
| 319 | /> | ||
| 320 | ); | ||
| 321 | } | ||
| 322 | |||
| 323 | function ComboboxCollection(props: ComboboxPrimitive.Collection.Props) { | ||
| 324 | return ( | ||
| 325 | <ComboboxPrimitive.Collection data-slot="combobox-collection" {...props} /> | ||
| 326 | ); | ||
| 327 | } | ||
| 328 | |||
| 329 | function ComboboxChips({ | ||
| 330 | className, | ||
| 331 | children, | ||
| 332 | startAddon, | ||
| 333 | ...props | ||
| 334 | }: ComboboxPrimitive.Chips.Props & { | ||
| 335 | startAddon?: React.ReactNode; | ||
| 336 | }) { | ||
| 337 | const { chipsRef } = React.useContext(ComboboxContext); | ||
| 338 | |||
| 339 | return ( | ||
| 340 | <ComboboxPrimitive.Chips | ||
| 341 | className={cn( | ||
| 342 | "relative inline-flex min-h-9 w-full flex-wrap gap-1 rounded-lg border border-input bg-background not-dark:bg-clip-padding p-[calc(--spacing(1)-1px)] text-base shadow-xs/5 outline-none ring-ring/24 transition-shadow *:min-h-7 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-disabled:not-focus-within:not-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] focus-within:border-ring focus-within:ring-[3px] has-disabled:pointer-events-none has-data-[size=lg]:min-h-10 has-data-[size=sm]:min-h-8 has-aria-invalid:border-destructive/36 has-autofill:bg-foreground/4 has-disabled:opacity-64 has-[:disabled,:focus-within,[aria-invalid]]:shadow-none focus-within:has-aria-invalid:border-destructive/64 focus-within:has-aria-invalid:ring-destructive/16 has-data-[size=lg]:*:min-h-8 has-data-[size=sm]:*:min-h-6 sm:min-h-8 sm:text-sm sm:has-data-[size=lg]:min-h-9 sm:has-data-[size=sm]:min-h-7 sm:*:min-h-6 sm:has-data-[size=lg]:*:min-h-7 sm:has-data-[size=sm]:*:min-h-5 dark:not-has-disabled:bg-input/32 dark:has-autofill:bg-foreground/8 dark:has-aria-invalid:ring-destructive/24 dark:not-has-disabled:not-focus-within:not-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)]", | ||
| 343 | className, | ||
| 344 | )} | ||
| 345 | data-slot="combobox-chips" | ||
| 346 | onMouseDown={(e) => { | ||
| 347 | const target = e.target as HTMLElement; | ||
| 348 | const isChip = target.closest('[data-slot="combobox-chip"]'); | ||
| 349 | if (isChip || !chipsRef?.current) return; | ||
| 350 | e.preventDefault(); | ||
| 351 | const input: HTMLInputElement | null = | ||
| 352 | chipsRef.current.querySelector("input"); | ||
| 353 | if (input && !chipsRef.current.querySelector("input:focus")) { | ||
| 354 | input.focus(); | ||
| 355 | } | ||
| 356 | }} | ||
| 357 | ref={chipsRef as React.Ref<HTMLDivElement> | null} | ||
| 358 | {...props} | ||
| 359 | > | ||
| 360 | {startAddon && ( | ||
| 361 | <div | ||
| 362 | aria-hidden="true" | ||
| 363 | className="[&_svg]:-ms-0.5 [&_svg]:-me-1.5 flex shrink-0 items-center ps-2 opacity-80 has-[~[data-size=sm]]:has-[+[data-slot=combobox-chip]]:pe-1.5 has-[~[data-size=sm]]:ps-1.5 has-[+[data-slot=combobox-chip]]:pe-2 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none" | ||
| 364 | data-slot="combobox-start-addon" | ||
| 365 | > | ||
| 366 | {startAddon} | ||
| 367 | </div> | ||
| 368 | )} | ||
| 369 | {children} | ||
| 370 | </ComboboxPrimitive.Chips> | ||
| 371 | ); | ||
| 372 | } | ||
| 373 | |||
| 374 | function ComboboxChip({ children, ...props }: ComboboxPrimitive.Chip.Props) { | ||
| 375 | return ( | ||
| 376 | <ComboboxPrimitive.Chip | ||
| 377 | className="flex items-center rounded-[calc(var(--radius-md)-1px)] bg-accent ps-2 font-medium text-accent-foreground text-sm outline-none sm:text-xs/(--text-xs--line-height) [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5" | ||
| 378 | data-slot="combobox-chip" | ||
| 379 | {...props} | ||
| 380 | > | ||
| 381 | {children} | ||
| 382 | <ComboboxChipRemove /> | ||
| 383 | </ComboboxPrimitive.Chip> | ||
| 384 | ); | ||
| 385 | } | ||
| 386 | |||
| 387 | function ComboboxChipRemove(props: ComboboxPrimitive.ChipRemove.Props) { | ||
| 388 | return ( | ||
| 389 | <ComboboxPrimitive.ChipRemove | ||
| 390 | aria-label="Remove" | ||
| 391 | className="h-full shrink-0 cursor-pointer px-1.5 opacity-80 hover:opacity-100 [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5" | ||
| 392 | data-slot="combobox-chip-remove" | ||
| 393 | {...props} | ||
| 394 | > | ||
| 395 | <XIcon /> | ||
| 396 | </ComboboxPrimitive.ChipRemove> | ||
| 397 | ); | ||
| 398 | } | ||
| 399 | |||
| 400 | const useComboboxFilter = ComboboxPrimitive.useFilter; | ||
| 401 | |||
| 402 | export { | ||
| 403 | Combobox, | ||
| 404 | ComboboxChipsInput, | ||
| 405 | ComboboxInput, | ||
| 406 | ComboboxTrigger, | ||
| 407 | ComboboxPopup, | ||
| 408 | ComboboxItem, | ||
| 409 | ComboboxSeparator, | ||
| 410 | ComboboxGroup, | ||
| 411 | ComboboxGroupLabel, | ||
| 412 | ComboboxEmpty, | ||
| 413 | ComboboxValue, | ||
| 414 | ComboboxList, | ||
| 415 | ComboboxClear, | ||
| 416 | ComboboxStatus, | ||
| 417 | ComboboxRow, | ||
| 418 | ComboboxCollection, | ||
| 419 | ComboboxChips, | ||
| 420 | ComboboxChip, | ||
| 421 | useComboboxFilter, | ||
| 422 | }; | ||
apps/renderer/src/components/ui/command.tsx created+262| ... | @@ -0,0 +1,262 @@ | ||
| 1 | import { Dialog as CommandDialogPrimitive } from "@base-ui/react/dialog"; | ||
| 2 | import { SearchIcon } from "lucide-react"; | ||
| 3 | import type * as React from "react"; | ||
| 4 | import { | ||
| 5 | Autocomplete, | ||
| 6 | AutocompleteCollection, | ||
| 7 | AutocompleteEmpty, | ||
| 8 | AutocompleteGroup, | ||
| 9 | AutocompleteGroupLabel, | ||
| 10 | AutocompleteInput, | ||
| 11 | AutocompleteItem, | ||
| 12 | AutocompleteList, | ||
| 13 | AutocompleteSeparator, | ||
| 14 | } from "#/components/ui/autocomplete"; | ||
| 15 | import { cn } from "#/lib/utils"; | ||
| 16 | |||
| 17 | const CommandDialog = CommandDialogPrimitive.Root; | ||
| 18 | |||
| 19 | const CommandDialogPortal = CommandDialogPrimitive.Portal; | ||
| 20 | |||
| 21 | const CommandCreateHandle = CommandDialogPrimitive.createHandle; | ||
| 22 | |||
| 23 | function CommandDialogTrigger(props: CommandDialogPrimitive.Trigger.Props) { | ||
| 24 | return ( | ||
| 25 | <CommandDialogPrimitive.Trigger | ||
| 26 | data-slot="command-dialog-trigger" | ||
| 27 | {...props} | ||
| 28 | /> | ||
| 29 | ); | ||
| 30 | } | ||
| 31 | |||
| 32 | function CommandDialogBackdrop({ | ||
| 33 | className, | ||
| 34 | ...props | ||
| 35 | }: CommandDialogPrimitive.Backdrop.Props) { | ||
| 36 | return ( | ||
| 37 | <CommandDialogPrimitive.Backdrop | ||
| 38 | className={cn( | ||
| 39 | "fixed inset-0 z-50 bg-black/32 backdrop-blur-sm transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0", | ||
| 40 | className, | ||
| 41 | )} | ||
| 42 | data-slot="command-dialog-backdrop" | ||
| 43 | {...props} | ||
| 44 | /> | ||
| 45 | ); | ||
| 46 | } | ||
| 47 | |||
| 48 | function CommandDialogViewport({ | ||
| 49 | className, | ||
| 50 | ...props | ||
| 51 | }: CommandDialogPrimitive.Viewport.Props) { | ||
| 52 | return ( | ||
| 53 | <CommandDialogPrimitive.Viewport | ||
| 54 | className={cn( | ||
| 55 | "fixed inset-0 z-50 flex flex-col items-center px-4 py-[max(--spacing(4),4vh)] sm:py-[10vh]", | ||
| 56 | className, | ||
| 57 | )} | ||
| 58 | data-slot="command-dialog-viewport" | ||
| 59 | {...props} | ||
| 60 | /> | ||
| 61 | ); | ||
| 62 | } | ||
| 63 | |||
| 64 | function CommandDialogPopup({ | ||
| 65 | className, | ||
| 66 | children, | ||
| 67 | ...props | ||
| 68 | }: CommandDialogPrimitive.Popup.Props) { | ||
| 69 | return ( | ||
| 70 | <CommandDialogPortal> | ||
| 71 | <CommandDialogBackdrop /> | ||
| 72 | <CommandDialogViewport> | ||
| 73 | <CommandDialogPrimitive.Popup | ||
| 74 | className={cn( | ||
| 75 | "-translate-y-[calc(1.25rem*var(--nested-dialogs))] relative row-start-2 flex max-h-105 min-h-0 w-full min-w-0 max-w-xl scale-[calc(1-0.1*var(--nested-dialogs))] flex-col rounded-2xl border bg-popover not-dark:bg-clip-padding text-popover-foreground opacity-[calc(1-0.1*var(--nested-dialogs))] shadow-lg/5 outline-none transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:bg-muted/72 before:shadow-[0_1px_--theme(--color-black/4%)] data-nested:data-ending-style:translate-y-8 data-nested:data-starting-style:translate-y-8 data-nested-dialog-open:origin-top data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0 **:data-[slot=scroll-area-viewport]:data-has-overflow-y:pe-1 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", | ||
| 76 | className, | ||
| 77 | )} | ||
| 78 | data-slot="command-dialog-popup" | ||
| 79 | {...props} | ||
| 80 | > | ||
| 81 | {children} | ||
| 82 | </CommandDialogPrimitive.Popup> | ||
| 83 | </CommandDialogViewport> | ||
| 84 | </CommandDialogPortal> | ||
| 85 | ); | ||
| 86 | } | ||
| 87 | |||
| 88 | function Command({ | ||
| 89 | autoHighlight = "always", | ||
| 90 | keepHighlight = true, | ||
| 91 | ...props | ||
| 92 | }: React.ComponentProps<typeof Autocomplete>) { | ||
| 93 | return ( | ||
| 94 | <Autocomplete | ||
| 95 | autoHighlight={autoHighlight} | ||
| 96 | inline | ||
| 97 | keepHighlight={keepHighlight} | ||
| 98 | open | ||
| 99 | {...props} | ||
| 100 | /> | ||
| 101 | ); | ||
| 102 | } | ||
| 103 | |||
| 104 | function CommandInput({ | ||
| 105 | className, | ||
| 106 | placeholder = undefined, | ||
| 107 | ...props | ||
| 108 | }: React.ComponentProps<typeof AutocompleteInput>) { | ||
| 109 | return ( | ||
| 110 | <div className="px-2.5 py-1.5"> | ||
| 111 | <AutocompleteInput | ||
| 112 | autoFocus | ||
| 113 | className={cn( | ||
| 114 | "border-transparent! bg-transparent! shadow-none before:hidden has-focus-visible:ring-0", | ||
| 115 | className, | ||
| 116 | )} | ||
| 117 | placeholder={placeholder} | ||
| 118 | size="lg" | ||
| 119 | startAddon={<SearchIcon />} | ||
| 120 | {...props} | ||
| 121 | /> | ||
| 122 | </div> | ||
| 123 | ); | ||
| 124 | } | ||
| 125 | |||
| 126 | function CommandList({ | ||
| 127 | className, | ||
| 128 | ...props | ||
| 129 | }: React.ComponentProps<typeof AutocompleteList>) { | ||
| 130 | return ( | ||
| 131 | <AutocompleteList | ||
| 132 | className={cn("not-empty:scroll-py-2 not-empty:p-2", className)} | ||
| 133 | data-slot="command-list" | ||
| 134 | {...props} | ||
| 135 | /> | ||
| 136 | ); | ||
| 137 | } | ||
| 138 | |||
| 139 | function CommandEmpty({ | ||
| 140 | className, | ||
| 141 | ...props | ||
| 142 | }: React.ComponentProps<typeof AutocompleteEmpty>) { | ||
| 143 | return ( | ||
| 144 | <AutocompleteEmpty | ||
| 145 | className={cn("not-empty:py-6", className)} | ||
| 146 | data-slot="command-empty" | ||
| 147 | {...props} | ||
| 148 | /> | ||
| 149 | ); | ||
| 150 | } | ||
| 151 | |||
| 152 | function CommandPanel({ className, ...props }: React.ComponentProps<"div">) { | ||
| 153 | return ( | ||
| 154 | <div | ||
| 155 | className="-mx-px not-has-[+[data-slot=command-footer]]:-mb-px relative min-h-0 rounded-t-xl not-has-[+[data-slot=command-footer]]:rounded-b-2xl border border-b-0 bg-popover bg-clip-padding shadow-xs/5 [clip-path:inset(0_1px)] not-has-[+[data-slot=command-footer]]:[clip-path:inset(0_1px_1px_1px_round_0_0_calc(var(--radius-2xl)-1px)_calc(var(--radius-2xl)-1px))] before:pointer-events-none before:absolute before:inset-0 before:rounded-t-[calc(var(--radius-xl)-1px)] **:data-[slot=scroll-area-scrollbar]:mt-2" | ||
| 156 | {...props} | ||
| 157 | /> | ||
| 158 | ); | ||
| 159 | } | ||
| 160 | |||
| 161 | function CommandGroup({ | ||
| 162 | className, | ||
| 163 | ...props | ||
| 164 | }: React.ComponentProps<typeof AutocompleteGroup>) { | ||
| 165 | return ( | ||
| 166 | <AutocompleteGroup | ||
| 167 | className={className} | ||
| 168 | data-slot="command-group" | ||
| 169 | {...props} | ||
| 170 | /> | ||
| 171 | ); | ||
| 172 | } | ||
| 173 | |||
| 174 | function CommandGroupLabel({ | ||
| 175 | className, | ||
| 176 | ...props | ||
| 177 | }: React.ComponentProps<typeof AutocompleteGroupLabel>) { | ||
| 178 | return ( | ||
| 179 | <AutocompleteGroupLabel | ||
| 180 | className={className} | ||
| 181 | data-slot="command-group-label" | ||
| 182 | {...props} | ||
| 183 | /> | ||
| 184 | ); | ||
| 185 | } | ||
| 186 | |||
| 187 | function CommandCollection({ | ||
| 188 | ...props | ||
| 189 | }: React.ComponentProps<typeof AutocompleteCollection>) { | ||
| 190 | return <AutocompleteCollection data-slot="command-collection" {...props} />; | ||
| 191 | } | ||
| 192 | |||
| 193 | function CommandItem({ | ||
| 194 | className, | ||
| 195 | ...props | ||
| 196 | }: React.ComponentProps<typeof AutocompleteItem>) { | ||
| 197 | return ( | ||
| 198 | <AutocompleteItem | ||
| 199 | className={cn("py-1.5", className)} | ||
| 200 | data-slot="command-item" | ||
| 201 | {...props} | ||
| 202 | /> | ||
| 203 | ); | ||
| 204 | } | ||
| 205 | |||
| 206 | function CommandSeparator({ | ||
| 207 | className, | ||
| 208 | ...props | ||
| 209 | }: React.ComponentProps<typeof AutocompleteSeparator>) { | ||
| 210 | return ( | ||
| 211 | <AutocompleteSeparator | ||
| 212 | className={cn("my-2", className)} | ||
| 213 | data-slot="command-separator" | ||
| 214 | {...props} | ||
| 215 | /> | ||
| 216 | ); | ||
| 217 | } | ||
| 218 | |||
| 219 | function CommandShortcut({ className, ...props }: React.ComponentProps<"kbd">) { | ||
| 220 | return ( | ||
| 221 | <kbd | ||
| 222 | className={cn( | ||
| 223 | "ms-auto font-medium font-sans text-muted-foreground/72 text-xs tracking-widest", | ||
| 224 | className, | ||
| 225 | )} | ||
| 226 | data-slot="command-shortcut" | ||
| 227 | {...props} | ||
| 228 | /> | ||
| 229 | ); | ||
| 230 | } | ||
| 231 | |||
| 232 | function CommandFooter({ className, ...props }: React.ComponentProps<"div">) { | ||
| 233 | return ( | ||
| 234 | <div | ||
| 235 | className={cn( | ||
| 236 | "flex items-center justify-between gap-2 rounded-b-[calc(var(--radius-2xl)-1px)] border-t px-5 py-3 text-muted-foreground text-xs", | ||
| 237 | className, | ||
| 238 | )} | ||
| 239 | data-slot="command-footer" | ||
| 240 | {...props} | ||
| 241 | /> | ||
| 242 | ); | ||
| 243 | } | ||
| 244 | |||
| 245 | export { | ||
| 246 | CommandCreateHandle, | ||
| 247 | Command, | ||
| 248 | CommandCollection, | ||
| 249 | CommandDialog, | ||
| 250 | CommandDialogPopup, | ||
| 251 | CommandDialogTrigger, | ||
| 252 | CommandEmpty, | ||
| 253 | CommandFooter, | ||
| 254 | CommandGroup, | ||
| 255 | CommandGroupLabel, | ||
| 256 | CommandInput, | ||
| 257 | CommandItem, | ||
| 258 | CommandList, | ||
| 259 | CommandPanel, | ||
| 260 | CommandSeparator, | ||
| 261 | CommandShortcut, | ||
| 262 | }; | ||
apps/renderer/src/components/ui/dialog.tsx created+194| ... | @@ -0,0 +1,194 @@ | ||
| 1 | import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"; | ||
| 2 | import { XIcon } from "lucide-react"; | ||
| 3 | import { Button } from "#/components/ui/button"; | ||
| 4 | import { ScrollArea } from "#/components/ui/scroll-area"; | ||
| 5 | import { cn } from "#/lib/utils"; | ||
| 6 | |||
| 7 | const DialogCreateHandle = DialogPrimitive.createHandle; | ||
| 8 | |||
| 9 | const Dialog = DialogPrimitive.Root; | ||
| 10 | |||
| 11 | const DialogPortal = DialogPrimitive.Portal; | ||
| 12 | |||
| 13 | function DialogTrigger(props: DialogPrimitive.Trigger.Props) { | ||
| 14 | return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />; | ||
| 15 | } | ||
| 16 | |||
| 17 | function DialogClose(props: DialogPrimitive.Close.Props) { | ||
| 18 | return <DialogPrimitive.Close data-slot="dialog-close" {...props} />; | ||
| 19 | } | ||
| 20 | |||
| 21 | function DialogBackdrop({ | ||
| 22 | className, | ||
| 23 | ...props | ||
| 24 | }: DialogPrimitive.Backdrop.Props) { | ||
| 25 | return ( | ||
| 26 | <DialogPrimitive.Backdrop | ||
| 27 | className={cn( | ||
| 28 | "fixed inset-0 z-50 bg-black/32 backdrop-blur-sm transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0", | ||
| 29 | className, | ||
| 30 | )} | ||
| 31 | data-slot="dialog-backdrop" | ||
| 32 | {...props} | ||
| 33 | /> | ||
| 34 | ); | ||
| 35 | } | ||
| 36 | |||
| 37 | function DialogViewport({ | ||
| 38 | className, | ||
| 39 | ...props | ||
| 40 | }: DialogPrimitive.Viewport.Props) { | ||
| 41 | return ( | ||
| 42 | <DialogPrimitive.Viewport | ||
| 43 | className={cn( | ||
| 44 | "fixed inset-0 z-50 grid grid-rows-[1fr_auto_3fr] justify-items-center p-4", | ||
| 45 | className, | ||
| 46 | )} | ||
| 47 | data-slot="dialog-viewport" | ||
| 48 | {...props} | ||
| 49 | /> | ||
| 50 | ); | ||
| 51 | } | ||
| 52 | |||
| 53 | function DialogPopup({ | ||
| 54 | className, | ||
| 55 | children, | ||
| 56 | showCloseButton = true, | ||
| 57 | bottomStickOnMobile = true, | ||
| 58 | ...props | ||
| 59 | }: DialogPrimitive.Popup.Props & { | ||
| 60 | showCloseButton?: boolean; | ||
| 61 | bottomStickOnMobile?: boolean; | ||
| 62 | }) { | ||
| 63 | return ( | ||
| 64 | <DialogPortal> | ||
| 65 | <DialogBackdrop /> | ||
| 66 | <DialogViewport | ||
| 67 | className={cn( | ||
| 68 | bottomStickOnMobile && | ||
| 69 | "max-sm:grid-rows-[1fr_auto] max-sm:p-0 max-sm:pt-12", | ||
| 70 | )} | ||
| 71 | > | ||
| 72 | <DialogPrimitive.Popup | ||
| 73 | className={cn( | ||
| 74 | "-translate-y-[calc(1.25rem*var(--nested-dialogs))] relative row-start-2 flex max-h-full min-h-0 w-full min-w-0 max-w-lg scale-[calc(1-0.1*var(--nested-dialogs))] flex-col rounded-2xl border bg-popover not-dark:bg-clip-padding text-popover-foreground opacity-[calc(1-0.1*var(--nested-dialogs))] shadow-lg/5 transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] data-nested:data-ending-style:translate-y-8 data-nested:data-starting-style:translate-y-8 data-nested-dialog-open:origin-top data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", | ||
| 75 | bottomStickOnMobile && | ||
| 76 | "max-sm:max-w-none max-sm:rounded-none max-sm:border-x-0 max-sm:border-t max-sm:border-b-0 max-sm:opacity-[calc(1-min(var(--nested-dialogs),1))] max-sm:data-ending-style:translate-y-4 max-sm:data-starting-style:translate-y-4 max-sm:before:hidden max-sm:before:rounded-none", | ||
| 77 | className, | ||
| 78 | )} | ||
| 79 | data-slot="dialog-popup" | ||
| 80 | {...props} | ||
| 81 | > | ||
| 82 | {children} | ||
| 83 | {showCloseButton && ( | ||
| 84 | <DialogPrimitive.Close | ||
| 85 | aria-label="Close" | ||
| 86 | className="absolute end-2 top-2" | ||
| 87 | render={<Button size="icon" variant="ghost" />} | ||
| 88 | > | ||
| 89 | <XIcon /> | ||
| 90 | </DialogPrimitive.Close> | ||
| 91 | )} | ||
| 92 | </DialogPrimitive.Popup> | ||
| 93 | </DialogViewport> | ||
| 94 | </DialogPortal> | ||
| 95 | ); | ||
| 96 | } | ||
| 97 | |||
| 98 | function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { | ||
| 99 | return ( | ||
| 100 | <div | ||
| 101 | className={cn( | ||
| 102 | "flex flex-col gap-2 p-6 in-[[data-slot=dialog-popup]:has([data-slot=dialog-panel])]:pb-3 max-sm:pb-4", | ||
| 103 | className, | ||
| 104 | )} | ||
| 105 | data-slot="dialog-header" | ||
| 106 | {...props} | ||
| 107 | /> | ||
| 108 | ); | ||
| 109 | } | ||
| 110 | |||
| 111 | function DialogFooter({ | ||
| 112 | className, | ||
| 113 | variant = "default", | ||
| 114 | ...props | ||
| 115 | }: React.ComponentProps<"div"> & { | ||
| 116 | variant?: "default" | "bare"; | ||
| 117 | }) { | ||
| 118 | return ( | ||
| 119 | <div | ||
| 120 | className={cn( | ||
| 121 | "flex flex-col-reverse gap-2 px-6 sm:flex-row sm:justify-end sm:rounded-b-[calc(var(--radius-2xl)-1px)]", | ||
| 122 | variant === "default" && "border-t bg-muted/72 py-4", | ||
| 123 | variant === "bare" && | ||
| 124 | "in-[[data-slot=dialog-popup]:has([data-slot=dialog-panel])]:pt-3 pt-4 pb-6", | ||
| 125 | className, | ||
| 126 | )} | ||
| 127 | data-slot="dialog-footer" | ||
| 128 | {...props} | ||
| 129 | /> | ||
| 130 | ); | ||
| 131 | } | ||
| 132 | |||
| 133 | function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) { | ||
| 134 | return ( | ||
| 135 | <DialogPrimitive.Title | ||
| 136 | className={cn( | ||
| 137 | "font-heading font-semibold text-xl leading-none", | ||
| 138 | className, | ||
| 139 | )} | ||
| 140 | data-slot="dialog-title" | ||
| 141 | {...props} | ||
| 142 | /> | ||
| 143 | ); | ||
| 144 | } | ||
| 145 | |||
| 146 | function DialogDescription({ | ||
| 147 | className, | ||
| 148 | ...props | ||
| 149 | }: DialogPrimitive.Description.Props) { | ||
| 150 | return ( | ||
| 151 | <DialogPrimitive.Description | ||
| 152 | className={cn("text-muted-foreground text-sm", className)} | ||
| 153 | data-slot="dialog-description" | ||
| 154 | {...props} | ||
| 155 | /> | ||
| 156 | ); | ||
| 157 | } | ||
| 158 | |||
| 159 | function DialogPanel({ | ||
| 160 | className, | ||
| 161 | scrollFade = true, | ||
| 162 | ...props | ||
| 163 | }: React.ComponentProps<"div"> & { scrollFade?: boolean }) { | ||
| 164 | return ( | ||
| 165 | <ScrollArea scrollFade={scrollFade}> | ||
| 166 | <div | ||
| 167 | className={cn( | ||
| 168 | "p-6 in-[[data-slot=dialog-popup]:has([data-slot=dialog-header])]:pt-1 in-[[data-slot=dialog-popup]:has([data-slot=dialog-footer]:not(.border-t))]:pb-1", | ||
| 169 | className, | ||
| 170 | )} | ||
| 171 | data-slot="dialog-panel" | ||
| 172 | {...props} | ||
| 173 | /> | ||
| 174 | </ScrollArea> | ||
| 175 | ); | ||
| 176 | } | ||
| 177 | |||
| 178 | export { | ||
| 179 | DialogCreateHandle, | ||
| 180 | Dialog, | ||
| 181 | DialogTrigger, | ||
| 182 | DialogPortal, | ||
| 183 | DialogClose, | ||
| 184 | DialogBackdrop, | ||
| 185 | DialogBackdrop as DialogOverlay, | ||
| 186 | DialogPopup, | ||
| 187 | DialogPopup as DialogContent, | ||
| 188 | DialogHeader, | ||
| 189 | DialogFooter, | ||
| 190 | DialogTitle, | ||
| 191 | DialogDescription, | ||
| 192 | DialogPanel, | ||
| 193 | DialogViewport, | ||
| 194 | }; | ||
apps/renderer/src/components/ui/empty.tsx created+127| ... | @@ -0,0 +1,127 @@ | ||
| 1 | import { type VariantProps, cva } from "class-variance-authority"; | ||
| 2 | |||
| 3 | import { cn } from "#/lib/utils"; | ||
| 4 | |||
| 5 | function Empty({ className, ...props }: React.ComponentProps<"div">) { | ||
| 6 | return ( | ||
| 7 | <div | ||
| 8 | className={cn( | ||
| 9 | "flex min-w-0 flex-1 flex-col items-center justify-center gap-6 text-balance p-6 text-center md:p-12", | ||
| 10 | className, | ||
| 11 | )} | ||
| 12 | data-slot="empty" | ||
| 13 | {...props} | ||
| 14 | /> | ||
| 15 | ); | ||
| 16 | } | ||
| 17 | |||
| 18 | function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) { | ||
| 19 | return ( | ||
| 20 | <div | ||
| 21 | className={cn( | ||
| 22 | "flex max-w-sm flex-col items-center text-center", | ||
| 23 | className, | ||
| 24 | )} | ||
| 25 | data-slot="empty-header" | ||
| 26 | {...props} | ||
| 27 | /> | ||
| 28 | ); | ||
| 29 | } | ||
| 30 | |||
| 31 | const emptyMediaVariants = cva( | ||
| 32 | "flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0", | ||
| 33 | { | ||
| 34 | defaultVariants: { | ||
| 35 | variant: "default", | ||
| 36 | }, | ||
| 37 | variants: { | ||
| 38 | variant: { | ||
| 39 | default: "bg-transparent", | ||
| 40 | icon: "relative flex size-9 shrink-0 items-center justify-center rounded-md border bg-card not-dark:bg-clip-padding text-foreground shadow-sm/5 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-md)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)] [&_svg:not([class*='size-'])]:size-4.5", | ||
| 41 | }, | ||
| 42 | }, | ||
| 43 | }, | ||
| 44 | ); | ||
| 45 | |||
| 46 | function EmptyMedia({ | ||
| 47 | className, | ||
| 48 | variant = "default", | ||
| 49 | ...props | ||
| 50 | }: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) { | ||
| 51 | return ( | ||
| 52 | <div | ||
| 53 | className={cn("relative mb-6", className)} | ||
| 54 | data-slot="empty-media" | ||
| 55 | data-variant={variant} | ||
| 56 | {...props} | ||
| 57 | > | ||
| 58 | {variant === "icon" && ( | ||
| 59 | <> | ||
| 60 | <div | ||
| 61 | aria-hidden="true" | ||
| 62 | className={cn( | ||
| 63 | emptyMediaVariants({ className, variant }), | ||
| 64 | "-translate-x-0.5 -rotate-10 pointer-events-none absolute bottom-px origin-bottom-left scale-84 shadow-none", | ||
| 65 | )} | ||
| 66 | /> | ||
| 67 | <div | ||
| 68 | aria-hidden="true" | ||
| 69 | className={cn( | ||
| 70 | emptyMediaVariants({ className, variant }), | ||
| 71 | "pointer-events-none absolute bottom-px origin-bottom-right translate-x-0.5 rotate-10 scale-84 shadow-none", | ||
| 72 | )} | ||
| 73 | /> | ||
| 74 | </> | ||
| 75 | )} | ||
| 76 | <div | ||
| 77 | className={cn(emptyMediaVariants({ className, variant }))} | ||
| 78 | {...props} | ||
| 79 | /> | ||
| 80 | </div> | ||
| 81 | ); | ||
| 82 | } | ||
| 83 | |||
| 84 | function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) { | ||
| 85 | return ( | ||
| 86 | <div | ||
| 87 | className={cn("font-heading font-semibold text-xl", className)} | ||
| 88 | data-slot="empty-title" | ||
| 89 | {...props} | ||
| 90 | /> | ||
| 91 | ); | ||
| 92 | } | ||
| 93 | |||
| 94 | function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) { | ||
| 95 | return ( | ||
| 96 | <div | ||
| 97 | className={cn( | ||
| 98 | "text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1", | ||
| 99 | className, | ||
| 100 | )} | ||
| 101 | data-slot="empty-description" | ||
| 102 | {...props} | ||
| 103 | /> | ||
| 104 | ); | ||
| 105 | } | ||
| 106 | |||
| 107 | function EmptyContent({ className, ...props }: React.ComponentProps<"div">) { | ||
| 108 | return ( | ||
| 109 | <div | ||
| 110 | className={cn( | ||
| 111 | "flex w-full min-w-0 max-w-sm flex-col items-center gap-4 text-balance text-sm", | ||
| 112 | className, | ||
| 113 | )} | ||
| 114 | data-slot="empty-content" | ||
| 115 | {...props} | ||
| 116 | /> | ||
| 117 | ); | ||
| 118 | } | ||
| 119 | |||
| 120 | export { | ||
| 121 | Empty, | ||
| 122 | EmptyHeader, | ||
| 123 | EmptyTitle, | ||
| 124 | EmptyDescription, | ||
| 125 | EmptyContent, | ||
| 126 | EmptyMedia, | ||
| 127 | }; | ||
apps/renderer/src/components/ui/field.tsx created+72| ... | @@ -0,0 +1,72 @@ | ||
| 1 | import { Field as FieldPrimitive } from "@base-ui/react/field"; | ||
| 2 | |||
| 3 | import { cn } from "#/lib/utils"; | ||
| 4 | |||
| 5 | function Field({ className, ...props }: FieldPrimitive.Root.Props) { | ||
| 6 | return ( | ||
| 7 | <FieldPrimitive.Root | ||
| 8 | className={cn("flex flex-col items-start gap-2", className)} | ||
| 9 | data-slot="field" | ||
| 10 | {...props} | ||
| 11 | /> | ||
| 12 | ); | ||
| 13 | } | ||
| 14 | |||
| 15 | function FieldLabel({ className, ...props }: FieldPrimitive.Label.Props) { | ||
| 16 | return ( | ||
| 17 | <FieldPrimitive.Label | ||
| 18 | className={cn( | ||
| 19 | "inline-flex items-center gap-2 font-medium text-base/4.5 text-foreground sm:text-sm/4", | ||
| 20 | className, | ||
| 21 | )} | ||
| 22 | data-slot="field-label" | ||
| 23 | {...props} | ||
| 24 | /> | ||
| 25 | ); | ||
| 26 | } | ||
| 27 | |||
| 28 | function FieldItem({ className, ...props }: FieldPrimitive.Item.Props) { | ||
| 29 | return ( | ||
| 30 | <FieldPrimitive.Item | ||
| 31 | className={cn("flex", className)} | ||
| 32 | data-slot="field-item" | ||
| 33 | {...props} | ||
| 34 | /> | ||
| 35 | ); | ||
| 36 | } | ||
| 37 | |||
| 38 | function FieldDescription({ | ||
| 39 | className, | ||
| 40 | ...props | ||
| 41 | }: FieldPrimitive.Description.Props) { | ||
| 42 | return ( | ||
| 43 | <FieldPrimitive.Description | ||
| 44 | className={cn("text-muted-foreground text-xs", className)} | ||
| 45 | data-slot="field-description" | ||
| 46 | {...props} | ||
| 47 | /> | ||
| 48 | ); | ||
| 49 | } | ||
| 50 | |||
| 51 | function FieldError({ className, ...props }: FieldPrimitive.Error.Props) { | ||
| 52 | return ( | ||
| 53 | <FieldPrimitive.Error | ||
| 54 | className={cn("text-destructive-foreground text-xs", className)} | ||
| 55 | data-slot="field-error" | ||
| 56 | {...props} | ||
| 57 | /> | ||
| 58 | ); | ||
| 59 | } | ||
| 60 | |||
| 61 | const FieldControl = FieldPrimitive.Control; | ||
| 62 | const FieldValidity = FieldPrimitive.Validity; | ||
| 63 | |||
| 64 | export { | ||
| 65 | Field, | ||
| 66 | FieldLabel, | ||
| 67 | FieldDescription, | ||
| 68 | FieldError, | ||
| 69 | FieldControl, | ||
| 70 | FieldItem, | ||
| 71 | FieldValidity, | ||
| 72 | }; | ||
apps/renderer/src/components/ui/fieldset.tsx created+27| ... | @@ -0,0 +1,27 @@ | ||
| 1 | import { Fieldset as FieldsetPrimitive } from "@base-ui/react/fieldset"; | ||
| 2 | |||
| 3 | import { cn } from "#/lib/utils"; | ||
| 4 | |||
| 5 | function Fieldset({ className, ...props }: FieldsetPrimitive.Root.Props) { | ||
| 6 | return ( | ||
| 7 | <FieldsetPrimitive.Root | ||
| 8 | className={cn("flex w-full max-w-64 flex-col gap-6", className)} | ||
| 9 | data-slot="fieldset" | ||
| 10 | {...props} | ||
| 11 | /> | ||
| 12 | ); | ||
| 13 | } | ||
| 14 | function FieldsetLegend({ | ||
| 15 | className, | ||
| 16 | ...props | ||
| 17 | }: FieldsetPrimitive.Legend.Props) { | ||
| 18 | return ( | ||
| 19 | <FieldsetPrimitive.Legend | ||
| 20 | className={cn("font-semibold text-foreground", className)} | ||
| 21 | data-slot="fieldset-legend" | ||
| 22 | {...props} | ||
| 23 | /> | ||
| 24 | ); | ||
| 25 | } | ||
| 26 | |||
| 27 | export { Fieldset, FieldsetLegend }; | ||
apps/renderer/src/components/ui/form.tsx created+15| ... | @@ -0,0 +1,15 @@ | ||
| 1 | import { Form as FormPrimitive } from "@base-ui/react/form"; | ||
| 2 | |||
| 3 | import { cn } from "#/lib/utils"; | ||
| 4 | |||
| 5 | function Form({ className, ...props }: FormPrimitive.Props) { | ||
| 6 | return ( | ||
| 7 | <FormPrimitive | ||
| 8 | className={cn("flex w-full flex-col gap-4", className)} | ||
| 9 | data-slot="form" | ||
| 10 | {...props} | ||
| 11 | /> | ||
| 12 | ); | ||
| 13 | } | ||
| 14 | |||
| 15 | export { Form }; | ||
apps/renderer/src/components/ui/input-group.tsx created+99| ... | @@ -0,0 +1,99 @@ | ||
| 1 | import { type VariantProps, cva } from "class-variance-authority"; | ||
| 2 | import type * as React from "react"; | ||
| 3 | |||
| 4 | import { Input, type InputProps } from "#/components/ui/input"; | ||
| 5 | import { Textarea, type TextareaProps } from "#/components/ui/textarea"; | ||
| 6 | import { cn } from "#/lib/utils"; | ||
| 7 | |||
| 8 | function InputGroup({ className, ...props }: React.ComponentProps<"div">) { | ||
| 9 | return ( | ||
| 10 | <div | ||
| 11 | className={cn( | ||
| 12 | "relative inline-flex w-full min-w-0 items-center rounded-lg border border-input bg-background not-dark:bg-clip-padding text-base text-foreground shadow-xs/5 ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-[input:disabled,textarea:disabled]:not-has-[input:focus-visible,textarea:focus-visible]:not-has-[input[aria-invalid],textarea[aria-invalid]]:before:shadow-[0_1px_--theme(--color-black/4%)] has-[input:focus-visible,textarea:focus-visible]:has-[input[aria-invalid],textarea[aria-invalid]]:border-destructive/64 has-[input:focus-visible,textarea:focus-visible]:has-[input[aria-invalid],textarea[aria-invalid]]:ring-destructive/16 has-[textarea]:h-auto has-data-[align=block-end]:h-auto has-data-[align=block-start]:h-auto has-data-[align=block-end]:flex-col has-data-[align=block-start]:flex-col has-[input:focus-visible,textarea:focus-visible]:border-ring has-[input[aria-invalid],textarea[aria-invalid]]:border-destructive/36 has-autofill:bg-foreground/4 has-[input:disabled,textarea:disabled]:opacity-64 has-[input:disabled,textarea:disabled,input:focus-visible,textarea:focus-visible,input[aria-invalid],textarea[aria-invalid]]:shadow-none has-[input:focus-visible,textarea:focus-visible]:ring-[3px] sm:text-sm dark:bg-input/32 dark:has-autofill:bg-foreground/8 dark:has-[input[aria-invalid],textarea[aria-invalid]]:ring-destructive/24 dark:not-has-[input:disabled,textarea:disabled]:not-has-[input:focus-visible,textarea:focus-visible]:not-has-[input[aria-invalid],textarea[aria-invalid]]:before:shadow-[0_-1px_--theme(--color-white/6%)] has-data-[align=inline-start]:**:[[data-size=sm]_input]:ps-1.5 has-data-[align=inline-end]:**:[[data-size=sm]_input]:pe-1.5 *:[[data-slot=input-control],[data-slot=textarea-control]]:contents *:[[data-slot=input-control],[data-slot=textarea-control]]:before:hidden has-[[data-align=block-start],[data-align=block-end]]:**:[input]:h-auto has-data-[align=inline-start]:**:[input]:ps-2 has-data-[align=inline-end]:**:[input]:pe-2 has-data-[align=block-end]:**:[input]:pt-1.5 has-data-[align=block-start]:**:[input]:pb-1.5 **:[textarea]:min-h-20.5 **:[textarea]:resize-none **:[textarea]:py-[calc(--spacing(3)-1px)] **:[textarea]:max-sm:min-h-23.5 **:[textarea_button]:rounded-[calc(var(--radius-md)-1px)]", | ||
| 13 | className, | ||
| 14 | )} | ||
| 15 | data-slot="input-group" | ||
| 16 | role="group" | ||
| 17 | {...props} | ||
| 18 | /> | ||
| 19 | ); | ||
| 20 | } | ||
| 21 | |||
| 22 | const inputGroupAddonVariants = cva( | ||
| 23 | "[&_svg]:-mx-0.5 flex h-auto cursor-text select-none items-center justify-center gap-2 leading-none [&>kbd]:rounded-[calc(var(--radius)-5px)] in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4.5 sm:in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4 not-has-[button]:**:[svg:not([class*='opacity-'])]:opacity-80", | ||
| 24 | { | ||
| 25 | defaultVariants: { | ||
| 26 | align: "inline-start", | ||
| 27 | }, | ||
| 28 | variants: { | ||
| 29 | align: { | ||
| 30 | "block-end": | ||
| 31 | "order-last w-full justify-start px-[calc(--spacing(3)-1px)] pb-[calc(--spacing(3)-1px)] [.border-t]:pt-[calc(--spacing(3)-1px)] [[data-size=sm]+&]:px-[calc(--spacing(2.5)-1px)]", | ||
| 32 | "block-start": | ||
| 33 | "order-first w-full justify-start px-[calc(--spacing(3)-1px)] pt-[calc(--spacing(3)-1px)] [.border-b]:pb-[calc(--spacing(3)-1px)] [[data-size=sm]+&]:px-[calc(--spacing(2.5)-1px)]", | ||
| 34 | "inline-end": | ||
| 35 | "has-[>:last-child[data-slot=badge]]:-me-1.5 has-[>button]:-me-2 order-last pe-[calc(--spacing(3)-1px)] has-[>kbd:last-child]:me-[-0.35rem] [[data-size=sm]+&]:pe-[calc(--spacing(2.5)-1px)]", | ||
| 36 | "inline-start": | ||
| 37 | "has-[>:last-child[data-slot=badge]]:-ms-1.5 has-[>button]:-ms-2 order-first ps-[calc(--spacing(3)-1px)] has-[>kbd:last-child]:ms-[-0.35rem] [[data-size=sm]+&]:ps-[calc(--spacing(2.5)-1px)]", | ||
| 38 | }, | ||
| 39 | }, | ||
| 40 | }, | ||
| 41 | ); | ||
| 42 | |||
| 43 | function InputGroupAddon({ | ||
| 44 | className, | ||
| 45 | align = "inline-start", | ||
| 46 | ...props | ||
| 47 | }: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) { | ||
| 48 | return ( | ||
| 49 | <div | ||
| 50 | className={cn(inputGroupAddonVariants({ align }), className)} | ||
| 51 | data-align={align} | ||
| 52 | data-slot="input-group-addon" | ||
| 53 | onMouseDown={(e) => { | ||
| 54 | const target = e.target as HTMLElement; | ||
| 55 | const isInteractive = target.closest( | ||
| 56 | "button, a, input, select, textarea, [role='button'], [role='combobox'], [role='listbox'], [data-slot='select-trigger']", | ||
| 57 | ); | ||
| 58 | if (isInteractive) return; | ||
| 59 | e.preventDefault(); | ||
| 60 | const parent = e.currentTarget.parentElement; | ||
| 61 | const input = parent?.querySelector< | ||
| 62 | HTMLInputElement | HTMLTextAreaElement | ||
| 63 | >("input, textarea"); | ||
| 64 | if (input && !parent?.querySelector("input:focus, textarea:focus")) { | ||
| 65 | input.focus(); | ||
| 66 | } | ||
| 67 | }} | ||
| 68 | {...props} | ||
| 69 | /> | ||
| 70 | ); | ||
| 71 | } | ||
| 72 | |||
| 73 | function InputGroupText({ className, ...props }: React.ComponentProps<"span">) { | ||
| 74 | return ( | ||
| 75 | <span | ||
| 76 | className={cn( | ||
| 77 | "[&_svg]:-mx-0.5 line-clamp-1 flex items-center gap-2 text-muted-foreground leading-none in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4.5 sm:in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none", | ||
| 78 | className, | ||
| 79 | )} | ||
| 80 | {...props} | ||
| 81 | /> | ||
| 82 | ); | ||
| 83 | } | ||
| 84 | |||
| 85 | function InputGroupInput({ className, ...props }: InputProps) { | ||
| 86 | return <Input className={className} unstyled {...props} />; | ||
| 87 | } | ||
| 88 | |||
| 89 | function InputGroupTextarea({ className, ...props }: TextareaProps) { | ||
| 90 | return <Textarea className={className} unstyled {...props} />; | ||
| 91 | } | ||
| 92 | |||
| 93 | export { | ||
| 94 | InputGroup, | ||
| 95 | InputGroupAddon, | ||
| 96 | InputGroupText, | ||
| 97 | InputGroupInput, | ||
| 98 | InputGroupTextarea, | ||
| 99 | }; | ||
apps/renderer/src/components/ui/input.tsx created+64| ... | @@ -0,0 +1,64 @@ | ||
| 1 | import { Input as InputPrimitive } from "@base-ui/react/input"; | ||
| 2 | import type * as React from "react"; | ||
| 3 | |||
| 4 | import { cn } from "#/lib/utils"; | ||
| 5 | |||
| 6 | type InputProps = Omit< | ||
| 7 | InputPrimitive.Props & React.RefAttributes<HTMLInputElement>, | ||
| 8 | "size" | ||
| 9 | > & { | ||
| 10 | size?: "sm" | "default" | "lg" | number; | ||
| 11 | unstyled?: boolean; | ||
| 12 | nativeInput?: boolean; | ||
| 13 | }; | ||
| 14 | |||
| 15 | function Input({ | ||
| 16 | className, | ||
| 17 | size = "default", | ||
| 18 | unstyled = false, | ||
| 19 | nativeInput = false, | ||
| 20 | ...props | ||
| 21 | }: InputProps) { | ||
| 22 | const inputClassName = cn( | ||
| 23 | "h-8.5 w-full min-w-0 rounded-[inherit] px-[calc(--spacing(3)-1px)] leading-8.5 outline-none placeholder:text-muted-foreground/72 sm:h-7.5 sm:leading-7.5 [transition:background-color_5000000s_ease-in-out_0s]", | ||
| 24 | size === "sm" && | ||
| 25 | "h-7.5 px-[calc(--spacing(2.5)-1px)] leading-7.5 sm:h-6.5 sm:leading-6.5", | ||
| 26 | size === "lg" && "h-9.5 leading-9.5 sm:h-8.5 sm:leading-8.5", | ||
| 27 | props.type === "search" && | ||
| 28 | "[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none [&::-webkit-search-results-button]:appearance-none [&::-webkit-search-results-decoration]:appearance-none", | ||
| 29 | props.type === "file" && | ||
| 30 | "text-muted-foreground file:me-3 file:bg-transparent file:font-medium file:text-foreground file:text-sm", | ||
| 31 | ); | ||
| 32 | |||
| 33 | return ( | ||
| 34 | <span | ||
| 35 | className={ | ||
| 36 | cn( | ||
| 37 | !unstyled && | ||
| 38 | "relative inline-flex w-full rounded-lg border border-input bg-background not-dark:bg-clip-padding text-base text-foreground shadow-xs/5 ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] has-focus-visible:has-aria-invalid:border-destructive/64 has-focus-visible:has-aria-invalid:ring-destructive/16 has-aria-invalid:border-destructive/36 has-focus-visible:border-ring has-autofill:bg-foreground/4 has-disabled:opacity-64 has-[:disabled,:focus-visible,[aria-invalid]]:shadow-none has-focus-visible:ring-[3px] sm:text-sm dark:bg-input/32 dark:has-autofill:bg-foreground/8 dark:has-aria-invalid:ring-destructive/24 dark:not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)]", | ||
| 39 | className, | ||
| 40 | ) || undefined | ||
| 41 | } | ||
| 42 | data-size={size} | ||
| 43 | data-slot="input-control" | ||
| 44 | > | ||
| 45 | {nativeInput ? ( | ||
| 46 | <input | ||
| 47 | className={inputClassName} | ||
| 48 | data-slot="input" | ||
| 49 | size={typeof size === "number" ? size : undefined} | ||
| 50 | {...props} | ||
| 51 | /> | ||
| 52 | ) : ( | ||
| 53 | <InputPrimitive | ||
| 54 | className={inputClassName} | ||
| 55 | data-slot="input" | ||
| 56 | size={typeof size === "number" ? size : undefined} | ||
| 57 | {...props} | ||
| 58 | /> | ||
| 59 | )} | ||
| 60 | </span> | ||
| 61 | ); | ||
| 62 | } | ||
| 63 | |||
| 64 | export { Input, type InputProps }; | ||
apps/renderer/src/components/ui/kbd.tsx created+28| ... | @@ -0,0 +1,28 @@ | ||
| 1 | import type * as React from "react"; | ||
| 2 | |||
| 3 | import { cn } from "#/lib/utils"; | ||
| 4 | |||
| 5 | function Kbd({ className, ...props }: React.ComponentProps<"kbd">) { | ||
| 6 | return ( | ||
| 7 | <kbd | ||
| 8 | className={cn( | ||
| 9 | "pointer-events-none inline-flex h-5 min-w-5 select-none items-center justify-center gap-1 rounded bg-muted px-1 font-medium font-sans text-muted-foreground text-xs [&_svg:not([class*='size-'])]:size-3", | ||
| 10 | className, | ||
| 11 | )} | ||
| 12 | data-slot="kbd" | ||
| 13 | {...props} | ||
| 14 | /> | ||
| 15 | ); | ||
| 16 | } | ||
| 17 | |||
| 18 | function KbdGroup({ className, ...props }: React.ComponentProps<"kbd">) { | ||
| 19 | return ( | ||
| 20 | <kbd | ||
| 21 | className={cn("inline-flex items-center gap-1", className)} | ||
| 22 | data-slot="kbd-group" | ||
| 23 | {...props} | ||
| 24 | /> | ||
| 25 | ); | ||
| 26 | } | ||
| 27 | |||
| 28 | export { Kbd, KbdGroup }; | ||
apps/renderer/src/components/ui/label.tsx created+26| ... | @@ -0,0 +1,26 @@ | ||
| 1 | import { mergeProps } from "@base-ui/react/merge-props"; | ||
| 2 | import { useRender } from "@base-ui/react/use-render"; | ||
| 3 | |||
| 4 | import { cn } from "#/lib/utils"; | ||
| 5 | |||
| 6 | function Label({ | ||
| 7 | className, | ||
| 8 | render, | ||
| 9 | ...props | ||
| 10 | }: useRender.ComponentProps<"label">) { | ||
| 11 | const defaultProps = { | ||
| 12 | className: cn( | ||
| 13 | "inline-flex items-center gap-2 text-base/4.5 sm:text-sm/4 font-medium text-foreground", | ||
| 14 | className, | ||
| 15 | ), | ||
| 16 | "data-slot": "label", | ||
| 17 | }; | ||
| 18 | |||
| 19 | return useRender({ | ||
| 20 | defaultTagName: "label", | ||
| 21 | props: mergeProps<"label">(defaultProps, props), | ||
| 22 | render, | ||
| 23 | }); | ||
| 24 | } | ||
| 25 | |||
| 26 | export { Label }; | ||
apps/renderer/src/components/ui/menu.tsx created+308| ... | @@ -0,0 +1,308 @@ | ||
| 1 | import { Menu as MenuPrimitive } from "@base-ui/react/menu"; | ||
| 2 | import { ChevronRightIcon } from "lucide-react"; | ||
| 3 | import type * as React from "react"; | ||
| 4 | import { cn } from "#/lib/utils"; | ||
| 5 | |||
| 6 | const MenuCreateHandle = MenuPrimitive.createHandle; | ||
| 7 | |||
| 8 | const Menu = MenuPrimitive.Root; | ||
| 9 | |||
| 10 | const MenuPortal = MenuPrimitive.Portal; | ||
| 11 | |||
| 12 | function MenuTrigger(props: MenuPrimitive.Trigger.Props) { | ||
| 13 | return <MenuPrimitive.Trigger data-slot="menu-trigger" {...props} />; | ||
| 14 | } | ||
| 15 | |||
| 16 | function MenuPopup({ | ||
| 17 | children, | ||
| 18 | className, | ||
| 19 | sideOffset = 4, | ||
| 20 | align = "center", | ||
| 21 | alignOffset, | ||
| 22 | side = "bottom", | ||
| 23 | ...props | ||
| 24 | }: MenuPrimitive.Popup.Props & { | ||
| 25 | align?: MenuPrimitive.Positioner.Props["align"]; | ||
| 26 | sideOffset?: MenuPrimitive.Positioner.Props["sideOffset"]; | ||
| 27 | alignOffset?: MenuPrimitive.Positioner.Props["alignOffset"]; | ||
| 28 | side?: MenuPrimitive.Positioner.Props["side"]; | ||
| 29 | }) { | ||
| 30 | return ( | ||
| 31 | <MenuPrimitive.Portal> | ||
| 32 | <MenuPrimitive.Positioner | ||
| 33 | align={align} | ||
| 34 | alignOffset={alignOffset} | ||
| 35 | className="z-50" | ||
| 36 | data-slot="menu-positioner" | ||
| 37 | side={side} | ||
| 38 | sideOffset={sideOffset} | ||
| 39 | > | ||
| 40 | <MenuPrimitive.Popup | ||
| 41 | className={cn( | ||
| 42 | "relative flex not-[class*='w-']:min-w-32 origin-(--transform-origin) rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 outline-none before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] focus:outline-none dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", | ||
| 43 | className, | ||
| 44 | )} | ||
| 45 | data-slot="menu-popup" | ||
| 46 | {...props} | ||
| 47 | > | ||
| 48 | <div className="max-h-(--available-height) w-full overflow-y-auto p-1"> | ||
| 49 | {children} | ||
| 50 | </div> | ||
| 51 | </MenuPrimitive.Popup> | ||
| 52 | </MenuPrimitive.Positioner> | ||
| 53 | </MenuPrimitive.Portal> | ||
| 54 | ); | ||
| 55 | } | ||
| 56 | |||
| 57 | function MenuGroup(props: MenuPrimitive.Group.Props) { | ||
| 58 | return <MenuPrimitive.Group data-slot="menu-group" {...props} />; | ||
| 59 | } | ||
| 60 | |||
| 61 | function MenuItem({ | ||
| 62 | className, | ||
| 63 | inset, | ||
| 64 | variant = "default", | ||
| 65 | ...props | ||
| 66 | }: MenuPrimitive.Item.Props & { | ||
| 67 | inset?: boolean; | ||
| 68 | variant?: "default" | "destructive"; | ||
| 69 | }) { | ||
| 70 | return ( | ||
| 71 | <MenuPrimitive.Item | ||
| 72 | className={cn( | ||
| 73 | "[&>svg]:-mx-0.5 flex min-h-8 cursor-default select-none items-center gap-2 rounded-sm px-2 py-1 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-inset:ps-8 data-[variant=destructive]:text-destructive-foreground data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&>svg:not([class*='opacity-'])]:opacity-80 [&>svg:not([class*='size-'])]:size-4.5 sm:[&>svg:not([class*='size-'])]:size-4 [&>svg]:pointer-events-none [&>svg]:shrink-0", | ||
| 74 | className, | ||
| 75 | )} | ||
| 76 | data-inset={inset} | ||
| 77 | data-slot="menu-item" | ||
| 78 | data-variant={variant} | ||
| 79 | {...props} | ||
| 80 | /> | ||
| 81 | ); | ||
| 82 | } | ||
| 83 | |||
| 84 | function MenuCheckboxItem({ | ||
| 85 | className, | ||
| 86 | children, | ||
| 87 | checked, | ||
| 88 | variant = "default", | ||
| 89 | ...props | ||
| 90 | }: MenuPrimitive.CheckboxItem.Props & { | ||
| 91 | variant?: "default" | "switch"; | ||
| 92 | }) { | ||
| 93 | return ( | ||
| 94 | <MenuPrimitive.CheckboxItem | ||
| 95 | checked={checked} | ||
| 96 | className={cn( | ||
| 97 | "grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default items-center gap-2 rounded-sm py-1 ps-2 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", | ||
| 98 | variant === "switch" | ||
| 99 | ? "grid-cols-[1fr_auto] gap-4 pe-1.5" | ||
| 100 | : "grid-cols-[1rem_1fr] pe-4", | ||
| 101 | className, | ||
| 102 | )} | ||
| 103 | data-slot="menu-checkbox-item" | ||
| 104 | {...props} | ||
| 105 | > | ||
| 106 | {variant === "switch" ? ( | ||
| 107 | <> | ||
| 108 | <span className="col-start-1">{children}</span> | ||
| 109 | <MenuPrimitive.CheckboxItemIndicator | ||
| 110 | className="inset-shadow-[0_1px_--theme(--color-black/4%)] inline-flex h-[calc(var(--thumb-size)+2px)] w-[calc(var(--thumb-size)*2-2px)] shrink-0 items-center rounded-full p-px outline-none transition-[background-color,box-shadow] duration-200 [--thumb-size:--spacing(4)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-checked:bg-primary data-unchecked:bg-input data-disabled:opacity-64 sm:[--thumb-size:--spacing(3)]" | ||
| 111 | keepMounted | ||
| 112 | > | ||
| 113 | <span className="pointer-events-none block aspect-square h-full in-[[data-slot=menu-checkbox-item][data-checked]]:origin-[var(--thumb-size)_50%] origin-left in-[[data-slot=menu-checkbox-item][data-checked]]:translate-x-[calc(var(--thumb-size)-4px)] in-[[data-slot=menu-checkbox-item]:active]:not-data-disabled:scale-x-110 in-[[data-slot=menu-checkbox-item]:active]:rounded-[var(--thumb-size)/calc(var(--thumb-size)*1.10)] rounded-(--thumb-size) bg-background shadow-sm/5 will-change-transform [transition:translate_.15s,border-radius_.15s,scale_.1s_.1s,transform-origin_.15s]" /> | ||
| 114 | </MenuPrimitive.CheckboxItemIndicator> | ||
| 115 | </> | ||
| 116 | ) : ( | ||
| 117 | <> | ||
| 118 | <MenuPrimitive.CheckboxItemIndicator className="col-start-1"> | ||
| 119 | <svg | ||
| 120 | fill="none" | ||
| 121 | height="24" | ||
| 122 | stroke="currentColor" | ||
| 123 | strokeLinecap="round" | ||
| 124 | strokeLinejoin="round" | ||
| 125 | strokeWidth="2" | ||
| 126 | viewBox="0 0 24 24" | ||
| 127 | width="24" | ||
| 128 | xmlns="http://www.w3.org/2000/svg" | ||
| 129 | > | ||
| 130 | <path d="M5.252 12.7 10.2 18.63 18.748 5.37" /> | ||
| 131 | </svg> | ||
| 132 | </MenuPrimitive.CheckboxItemIndicator> | ||
| 133 | <span className="col-start-2">{children}</span> | ||
| 134 | </> | ||
| 135 | )} | ||
| 136 | </MenuPrimitive.CheckboxItem> | ||
| 137 | ); | ||
| 138 | } | ||
| 139 | |||
| 140 | function MenuRadioGroup(props: MenuPrimitive.RadioGroup.Props) { | ||
| 141 | return <MenuPrimitive.RadioGroup data-slot="menu-radio-group" {...props} />; | ||
| 142 | } | ||
| 143 | |||
| 144 | function MenuRadioItem({ | ||
| 145 | className, | ||
| 146 | children, | ||
| 147 | ...props | ||
| 148 | }: MenuPrimitive.RadioItem.Props) { | ||
| 149 | return ( | ||
| 150 | <MenuPrimitive.RadioItem | ||
| 151 | className={cn( | ||
| 152 | "grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default grid-cols-[1rem_1fr] items-center gap-2 rounded-sm py-1 ps-2 pe-4 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", | ||
| 153 | className, | ||
| 154 | )} | ||
| 155 | data-slot="menu-radio-item" | ||
| 156 | {...props} | ||
| 157 | > | ||
| 158 | <MenuPrimitive.RadioItemIndicator className="col-start-1"> | ||
| 159 | <svg | ||
| 160 | fill="none" | ||
| 161 | height="24" | ||
| 162 | stroke="currentColor" | ||
| 163 | strokeLinecap="round" | ||
| 164 | strokeLinejoin="round" | ||
| 165 | strokeWidth="2" | ||
| 166 | viewBox="0 0 24 24" | ||
| 167 | width="24" | ||
| 168 | xmlns="http://www.w3.org/2000/svg" | ||
| 169 | > | ||
| 170 | <path d="M5.252 12.7 10.2 18.63 18.748 5.37" /> | ||
| 171 | </svg> | ||
| 172 | </MenuPrimitive.RadioItemIndicator> | ||
| 173 | <span className="col-start-2">{children}</span> | ||
| 174 | </MenuPrimitive.RadioItem> | ||
| 175 | ); | ||
| 176 | } | ||
| 177 | |||
| 178 | function MenuGroupLabel({ | ||
| 179 | className, | ||
| 180 | inset, | ||
| 181 | ...props | ||
| 182 | }: MenuPrimitive.GroupLabel.Props & { | ||
| 183 | inset?: boolean; | ||
| 184 | }) { | ||
| 185 | return ( | ||
| 186 | <MenuPrimitive.GroupLabel | ||
| 187 | className={cn( | ||
| 188 | "px-2 py-1.5 font-medium text-muted-foreground text-xs data-inset:ps-9 sm:data-inset:ps-8", | ||
| 189 | className, | ||
| 190 | )} | ||
| 191 | data-inset={inset} | ||
| 192 | data-slot="menu-label" | ||
| 193 | {...props} | ||
| 194 | /> | ||
| 195 | ); | ||
| 196 | } | ||
| 197 | |||
| 198 | function MenuSeparator({ className, ...props }: MenuPrimitive.Separator.Props) { | ||
| 199 | return ( | ||
| 200 | <MenuPrimitive.Separator | ||
| 201 | className={cn("mx-2 my-1 h-px bg-border", className)} | ||
| 202 | data-slot="menu-separator" | ||
| 203 | {...props} | ||
| 204 | /> | ||
| 205 | ); | ||
| 206 | } | ||
| 207 | |||
| 208 | function MenuShortcut({ className, ...props }: React.ComponentProps<"kbd">) { | ||
| 209 | return ( | ||
| 210 | <kbd | ||
| 211 | className={cn( | ||
| 212 | "ms-auto font-medium font-sans text-muted-foreground/72 text-xs tracking-widest", | ||
| 213 | className, | ||
| 214 | )} | ||
| 215 | data-slot="menu-shortcut" | ||
| 216 | {...props} | ||
| 217 | /> | ||
| 218 | ); | ||
| 219 | } | ||
| 220 | |||
| 221 | function MenuSub(props: MenuPrimitive.SubmenuRoot.Props) { | ||
| 222 | return <MenuPrimitive.SubmenuRoot data-slot="menu-sub" {...props} />; | ||
| 223 | } | ||
| 224 | |||
| 225 | function MenuSubTrigger({ | ||
| 226 | className, | ||
| 227 | inset, | ||
| 228 | children, | ||
| 229 | ...props | ||
| 230 | }: MenuPrimitive.SubmenuTrigger.Props & { | ||
| 231 | inset?: boolean; | ||
| 232 | }) { | ||
| 233 | return ( | ||
| 234 | <MenuPrimitive.SubmenuTrigger | ||
| 235 | className={cn( | ||
| 236 | "flex min-h-8 items-center gap-2 rounded-sm px-2 py-1 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-popup-open:bg-accent data-inset:ps-8 data-highlighted:text-accent-foreground data-popup-open:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none", | ||
| 237 | className, | ||
| 238 | )} | ||
| 239 | data-inset={inset} | ||
| 240 | data-slot="menu-sub-trigger" | ||
| 241 | {...props} | ||
| 242 | > | ||
| 243 | {children} | ||
| 244 | <ChevronRightIcon className="-me-0.5 ms-auto opacity-80" /> | ||
| 245 | </MenuPrimitive.SubmenuTrigger> | ||
| 246 | ); | ||
| 247 | } | ||
| 248 | |||
| 249 | function MenuSubPopup({ | ||
| 250 | className, | ||
| 251 | sideOffset = 0, | ||
| 252 | alignOffset, | ||
| 253 | align = "start", | ||
| 254 | ...props | ||
| 255 | }: MenuPrimitive.Popup.Props & { | ||
| 256 | align?: MenuPrimitive.Positioner.Props["align"]; | ||
| 257 | sideOffset?: MenuPrimitive.Positioner.Props["sideOffset"]; | ||
| 258 | alignOffset?: MenuPrimitive.Positioner.Props["alignOffset"]; | ||
| 259 | }) { | ||
| 260 | const defaultAlignOffset = align !== "center" ? -5 : undefined; | ||
| 261 | |||
| 262 | return ( | ||
| 263 | <MenuPopup | ||
| 264 | align={align} | ||
| 265 | alignOffset={alignOffset ?? defaultAlignOffset} | ||
| 266 | className={className} | ||
| 267 | data-slot="menu-sub-content" | ||
| 268 | side="inline-end" | ||
| 269 | sideOffset={sideOffset} | ||
| 270 | {...props} | ||
| 271 | /> | ||
| 272 | ); | ||
| 273 | } | ||
| 274 | |||
| 275 | export { | ||
| 276 | MenuCreateHandle, | ||
| 277 | MenuCreateHandle as DropdownMenuCreateHandle, | ||
| 278 | Menu, | ||
| 279 | Menu as DropdownMenu, | ||
| 280 | MenuPortal, | ||
| 281 | MenuPortal as DropdownMenuPortal, | ||
| 282 | MenuTrigger, | ||
| 283 | MenuTrigger as DropdownMenuTrigger, | ||
| 284 | MenuPopup, | ||
| 285 | MenuPopup as DropdownMenuContent, | ||
| 286 | MenuGroup, | ||
| 287 | MenuGroup as DropdownMenuGroup, | ||
| 288 | MenuItem, | ||
| 289 | MenuItem as DropdownMenuItem, | ||
| 290 | MenuCheckboxItem, | ||
| 291 | MenuCheckboxItem as DropdownMenuCheckboxItem, | ||
| 292 | MenuRadioGroup, | ||
| 293 | MenuRadioGroup as DropdownMenuRadioGroup, | ||
| 294 | MenuRadioItem, | ||
| 295 | MenuRadioItem as DropdownMenuRadioItem, | ||
| 296 | MenuGroupLabel, | ||
| 297 | MenuGroupLabel as DropdownMenuLabel, | ||
| 298 | MenuSeparator, | ||
| 299 | MenuSeparator as DropdownMenuSeparator, | ||
| 300 | MenuShortcut, | ||
| 301 | MenuShortcut as DropdownMenuShortcut, | ||
| 302 | MenuSub, | ||
| 303 | MenuSub as DropdownMenuSub, | ||
| 304 | MenuSubTrigger, | ||
| 305 | MenuSubTrigger as DropdownMenuSubTrigger, | ||
| 306 | MenuSubPopup, | ||
| 307 | MenuSubPopup as DropdownMenuSubContent, | ||
| 308 | }; | ||
apps/renderer/src/components/ui/popover.tsx created+102| ... | @@ -0,0 +1,102 @@ | ||
| 1 | import { Popover as PopoverPrimitive } from "@base-ui/react/popover"; | ||
| 2 | |||
| 3 | import { cn } from "#/lib/utils"; | ||
| 4 | |||
| 5 | const PopoverCreateHandle = PopoverPrimitive.createHandle; | ||
| 6 | |||
| 7 | const Popover = PopoverPrimitive.Root; | ||
| 8 | |||
| 9 | function PopoverTrigger(props: PopoverPrimitive.Trigger.Props) { | ||
| 10 | return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />; | ||
| 11 | } | ||
| 12 | |||
| 13 | function PopoverPopup({ | ||
| 14 | children, | ||
| 15 | className, | ||
| 16 | side = "bottom", | ||
| 17 | align = "center", | ||
| 18 | sideOffset = 4, | ||
| 19 | alignOffset = 0, | ||
| 20 | tooltipStyle = false, | ||
| 21 | ...props | ||
| 22 | }: PopoverPrimitive.Popup.Props & { | ||
| 23 | side?: PopoverPrimitive.Positioner.Props["side"]; | ||
| 24 | align?: PopoverPrimitive.Positioner.Props["align"]; | ||
| 25 | sideOffset?: PopoverPrimitive.Positioner.Props["sideOffset"]; | ||
| 26 | alignOffset?: PopoverPrimitive.Positioner.Props["alignOffset"]; | ||
| 27 | tooltipStyle?: boolean; | ||
| 28 | }) { | ||
| 29 | return ( | ||
| 30 | <PopoverPrimitive.Portal> | ||
| 31 | <PopoverPrimitive.Positioner | ||
| 32 | align={align} | ||
| 33 | alignOffset={alignOffset} | ||
| 34 | className="z-50 h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none" | ||
| 35 | data-slot="popover-positioner" | ||
| 36 | side={side} | ||
| 37 | sideOffset={sideOffset} | ||
| 38 | > | ||
| 39 | <PopoverPrimitive.Popup | ||
| 40 | className={cn( | ||
| 41 | "relative flex h-(--popup-height,auto) w-(--popup-width,auto) origin-(--transform-origin) rounded-lg border bg-popover not-dark:bg-clip-padding text-popover-foreground shadow-lg/5 transition-[width,height,scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] data-starting-style:scale-98 data-starting-style:opacity-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", | ||
| 42 | tooltipStyle && | ||
| 43 | "w-fit text-balance rounded-md text-xs shadow-md/5 before:rounded-[calc(var(--radius-md)-1px)]", | ||
| 44 | className, | ||
| 45 | )} | ||
| 46 | data-slot="popover-popup" | ||
| 47 | {...props} | ||
| 48 | > | ||
| 49 | <PopoverPrimitive.Viewport | ||
| 50 | className={cn( | ||
| 51 | "relative size-full max-h-(--available-height) overflow-clip px-(--viewport-inline-padding) py-4 outline-none [--viewport-inline-padding:--spacing(4)] data-instant:transition-none **:data-current:data-ending-style:opacity-0 **:data-current:data-starting-style:opacity-0 **:data-previous:data-ending-style:opacity-0 **:data-previous:data-starting-style:opacity-0 **:data-current:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-previous:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-current:opacity-100 **:data-previous:opacity-100 **:data-current:transition-opacity **:data-previous:transition-opacity", | ||
| 52 | tooltipStyle | ||
| 53 | ? "py-1 [--viewport-inline-padding:--spacing(2)]" | ||
| 54 | : "not-data-transitioning:overflow-y-auto", | ||
| 55 | )} | ||
| 56 | data-slot="popover-viewport" | ||
| 57 | > | ||
| 58 | {children} | ||
| 59 | </PopoverPrimitive.Viewport> | ||
| 60 | </PopoverPrimitive.Popup> | ||
| 61 | </PopoverPrimitive.Positioner> | ||
| 62 | </PopoverPrimitive.Portal> | ||
| 63 | ); | ||
| 64 | } | ||
| 65 | |||
| 66 | function PopoverClose({ ...props }: PopoverPrimitive.Close.Props) { | ||
| 67 | return <PopoverPrimitive.Close data-slot="popover-close" {...props} />; | ||
| 68 | } | ||
| 69 | |||
| 70 | function PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) { | ||
| 71 | return ( | ||
| 72 | <PopoverPrimitive.Title | ||
| 73 | className={cn("font-semibold text-lg leading-none", className)} | ||
| 74 | data-slot="popover-title" | ||
| 75 | {...props} | ||
| 76 | /> | ||
| 77 | ); | ||
| 78 | } | ||
| 79 | |||
| 80 | function PopoverDescription({ | ||
| 81 | className, | ||
| 82 | ...props | ||
| 83 | }: PopoverPrimitive.Description.Props) { | ||
| 84 | return ( | ||
| 85 | <PopoverPrimitive.Description | ||
| 86 | className={cn("text-muted-foreground text-sm", className)} | ||
| 87 | data-slot="popover-description" | ||
| 88 | {...props} | ||
| 89 | /> | ||
| 90 | ); | ||
| 91 | } | ||
| 92 | |||
| 93 | export { | ||
| 94 | PopoverCreateHandle, | ||
| 95 | Popover, | ||
| 96 | PopoverTrigger, | ||
| 97 | PopoverPopup, | ||
| 98 | PopoverPopup as PopoverContent, | ||
| 99 | PopoverTitle, | ||
| 100 | PopoverDescription, | ||
| 101 | PopoverClose, | ||
| 102 | }; | ||
apps/renderer/src/components/ui/progress.tsx created+79| ... | @@ -0,0 +1,79 @@ | ||
| 1 | import { Progress as ProgressPrimitive } from "@base-ui/react/progress"; | ||
| 2 | |||
| 3 | import { cn } from "#/lib/utils"; | ||
| 4 | |||
| 5 | function Progress({ | ||
| 6 | className, | ||
| 7 | children, | ||
| 8 | ...props | ||
| 9 | }: ProgressPrimitive.Root.Props) { | ||
| 10 | return ( | ||
| 11 | <ProgressPrimitive.Root | ||
| 12 | className={cn("flex w-full flex-col gap-2", className)} | ||
| 13 | data-slot="progress" | ||
| 14 | {...props} | ||
| 15 | > | ||
| 16 | {children ? ( | ||
| 17 | children | ||
| 18 | ) : ( | ||
| 19 | <ProgressTrack> | ||
| 20 | <ProgressIndicator /> | ||
| 21 | </ProgressTrack> | ||
| 22 | )} | ||
| 23 | </ProgressPrimitive.Root> | ||
| 24 | ); | ||
| 25 | } | ||
| 26 | |||
| 27 | function ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) { | ||
| 28 | return ( | ||
| 29 | <ProgressPrimitive.Label | ||
| 30 | className={cn("font-medium text-sm", className)} | ||
| 31 | data-slot="progress-label" | ||
| 32 | {...props} | ||
| 33 | /> | ||
| 34 | ); | ||
| 35 | } | ||
| 36 | |||
| 37 | function ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) { | ||
| 38 | return ( | ||
| 39 | <ProgressPrimitive.Track | ||
| 40 | className={cn( | ||
| 41 | "block h-1.5 w-full overflow-hidden rounded-full bg-input", | ||
| 42 | className, | ||
| 43 | )} | ||
| 44 | data-slot="progress-track" | ||
| 45 | {...props} | ||
| 46 | /> | ||
| 47 | ); | ||
| 48 | } | ||
| 49 | |||
| 50 | function ProgressIndicator({ | ||
| 51 | className, | ||
| 52 | ...props | ||
| 53 | }: ProgressPrimitive.Indicator.Props) { | ||
| 54 | return ( | ||
| 55 | <ProgressPrimitive.Indicator | ||
| 56 | className={cn("bg-primary transition-all duration-500", className)} | ||
| 57 | data-slot="progress-indicator" | ||
| 58 | {...props} | ||
| 59 | /> | ||
| 60 | ); | ||
| 61 | } | ||
| 62 | |||
| 63 | function ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) { | ||
| 64 | return ( | ||
| 65 | <ProgressPrimitive.Value | ||
| 66 | className={cn("text-sm tabular-nums", className)} | ||
| 67 | data-slot="progress-value" | ||
| 68 | {...props} | ||
| 69 | /> | ||
| 70 | ); | ||
| 71 | } | ||
| 72 | |||
| 73 | export { | ||
| 74 | Progress, | ||
| 75 | ProgressLabel, | ||
| 76 | ProgressTrack, | ||
| 77 | ProgressIndicator, | ||
| 78 | ProgressValue, | ||
| 79 | }; | ||
apps/renderer/src/components/ui/radio-group.tsx created+34| ... | @@ -0,0 +1,34 @@ | ||
| 1 | import { Radio as RadioPrimitive } from "@base-ui/react/radio"; | ||
| 2 | import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group"; | ||
| 3 | |||
| 4 | import { cn } from "#/lib/utils"; | ||
| 5 | |||
| 6 | function RadioGroup({ className, ...props }: RadioGroupPrimitive.Props) { | ||
| 7 | return ( | ||
| 8 | <RadioGroupPrimitive | ||
| 9 | className={cn("flex flex-col gap-3", className)} | ||
| 10 | data-slot="radio-group" | ||
| 11 | {...props} | ||
| 12 | /> | ||
| 13 | ); | ||
| 14 | } | ||
| 15 | |||
| 16 | function Radio({ className, ...props }: RadioPrimitive.Root.Props) { | ||
| 17 | return ( | ||
| 18 | <RadioPrimitive.Root | ||
| 19 | className={cn( | ||
| 20 | "relative inline-flex size-4.5 shrink-0 items-center justify-center rounded-full border border-input bg-background not-dark:bg-clip-padding shadow-xs/5 outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-full not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:ring-destructive/48 data-disabled:opacity-64 sm:size-4 dark:not-data-checked:bg-input/32 dark:aria-invalid:ring-destructive/24 dark:not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)] [[data-disabled],[data-checked],[aria-invalid]]:shadow-none", | ||
| 21 | className, | ||
| 22 | )} | ||
| 23 | data-slot="radio" | ||
| 24 | {...props} | ||
| 25 | > | ||
| 26 | <RadioPrimitive.Indicator | ||
| 27 | className="-inset-px absolute flex size-4.5 items-center justify-center rounded-full before:size-2 before:rounded-full before:bg-primary-foreground data-unchecked:hidden data-checked:bg-primary sm:size-4 sm:before:size-1.5" | ||
| 28 | data-slot="radio-indicator" | ||
| 29 | /> | ||
| 30 | </RadioPrimitive.Root> | ||
| 31 | ); | ||
| 32 | } | ||
| 33 | |||
| 34 | export { RadioGroup, Radio, Radio as RadioGroupItem }; | ||
apps/renderer/src/components/ui/scroll-area.tsx created+62| ... | @@ -0,0 +1,62 @@ | ||
| 1 | import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area"; | ||
| 2 | |||
| 3 | import { cn } from "#/lib/utils"; | ||
| 4 | |||
| 5 | function ScrollArea({ | ||
| 6 | className, | ||
| 7 | children, | ||
| 8 | scrollFade = false, | ||
| 9 | scrollbarGutter = false, | ||
| 10 | ...props | ||
| 11 | }: ScrollAreaPrimitive.Root.Props & { | ||
| 12 | scrollFade?: boolean; | ||
| 13 | scrollbarGutter?: boolean; | ||
| 14 | }) { | ||
| 15 | return ( | ||
| 16 | <ScrollAreaPrimitive.Root | ||
| 17 | className={cn("size-full min-h-0", className)} | ||
| 18 | {...props} | ||
| 19 | > | ||
| 20 | <ScrollAreaPrimitive.Viewport | ||
| 21 | className={cn( | ||
| 22 | "h-full overscroll-contain rounded-[inherit] outline-none transition-shadows focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-has-overflow-x:overscroll-x-contain", | ||
| 23 | scrollFade && | ||
| 24 | "mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] [--fade-size:1.5rem]", | ||
| 25 | scrollbarGutter && | ||
| 26 | "data-has-overflow-y:pe-2.5 data-has-overflow-x:pb-2.5", | ||
| 27 | )} | ||
| 28 | data-slot="scroll-area-viewport" | ||
| 29 | > | ||
| 30 | {children} | ||
| 31 | </ScrollAreaPrimitive.Viewport> | ||
| 32 | <ScrollBar orientation="vertical" /> | ||
| 33 | <ScrollBar orientation="horizontal" /> | ||
| 34 | <ScrollAreaPrimitive.Corner data-slot="scroll-area-corner" /> | ||
| 35 | </ScrollAreaPrimitive.Root> | ||
| 36 | ); | ||
| 37 | } | ||
| 38 | |||
| 39 | function ScrollBar({ | ||
| 40 | className, | ||
| 41 | orientation = "vertical", | ||
| 42 | ...props | ||
| 43 | }: ScrollAreaPrimitive.Scrollbar.Props) { | ||
| 44 | return ( | ||
| 45 | <ScrollAreaPrimitive.Scrollbar | ||
| 46 | className={cn( | ||
| 47 | "m-1 flex opacity-0 transition-opacity delay-300 data-[orientation=horizontal]:h-1.5 data-[orientation=vertical]:w-1.5 data-[orientation=horizontal]:flex-col data-hovering:opacity-100 data-scrolling:opacity-100 data-hovering:delay-0 data-scrolling:delay-0 data-hovering:duration-100 data-scrolling:duration-100", | ||
| 48 | className, | ||
| 49 | )} | ||
| 50 | data-slot="scroll-area-scrollbar" | ||
| 51 | orientation={orientation} | ||
| 52 | {...props} | ||
| 53 | > | ||
| 54 | <ScrollAreaPrimitive.Thumb | ||
| 55 | className="relative flex-1 rounded-full bg-foreground/20" | ||
| 56 | data-slot="scroll-area-thumb" | ||
| 57 | /> | ||
| 58 | </ScrollAreaPrimitive.Scrollbar> | ||
| 59 | ); | ||
| 60 | } | ||
| 61 | |||
| 62 | export { ScrollArea, ScrollBar }; | ||
apps/renderer/src/components/ui/select.tsx created+187| ... | @@ -0,0 +1,187 @@ | ||
| 1 | import { Select as SelectPrimitive } from "@base-ui/react/select"; | ||
| 2 | import { | ||
| 3 | ChevronDownIcon, | ||
| 4 | ChevronUpIcon, | ||
| 5 | ChevronsUpDownIcon, | ||
| 6 | } from "lucide-react"; | ||
| 7 | |||
| 8 | import { cn } from "#/lib/utils"; | ||
| 9 | |||
| 10 | const Select = SelectPrimitive.Root; | ||
| 11 | |||
| 12 | function SelectTrigger({ | ||
| 13 | className, | ||
| 14 | size = "default", | ||
| 15 | children, | ||
| 16 | ...props | ||
| 17 | }: SelectPrimitive.Trigger.Props & { | ||
| 18 | size?: "sm" | "default" | "lg"; | ||
| 19 | }) { | ||
| 20 | return ( | ||
| 21 | <SelectPrimitive.Trigger | ||
| 22 | className={cn( | ||
| 23 | "relative inline-flex min-h-9 w-full min-w-36 select-none items-center justify-center gap-2 rounded-lg border border-input bg-background not-dark:bg-clip-padding px-[calc(--spacing(3)-1px)] text-left text-base text-foreground shadow-xs/5 outline-none ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 focus-visible:border-ring focus-visible:ring-[3px] aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:ring-destructive/16 data-disabled:pointer-events-none data-disabled:opacity-64 sm:min-h-8 sm:text-sm dark:bg-input/32 dark:aria-invalid:ring-destructive/24 dark:not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 [[data-disabled],:focus-visible,[aria-invalid],[data-pressed]]:shadow-none", | ||
| 24 | size === "sm" && | ||
| 25 | "min-h-8 gap-1.5 px-[calc(--spacing(2.5)-1px)] sm:min-h-7", | ||
| 26 | size === "lg" && "min-h-10 sm:min-h-9", | ||
| 27 | className, | ||
| 28 | )} | ||
| 29 | data-slot="select-trigger" | ||
| 30 | {...props} | ||
| 31 | > | ||
| 32 | {children} | ||
| 33 | <SelectPrimitive.Icon data-slot="select-icon"> | ||
| 34 | <ChevronsUpDownIcon className="-me-1 size-4.5 opacity-80 sm:size-4" /> | ||
| 35 | </SelectPrimitive.Icon> | ||
| 36 | </SelectPrimitive.Trigger> | ||
| 37 | ); | ||
| 38 | } | ||
| 39 | |||
| 40 | function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) { | ||
| 41 | return ( | ||
| 42 | <SelectPrimitive.Value | ||
| 43 | className={cn( | ||
| 44 | "flex-1 truncate data-placeholder:text-muted-foreground", | ||
| 45 | className, | ||
| 46 | )} | ||
| 47 | data-slot="select-value" | ||
| 48 | {...props} | ||
| 49 | /> | ||
| 50 | ); | ||
| 51 | } | ||
| 52 | |||
| 53 | function SelectPopup({ | ||
| 54 | className, | ||
| 55 | children, | ||
| 56 | side = "bottom", | ||
| 57 | sideOffset = 4, | ||
| 58 | align = "start", | ||
| 59 | alignOffset = 0, | ||
| 60 | alignItemWithTrigger = true, | ||
| 61 | ...props | ||
| 62 | }: SelectPrimitive.Popup.Props & { | ||
| 63 | side?: SelectPrimitive.Positioner.Props["side"]; | ||
| 64 | sideOffset?: SelectPrimitive.Positioner.Props["sideOffset"]; | ||
| 65 | align?: SelectPrimitive.Positioner.Props["align"]; | ||
| 66 | alignOffset?: SelectPrimitive.Positioner.Props["alignOffset"]; | ||
| 67 | alignItemWithTrigger?: SelectPrimitive.Positioner.Props["alignItemWithTrigger"]; | ||
| 68 | }) { | ||
| 69 | return ( | ||
| 70 | <SelectPrimitive.Portal> | ||
| 71 | <SelectPrimitive.Positioner | ||
| 72 | align={align} | ||
| 73 | alignItemWithTrigger={alignItemWithTrigger} | ||
| 74 | alignOffset={alignOffset} | ||
| 75 | className="z-50 select-none" | ||
| 76 | data-slot="select-positioner" | ||
| 77 | side={side} | ||
| 78 | sideOffset={sideOffset} | ||
| 79 | > | ||
| 80 | <SelectPrimitive.Popup | ||
| 81 | className="origin-(--transform-origin) text-foreground" | ||
| 82 | data-slot="select-popup" | ||
| 83 | {...props} | ||
| 84 | > | ||
| 85 | <SelectPrimitive.ScrollUpArrow | ||
| 86 | className="top-0 z-50 flex h-6 w-full cursor-default items-center justify-center before:pointer-events-none before:absolute before:inset-x-px before:top-px before:h-[200%] before:rounded-t-[calc(var(--radius-lg)-1px)] before:bg-linear-to-b before:from-50% before:from-popover" | ||
| 87 | data-slot="select-scroll-up-arrow" | ||
| 88 | > | ||
| 89 | <ChevronUpIcon className="relative size-4.5 sm:size-4" /> | ||
| 90 | </SelectPrimitive.ScrollUpArrow> | ||
| 91 | <div className="relative h-full min-w-(--anchor-width) rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg/5 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]"> | ||
| 92 | <SelectPrimitive.List | ||
| 93 | className={cn( | ||
| 94 | "max-h-(--available-height) overflow-y-auto p-1", | ||
| 95 | className, | ||
| 96 | )} | ||
| 97 | data-slot="select-list" | ||
| 98 | > | ||
| 99 | {children} | ||
| 100 | </SelectPrimitive.List> | ||
| 101 | </div> | ||
| 102 | <SelectPrimitive.ScrollDownArrow | ||
| 103 | className="bottom-0 z-50 flex h-6 w-full cursor-default items-center justify-center before:pointer-events-none before:absolute before:inset-x-px before:bottom-px before:h-[200%] before:rounded-b-[calc(var(--radius-lg)-1px)] before:bg-linear-to-t before:from-50% before:from-popover" | ||
| 104 | data-slot="select-scroll-down-arrow" | ||
| 105 | > | ||
| 106 | <ChevronDownIcon className="relative size-4.5 sm:size-4" /> | ||
| 107 | </SelectPrimitive.ScrollDownArrow> | ||
| 108 | </SelectPrimitive.Popup> | ||
| 109 | </SelectPrimitive.Positioner> | ||
| 110 | </SelectPrimitive.Portal> | ||
| 111 | ); | ||
| 112 | } | ||
| 113 | |||
| 114 | function SelectItem({ | ||
| 115 | className, | ||
| 116 | children, | ||
| 117 | ...props | ||
| 118 | }: SelectPrimitive.Item.Props) { | ||
| 119 | return ( | ||
| 120 | <SelectPrimitive.Item | ||
| 121 | className={cn( | ||
| 122 | "grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default grid-cols-[1rem_1fr] items-center gap-2 rounded-sm py-1 ps-2 pe-4 text-base outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", | ||
| 123 | className, | ||
| 124 | )} | ||
| 125 | data-slot="select-item" | ||
| 126 | {...props} | ||
| 127 | > | ||
| 128 | <SelectPrimitive.ItemIndicator className="col-start-1"> | ||
| 129 | <svg | ||
| 130 | fill="none" | ||
| 131 | height="24" | ||
| 132 | stroke="currentColor" | ||
| 133 | strokeLinecap="round" | ||
| 134 | strokeLinejoin="round" | ||
| 135 | strokeWidth="2" | ||
| 136 | viewBox="0 0 24 24" | ||
| 137 | width="24" | ||
| 138 | xmlns="http://www.w3.org/1500/svg" | ||
| 139 | > | ||
| 140 | <path d="M5.252 12.7 10.2 18.63 18.748 5.37" /> | ||
| 141 | </svg> | ||
| 142 | </SelectPrimitive.ItemIndicator> | ||
| 143 | <SelectPrimitive.ItemText className="col-start-2 min-w-0"> | ||
| 144 | {children} | ||
| 145 | </SelectPrimitive.ItemText> | ||
| 146 | </SelectPrimitive.Item> | ||
| 147 | ); | ||
| 148 | } | ||
| 149 | |||
| 150 | function SelectSeparator({ | ||
| 151 | className, | ||
| 152 | ...props | ||
| 153 | }: SelectPrimitive.Separator.Props) { | ||
| 154 | return ( | ||
| 155 | <SelectPrimitive.Separator | ||
| 156 | className={cn("mx-2 my-1 h-px bg-border", className)} | ||
| 157 | data-slot="select-separator" | ||
| 158 | {...props} | ||
| 159 | /> | ||
| 160 | ); | ||
| 161 | } | ||
| 162 | |||
| 163 | function SelectGroup(props: SelectPrimitive.Group.Props) { | ||
| 164 | return <SelectPrimitive.Group data-slot="select-group" {...props} />; | ||
| 165 | } | ||
| 166 | |||
| 167 | function SelectGroupLabel(props: SelectPrimitive.GroupLabel.Props) { | ||
| 168 | return ( | ||
| 169 | <SelectPrimitive.GroupLabel | ||
| 170 | className="px-2 py-1.5 font-medium text-muted-foreground text-xs" | ||
| 171 | data-slot="select-group-label" | ||
| 172 | {...props} | ||
| 173 | /> | ||
| 174 | ); | ||
| 175 | } | ||
| 176 | |||
| 177 | export { | ||
| 178 | Select, | ||
| 179 | SelectTrigger, | ||
| 180 | SelectValue, | ||
| 181 | SelectPopup, | ||
| 182 | SelectPopup as SelectContent, | ||
| 183 | SelectItem, | ||
| 184 | SelectSeparator, | ||
| 185 | SelectGroup, | ||
| 186 | SelectGroupLabel, | ||
| 187 | }; | ||
apps/renderer/src/components/ui/separator.tsx created+23| ... | @@ -0,0 +1,23 @@ | ||
| 1 | import { Separator as SeparatorPrimitive } from "@base-ui/react/separator"; | ||
| 2 | |||
| 3 | import { cn } from "#/lib/utils"; | ||
| 4 | |||
| 5 | function Separator({ | ||
| 6 | className, | ||
| 7 | orientation = "horizontal", | ||
| 8 | ...props | ||
| 9 | }: SeparatorPrimitive.Props) { | ||
| 10 | return ( | ||
| 11 | <SeparatorPrimitive | ||
| 12 | className={cn( | ||
| 13 | "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:not-[[class^='h-']]:not-[[class*='_h-']]:self-stretch", | ||
| 14 | className, | ||
| 15 | )} | ||
| 16 | data-slot="separator" | ||
| 17 | orientation={orientation} | ||
| 18 | {...props} | ||
| 19 | /> | ||
| 20 | ); | ||
| 21 | } | ||
| 22 | |||
| 23 | export { Separator }; | ||
apps/renderer/src/components/ui/sheet.tsx created+201| ... | @@ -0,0 +1,201 @@ | ||
| 1 | import { Dialog as SheetPrimitive } from "@base-ui/react/dialog"; | ||
| 2 | import { XIcon } from "lucide-react"; | ||
| 3 | import { Button } from "#/components/ui/button"; | ||
| 4 | import { ScrollArea } from "#/components/ui/scroll-area"; | ||
| 5 | import { cn } from "#/lib/utils"; | ||
| 6 | |||
| 7 | const Sheet = SheetPrimitive.Root; | ||
| 8 | |||
| 9 | const SheetPortal = SheetPrimitive.Portal; | ||
| 10 | |||
| 11 | function SheetTrigger(props: SheetPrimitive.Trigger.Props) { | ||
| 12 | return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />; | ||
| 13 | } | ||
| 14 | |||
| 15 | function SheetClose(props: SheetPrimitive.Close.Props) { | ||
| 16 | return <SheetPrimitive.Close data-slot="sheet-close" {...props} />; | ||
| 17 | } | ||
| 18 | |||
| 19 | function SheetBackdrop({ className, ...props }: SheetPrimitive.Backdrop.Props) { | ||
| 20 | return ( | ||
| 21 | <SheetPrimitive.Backdrop | ||
| 22 | className={cn( | ||
| 23 | "fixed inset-0 z-50 bg-black/32 backdrop-blur-sm transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0", | ||
| 24 | className, | ||
| 25 | )} | ||
| 26 | data-slot="sheet-backdrop" | ||
| 27 | {...props} | ||
| 28 | /> | ||
| 29 | ); | ||
| 30 | } | ||
| 31 | |||
| 32 | function SheetViewport({ | ||
| 33 | className, | ||
| 34 | side, | ||
| 35 | variant = "default", | ||
| 36 | ...props | ||
| 37 | }: SheetPrimitive.Viewport.Props & { | ||
| 38 | side?: "right" | "left" | "top" | "bottom"; | ||
| 39 | variant?: "default" | "inset"; | ||
| 40 | }) { | ||
| 41 | return ( | ||
| 42 | <SheetPrimitive.Viewport | ||
| 43 | className={cn( | ||
| 44 | "fixed inset-0 z-50 grid", | ||
| 45 | side === "bottom" && "grid grid-rows-[1fr_auto] pt-12", | ||
| 46 | side === "top" && "grid grid-rows-[auto_1fr] pb-12", | ||
| 47 | side === "left" && "flex justify-start", | ||
| 48 | side === "right" && "flex justify-end", | ||
| 49 | variant === "inset" && "sm:p-4", | ||
| 50 | )} | ||
| 51 | data-slot="sheet-viewport" | ||
| 52 | {...props} | ||
| 53 | /> | ||
| 54 | ); | ||
| 55 | } | ||
| 56 | |||
| 57 | function SheetPopup({ | ||
| 58 | className, | ||
| 59 | children, | ||
| 60 | showCloseButton = true, | ||
| 61 | side = "right", | ||
| 62 | variant = "default", | ||
| 63 | ...props | ||
| 64 | }: SheetPrimitive.Popup.Props & { | ||
| 65 | showCloseButton?: boolean; | ||
| 66 | side?: "right" | "left" | "top" | "bottom"; | ||
| 67 | variant?: "default" | "inset"; | ||
| 68 | }) { | ||
| 69 | return ( | ||
| 70 | <SheetPortal> | ||
| 71 | <SheetBackdrop /> | ||
| 72 | <SheetViewport side={side} variant={variant}> | ||
| 73 | <SheetPrimitive.Popup | ||
| 74 | className={cn( | ||
| 75 | "relative flex max-h-full min-h-0 w-full min-w-0 flex-col bg-popover not-dark:bg-clip-padding text-popover-foreground shadow-lg/5 transition-[opacity,translate] duration-200 ease-in-out will-change-transform before:pointer-events-none before:absolute before:inset-0 before:shadow-[0_1px_--theme(--color-black/4%)] data-ending-style:opacity-0 data-starting-style:opacity-0 max-sm:before:hidden dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", | ||
| 76 | side === "bottom" && | ||
| 77 | "row-start-2 border-t data-ending-style:translate-y-8 data-starting-style:translate-y-8", | ||
| 78 | side === "top" && | ||
| 79 | "data-ending-style:-translate-y-8 data-starting-style:-translate-y-8 border-b", | ||
| 80 | side === "left" && | ||
| 81 | "data-ending-style:-translate-x-8 data-starting-style:-translate-x-8 w-[calc(100%-(--spacing(12)))] max-w-md border-e", | ||
| 82 | side === "right" && | ||
| 83 | "col-start-2 w-[calc(100%-(--spacing(12)))] max-w-md border-s data-ending-style:translate-x-8 data-starting-style:translate-x-8", | ||
| 84 | variant === "inset" && | ||
| 85 | "before:hidden sm:rounded-2xl sm:border sm:before:rounded-[calc(var(--radius-2xl)-1px)] sm:**:data-[slot=sheet-footer]:rounded-b-[calc(var(--radius-2xl)-1px)]", | ||
| 86 | className, | ||
| 87 | )} | ||
| 88 | data-slot="sheet-popup" | ||
| 89 | {...props} | ||
| 90 | > | ||
| 91 | {children} | ||
| 92 | {showCloseButton && ( | ||
| 93 | <SheetPrimitive.Close | ||
| 94 | aria-label="Close" | ||
| 95 | className="absolute end-2 top-2" | ||
| 96 | render={<Button size="icon" variant="ghost" />} | ||
| 97 | > | ||
| 98 | <XIcon /> | ||
| 99 | </SheetPrimitive.Close> | ||
| 100 | )} | ||
| 101 | </SheetPrimitive.Popup> | ||
| 102 | </SheetViewport> | ||
| 103 | </SheetPortal> | ||
| 104 | ); | ||
| 105 | } | ||
| 106 | |||
| 107 | function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { | ||
| 108 | return ( | ||
| 109 | <div | ||
| 110 | className={cn( | ||
| 111 | "flex flex-col gap-2 p-6 in-[[data-slot=sheet-popup]:has([data-slot=sheet-panel])]:pb-3 max-sm:pb-4", | ||
| 112 | className, | ||
| 113 | )} | ||
| 114 | data-slot="sheet-header" | ||
| 115 | {...props} | ||
| 116 | /> | ||
| 117 | ); | ||
| 118 | } | ||
| 119 | |||
| 120 | function SheetFooter({ | ||
| 121 | className, | ||
| 122 | variant = "default", | ||
| 123 | ...props | ||
| 124 | }: React.ComponentProps<"div"> & { | ||
| 125 | variant?: "default" | "bare"; | ||
| 126 | }) { | ||
| 127 | return ( | ||
| 128 | <div | ||
| 129 | className={cn( | ||
| 130 | "flex flex-col-reverse gap-2 px-6 sm:flex-row sm:justify-end", | ||
| 131 | variant === "default" && "border-t bg-muted/72 py-4", | ||
| 132 | variant === "bare" && | ||
| 133 | "in-[[data-slot=sheet-popup]:has([data-slot=sheet-panel])]:pt-3 pt-4 pb-6", | ||
| 134 | className, | ||
| 135 | )} | ||
| 136 | data-slot="sheet-footer" | ||
| 137 | {...props} | ||
| 138 | /> | ||
| 139 | ); | ||
| 140 | } | ||
| 141 | |||
| 142 | function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) { | ||
| 143 | return ( | ||
| 144 | <SheetPrimitive.Title | ||
| 145 | className={cn( | ||
| 146 | "font-heading font-semibold text-xl leading-none", | ||
| 147 | className, | ||
| 148 | )} | ||
| 149 | data-slot="sheet-title" | ||
| 150 | {...props} | ||
| 151 | /> | ||
| 152 | ); | ||
| 153 | } | ||
| 154 | |||
| 155 | function SheetDescription({ | ||
| 156 | className, | ||
| 157 | ...props | ||
| 158 | }: SheetPrimitive.Description.Props) { | ||
| 159 | return ( | ||
| 160 | <SheetPrimitive.Description | ||
| 161 | className={cn("text-muted-foreground text-sm", className)} | ||
| 162 | data-slot="sheet-description" | ||
| 163 | {...props} | ||
| 164 | /> | ||
| 165 | ); | ||
| 166 | } | ||
| 167 | |||
| 168 | function SheetPanel({ | ||
| 169 | className, | ||
| 170 | scrollFade = true, | ||
| 171 | ...props | ||
| 172 | }: React.ComponentProps<"div"> & { scrollFade?: boolean }) { | ||
| 173 | return ( | ||
| 174 | <ScrollArea scrollFade={scrollFade}> | ||
| 175 | <div | ||
| 176 | className={cn( | ||
| 177 | "p-6 in-[[data-slot=sheet-popup]:has([data-slot=sheet-header])]:pt-1 in-[[data-slot=sheet-popup]:has([data-slot=sheet-footer]:not(.border-t))]:pb-1", | ||
| 178 | className, | ||
| 179 | )} | ||
| 180 | data-slot="sheet-panel" | ||
| 181 | {...props} | ||
| 182 | /> | ||
| 183 | </ScrollArea> | ||
| 184 | ); | ||
| 185 | } | ||
| 186 | |||
| 187 | export { | ||
| 188 | Sheet, | ||
| 189 | SheetTrigger, | ||
| 190 | SheetPortal, | ||
| 191 | SheetClose, | ||
| 192 | SheetBackdrop, | ||
| 193 | SheetBackdrop as SheetOverlay, | ||
| 194 | SheetPopup, | ||
| 195 | SheetPopup as SheetContent, | ||
| 196 | SheetHeader, | ||
| 197 | SheetFooter, | ||
| 198 | SheetTitle, | ||
| 199 | SheetDescription, | ||
| 200 | SheetPanel, | ||
| 201 | }; | ||
apps/renderer/src/components/ui/skeleton.tsx created+16| ... | @@ -0,0 +1,16 @@ | ||
| 1 | import { cn } from "#/lib/utils"; | ||
| 2 | |||
| 3 | function Skeleton({ className, ...props }: React.ComponentProps<"div">) { | ||
| 4 | return ( | ||
| 5 | <div | ||
| 6 | className={cn( | ||
| 7 | "animate-skeleton rounded-sm [--skeleton-highlight:--alpha(var(--color-white)/64%)] [background:linear-gradient(120deg,transparent_40%,var(--skeleton-highlight),transparent_60%)_var(--color-muted)_0_0/200%_100%_fixed] dark:[--skeleton-highlight:--alpha(var(--color-white)/4%)]", | ||
| 8 | className, | ||
| 9 | )} | ||
| 10 | data-slot="skeleton" | ||
| 11 | {...props} | ||
| 12 | /> | ||
| 13 | ); | ||
| 14 | } | ||
| 15 | |||
| 16 | export { Skeleton }; | ||
apps/renderer/src/components/ui/spinner.tsx created+18| ... | @@ -0,0 +1,18 @@ | ||
| 1 | import { Loader2Icon } from "lucide-react"; | ||
| 2 | import { cn } from "#/lib/utils"; | ||
| 3 | |||
| 4 | function Spinner({ | ||
| 5 | className, | ||
| 6 | ...props | ||
| 7 | }: React.ComponentProps<typeof Loader2Icon>) { | ||
| 8 | return ( | ||
| 9 | <Loader2Icon | ||
| 10 | aria-label="Loading" | ||
| 11 | className={cn("animate-spin", className)} | ||
| 12 | role="status" | ||
| 13 | {...props} | ||
| 14 | /> | ||
| 15 | ); | ||
| 16 | } | ||
| 17 | |||
| 18 | export { Spinner }; | ||
apps/renderer/src/components/ui/switch.tsx created+25| ... | @@ -0,0 +1,25 @@ | ||
| 1 | import { Switch as SwitchPrimitive } from "@base-ui/react/switch"; | ||
| 2 | |||
| 3 | import { cn } from "#/lib/utils"; | ||
| 4 | |||
| 5 | function Switch({ className, ...props }: SwitchPrimitive.Root.Props) { | ||
| 6 | return ( | ||
| 7 | <SwitchPrimitive.Root | ||
| 8 | className={cn( | ||
| 9 | "inline-flex h-[calc(var(--thumb-size)+2px)] w-[calc(var(--thumb-size)*2-2px)] shrink-0 items-center rounded-full p-px outline-none transition-[background-color,box-shadow] duration-200 [--thumb-size:--spacing(5)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-checked:bg-primary data-unchecked:bg-input data-disabled:opacity-64 sm:[--thumb-size:--spacing(4)]", | ||
| 10 | className, | ||
| 11 | )} | ||
| 12 | data-slot="switch" | ||
| 13 | {...props} | ||
| 14 | > | ||
| 15 | <SwitchPrimitive.Thumb | ||
| 16 | className={cn( | ||
| 17 | "pointer-events-none block aspect-square h-full origin-left in-[[role=switch]:active,[data-slot=label]:active]:not-data-disabled:scale-x-110 in-[[role=switch]:active,[data-slot=label]:active]:rounded-[var(--thumb-size)/calc(var(--thumb-size)*1.1)] rounded-(--thumb-size) bg-background shadow-sm/5 will-change-transform [transition:translate_.15s,border-radius_.15s,scale_.1s_.1s,transform-origin_.15s] data-checked:origin-[var(--thumb-size)_50%] data-checked:translate-x-[calc(var(--thumb-size)-4px)]", | ||
| 18 | )} | ||
| 19 | data-slot="switch-thumb" | ||
| 20 | /> | ||
| 21 | </SwitchPrimitive.Root> | ||
| 22 | ); | ||
| 23 | } | ||
| 24 | |||
| 25 | export { Switch }; | ||
apps/renderer/src/components/ui/table.tsx created+126| ... | @@ -0,0 +1,126 @@ | ||
| 1 | import type * as React from "react"; | ||
| 2 | |||
| 3 | import { cn } from "#/lib/utils"; | ||
| 4 | |||
| 5 | function Table({ className, ...props }: React.ComponentProps<"table">) { | ||
| 6 | return ( | ||
| 7 | <div | ||
| 8 | className="relative w-full overflow-x-auto" | ||
| 9 | data-slot="table-container" | ||
| 10 | > | ||
| 11 | <table | ||
| 12 | className={cn( | ||
| 13 | "w-full caption-bottom in-data-[slot=frame]:border-separate in-data-[slot=frame]:border-spacing-0 text-sm", | ||
| 14 | className, | ||
| 15 | )} | ||
| 16 | data-slot="table" | ||
| 17 | {...props} | ||
| 18 | /> | ||
| 19 | </div> | ||
| 20 | ); | ||
| 21 | } | ||
| 22 | |||
| 23 | function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { | ||
| 24 | return ( | ||
| 25 | <thead | ||
| 26 | className={cn( | ||
| 27 | "[&_tr]:border-b in-data-[slot=frame]:**:[th]:h-9 in-data-[slot=frame]:*:[tr]:border-none in-data-[slot=frame]:*:[tr]:hover:bg-transparent", | ||
| 28 | className, | ||
| 29 | )} | ||
| 30 | data-slot="table-header" | ||
| 31 | {...props} | ||
| 32 | /> | ||
| 33 | ); | ||
| 34 | } | ||
| 35 | |||
| 36 | function TableBody({ className, ...props }: React.ComponentProps<"tbody">) { | ||
| 37 | return ( | ||
| 38 | <tbody | ||
| 39 | className={cn( | ||
| 40 | "relative in-data-[slot=frame]:rounded-xl in-data-[slot=frame]:shadow-xs/5 before:pointer-events-none before:absolute before:inset-px not-in-data-[slot=frame]:before:hidden before:rounded-[calc(var(--radius-xl)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/8%)] [&_tr:last-child]:border-0 in-data-[slot=frame]:*:[tr]:border-0 in-data-[slot=frame]:*:[tr]:*:[td]:border-b in-data-[slot=frame]:*:[tr]:*:[td]:bg-background in-data-[slot=frame]:*:[tr]:*:[td]:bg-clip-padding in-data-[slot=frame]:*:[tr]:first:*:[td]:first:rounded-ss-xl in-data-[slot=frame]:*:[tr]:*:[td]:first:border-s in-data-[slot=frame]:*:[tr]:first:*:[td]:border-t in-data-[slot=frame]:*:[tr]:last:*:[td]:last:rounded-ee-xl in-data-[slot=frame]:*:[tr]:*:[td]:last:border-e in-data-[slot=frame]:*:[tr]:first:*:[td]:last:rounded-se-xl in-data-[slot=frame]:*:[tr]:last:*:[td]:first:rounded-es-xl in-data-[slot=frame]:*:[tr]:hover:*:[td]:bg-transparent in-data-[slot=frame]:*:[tr]:data-[state=selected]:*:[td]:bg-muted/72", | ||
| 41 | className, | ||
| 42 | )} | ||
| 43 | data-slot="table-body" | ||
| 44 | {...props} | ||
| 45 | /> | ||
| 46 | ); | ||
| 47 | } | ||
| 48 | |||
| 49 | function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { | ||
| 50 | return ( | ||
| 51 | <tfoot | ||
| 52 | className={cn( | ||
| 53 | "border-t in-data-[slot=frame]:border-none bg-muted/72 in-data-[slot=frame]:bg-transparent font-medium [&>tr]:last:border-b-0 in-data-[slot=frame]:*:[tr]:hover:bg-transparent", | ||
| 54 | className, | ||
| 55 | )} | ||
| 56 | data-slot="table-footer" | ||
| 57 | {...props} | ||
| 58 | /> | ||
| 59 | ); | ||
| 60 | } | ||
| 61 | |||
| 62 | function TableRow({ className, ...props }: React.ComponentProps<"tr">) { | ||
| 63 | return ( | ||
| 64 | <tr | ||
| 65 | className={cn( | ||
| 66 | "border-b transition-colors hover:bg-muted/72 in-data-[slot=frame]:hover:bg-transparent data-[state=selected]:bg-muted/72 in-data-[slot=frame]:data-[state=selected]:bg-transparent", | ||
| 67 | className, | ||
| 68 | )} | ||
| 69 | data-slot="table-row" | ||
| 70 | {...props} | ||
| 71 | /> | ||
| 72 | ); | ||
| 73 | } | ||
| 74 | |||
| 75 | function TableHead({ className, ...props }: React.ComponentProps<"th">) { | ||
| 76 | return ( | ||
| 77 | <th | ||
| 78 | className={cn( | ||
| 79 | "h-10 whitespace-nowrap px-2.5 text-left align-middle font-medium text-muted-foreground leading-none has-[[role=checkbox]]:w-px has-[[role=checkbox]]:pe-0", | ||
| 80 | className, | ||
| 81 | )} | ||
| 82 | data-slot="table-head" | ||
| 83 | {...props} | ||
| 84 | /> | ||
| 85 | ); | ||
| 86 | } | ||
| 87 | |||
| 88 | function TableCell({ className, ...props }: React.ComponentProps<"td">) { | ||
| 89 | return ( | ||
| 90 | <td | ||
| 91 | className={cn( | ||
| 92 | "whitespace-nowrap p-2.5 align-middle leading-none in-data-[slot=frame]:first:p-[calc(--spacing(2.5)-1px)] in-data-[slot=frame]:last:p-[calc(--spacing(2.5)-1px)] has-[[role=checkbox]]:pe-0", | ||
| 93 | className, | ||
| 94 | )} | ||
| 95 | data-slot="table-cell" | ||
| 96 | {...props} | ||
| 97 | /> | ||
| 98 | ); | ||
| 99 | } | ||
| 100 | |||
| 101 | function TableCaption({ | ||
| 102 | className, | ||
| 103 | ...props | ||
| 104 | }: React.ComponentProps<"caption">) { | ||
| 105 | return ( | ||
| 106 | <caption | ||
| 107 | className={cn( | ||
| 108 | "in-data-[slot=frame]:my-4 mt-4 text-muted-foreground text-sm", | ||
| 109 | className, | ||
| 110 | )} | ||
| 111 | data-slot="table-caption" | ||
| 112 | {...props} | ||
| 113 | /> | ||
| 114 | ); | ||
| 115 | } | ||
| 116 | |||
| 117 | export { | ||
| 118 | Table, | ||
| 119 | TableHeader, | ||
| 120 | TableBody, | ||
| 121 | TableFooter, | ||
| 122 | TableHead, | ||
| 123 | TableRow, | ||
| 124 | TableCell, | ||
| 125 | TableCaption, | ||
| 126 | }; | ||
apps/renderer/src/components/ui/tabs.tsx created+85| ... | @@ -0,0 +1,85 @@ | ||
| 1 | import { Tabs as TabsPrimitive } from "@base-ui/react/tabs"; | ||
| 2 | |||
| 3 | import { cn } from "#/lib/utils"; | ||
| 4 | |||
| 5 | type TabsVariant = "default" | "underline"; | ||
| 6 | |||
| 7 | function Tabs({ className, ...props }: TabsPrimitive.Root.Props) { | ||
| 8 | return ( | ||
| 9 | <TabsPrimitive.Root | ||
| 10 | className={cn( | ||
| 11 | "flex flex-col gap-2 data-[orientation=vertical]:flex-row", | ||
| 12 | className, | ||
| 13 | )} | ||
| 14 | data-slot="tabs" | ||
| 15 | {...props} | ||
| 16 | /> | ||
| 17 | ); | ||
| 18 | } | ||
| 19 | |||
| 20 | function TabsList({ | ||
| 21 | variant = "default", | ||
| 22 | className, | ||
| 23 | children, | ||
| 24 | ...props | ||
| 25 | }: TabsPrimitive.List.Props & { | ||
| 26 | variant?: TabsVariant; | ||
| 27 | }) { | ||
| 28 | return ( | ||
| 29 | <TabsPrimitive.List | ||
| 30 | className={cn( | ||
| 31 | "relative z-0 flex w-fit items-center justify-center gap-x-0.5 text-muted-foreground", | ||
| 32 | "data-[orientation=vertical]:flex-col", | ||
| 33 | variant === "default" | ||
| 34 | ? "rounded-lg bg-muted p-0.5 text-muted-foreground/72" | ||
| 35 | : "data-[orientation=vertical]:px-1 data-[orientation=horizontal]:py-1 *:data-[slot=tabs-tab]:hover:bg-accent", | ||
| 36 | className, | ||
| 37 | )} | ||
| 38 | data-slot="tabs-list" | ||
| 39 | {...props} | ||
| 40 | > | ||
| 41 | {children} | ||
| 42 | <TabsPrimitive.Indicator | ||
| 43 | className={cn( | ||
| 44 | "-translate-y-(--active-tab-bottom) absolute bottom-0 left-0 h-(--active-tab-height) w-(--active-tab-width) translate-x-(--active-tab-left) transition-[width,translate] duration-200 ease-in-out", | ||
| 45 | variant === "underline" | ||
| 46 | ? "data-[orientation=vertical]:-translate-x-px z-10 bg-primary data-[orientation=horizontal]:h-0.5 data-[orientation=vertical]:w-0.5 data-[orientation=horizontal]:translate-y-px" | ||
| 47 | : "-z-1 rounded-md bg-background shadow-sm/5 dark:bg-input", | ||
| 48 | )} | ||
| 49 | data-slot="tab-indicator" | ||
| 50 | /> | ||
| 51 | </TabsPrimitive.List> | ||
| 52 | ); | ||
| 53 | } | ||
| 54 | |||
| 55 | function TabsTab({ className, ...props }: TabsPrimitive.Tab.Props) { | ||
| 56 | return ( | ||
| 57 | <TabsPrimitive.Tab | ||
| 58 | className={cn( | ||
| 59 | "[&_svg]:-mx-0.5 relative flex h-9 shrink-0 grow cursor-pointer items-center justify-center gap-1.5 whitespace-nowrap rounded-md border border-transparent px-[calc(--spacing(2.5)-1px)] font-medium text-base outline-none transition-[color,background-color,box-shadow] hover:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring data-disabled:pointer-events-none data-[orientation=vertical]:w-full data-[orientation=vertical]:justify-start data-active:text-foreground data-disabled:opacity-64 sm:h-8 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", | ||
| 60 | className, | ||
| 61 | )} | ||
| 62 | data-slot="tabs-tab" | ||
| 63 | {...props} | ||
| 64 | /> | ||
| 65 | ); | ||
| 66 | } | ||
| 67 | |||
| 68 | function TabsPanel({ className, ...props }: TabsPrimitive.Panel.Props) { | ||
| 69 | return ( | ||
| 70 | <TabsPrimitive.Panel | ||
| 71 | className={cn("flex-1 outline-none", className)} | ||
| 72 | data-slot="tabs-content" | ||
| 73 | {...props} | ||
| 74 | /> | ||
| 75 | ); | ||
| 76 | } | ||
| 77 | |||
| 78 | export { | ||
| 79 | Tabs, | ||
| 80 | TabsList, | ||
| 81 | TabsTab, | ||
| 82 | TabsTab as TabsTrigger, | ||
| 83 | TabsPanel, | ||
| 84 | TabsPanel as TabsContent, | ||
| 85 | }; | ||
apps/renderer/src/components/ui/textarea.tsx created+49| ... | @@ -0,0 +1,49 @@ | ||
| 1 | import { Field as FieldPrimitive } from "@base-ui/react/field"; | ||
| 2 | import { mergeProps } from "@base-ui/react/merge-props"; | ||
| 3 | import type * as React from "react"; | ||
| 4 | |||
| 5 | import { cn } from "#/lib/utils"; | ||
| 6 | |||
| 7 | type TextareaProps = React.ComponentProps<"textarea"> & { | ||
| 8 | size?: "sm" | "default" | "lg" | number; | ||
| 9 | unstyled?: boolean; | ||
| 10 | }; | ||
| 11 | |||
| 12 | function Textarea({ | ||
| 13 | className, | ||
| 14 | size = "default", | ||
| 15 | unstyled = false, | ||
| 16 | ...props | ||
| 17 | }: TextareaProps) { | ||
| 18 | return ( | ||
| 19 | <span | ||
| 20 | className={ | ||
| 21 | cn( | ||
| 22 | !unstyled && | ||
| 23 | "relative inline-flex w-full rounded-lg border border-input bg-background not-dark:bg-clip-padding text-base text-foreground shadow-xs/5 ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] has-focus-visible:has-aria-invalid:border-destructive/64 has-focus-visible:has-aria-invalid:ring-destructive/16 has-aria-invalid:border-destructive/36 has-focus-visible:border-ring has-disabled:opacity-64 has-[:disabled,:focus-visible,[aria-invalid]]:shadow-none has-focus-visible:ring-[3px] not-has-disabled:has-not-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] sm:text-sm dark:bg-input/32 dark:has-aria-invalid:ring-destructive/24 dark:not-has-disabled:has-not-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)]", | ||
| 24 | className, | ||
| 25 | ) || undefined | ||
| 26 | } | ||
| 27 | data-size={size} | ||
| 28 | data-slot="textarea-control" | ||
| 29 | > | ||
| 30 | <FieldPrimitive.Control | ||
| 31 | render={(defaultProps) => ( | ||
| 32 | <textarea | ||
| 33 | className={cn( | ||
| 34 | "field-sizing-content min-h-17.5 w-full rounded-[inherit] px-[calc(--spacing(3)-1px)] py-[calc(--spacing(1.5)-1px)] outline-none max-sm:min-h-20.5", | ||
| 35 | size === "sm" && | ||
| 36 | "min-h-16.5 px-[calc(--spacing(2.5)-1px)] py-[calc(--spacing(1)-1px)] max-sm:min-h-19.5", | ||
| 37 | size === "lg" && | ||
| 38 | "min-h-18.5 py-[calc(--spacing(2)-1px)] max-sm:min-h-21.5", | ||
| 39 | )} | ||
| 40 | data-slot="textarea" | ||
| 41 | {...mergeProps(defaultProps, props)} | ||
| 42 | /> | ||
| 43 | )} | ||
| 44 | /> | ||
| 45 | </span> | ||
| 46 | ); | ||
| 47 | } | ||
| 48 | |||
| 49 | export { Textarea, type TextareaProps }; | ||
apps/renderer/src/components/ui/toast.tsx created+267| ... | @@ -0,0 +1,267 @@ | ||
| 1 | import { Toast } from "@base-ui/react/toast"; | ||
| 2 | import { | ||
| 3 | CircleAlertIcon, | ||
| 4 | CircleCheckIcon, | ||
| 5 | InfoIcon, | ||
| 6 | LoaderCircleIcon, | ||
| 7 | TriangleAlertIcon, | ||
| 8 | } from "lucide-react"; | ||
| 9 | |||
| 10 | import { buttonVariants } from "#/components/ui/button"; | ||
| 11 | import { cn } from "#/lib/utils"; | ||
| 12 | |||
| 13 | const toastManager = Toast.createToastManager(); | ||
| 14 | const anchoredToastManager = Toast.createToastManager(); | ||
| 15 | |||
| 16 | const TOAST_ICONS = { | ||
| 17 | error: CircleAlertIcon, | ||
| 18 | info: InfoIcon, | ||
| 19 | loading: LoaderCircleIcon, | ||
| 20 | success: CircleCheckIcon, | ||
| 21 | warning: TriangleAlertIcon, | ||
| 22 | } as const; | ||
| 23 | |||
| 24 | type ToastPosition = | ||
| 25 | | "top-left" | ||
| 26 | | "top-center" | ||
| 27 | | "top-right" | ||
| 28 | | "bottom-left" | ||
| 29 | | "bottom-center" | ||
| 30 | | "bottom-right"; | ||
| 31 | |||
| 32 | interface ToastProviderProps extends Toast.Provider.Props { | ||
| 33 | position?: ToastPosition; | ||
| 34 | } | ||
| 35 | |||
| 36 | function ToastProvider({ | ||
| 37 | children, | ||
| 38 | position = "bottom-right", | ||
| 39 | ...props | ||
| 40 | }: ToastProviderProps) { | ||
| 41 | return ( | ||
| 42 | <Toast.Provider toastManager={toastManager} {...props}> | ||
| 43 | {children} | ||
| 44 | <Toasts position={position} /> | ||
| 45 | </Toast.Provider> | ||
| 46 | ); | ||
| 47 | } | ||
| 48 | |||
| 49 | function Toasts({ position = "bottom-right" }: { position: ToastPosition }) { | ||
| 50 | const { toasts } = Toast.useToastManager(); | ||
| 51 | const isTop = position.startsWith("top"); | ||
| 52 | |||
| 53 | return ( | ||
| 54 | <Toast.Portal data-slot="toast-portal"> | ||
| 55 | <Toast.Viewport | ||
| 56 | className={cn( | ||
| 57 | "fixed z-50 mx-auto flex w-[calc(100%-var(--toast-inset)*2)] max-w-90 [--toast-inset:--spacing(4)] sm:[--toast-inset:--spacing(8)]", | ||
| 58 | // Vertical positioning | ||
| 59 | "data-[position*=top]:top-(--toast-inset)", | ||
| 60 | "data-[position*=bottom]:bottom-(--toast-inset)", | ||
| 61 | // Horizontal positioning | ||
| 62 | "data-[position*=left]:left-(--toast-inset)", | ||
| 63 | "data-[position*=right]:right-(--toast-inset)", | ||
| 64 | "data-[position*=center]:-translate-x-1/2 data-[position*=center]:left-1/2", | ||
| 65 | )} | ||
| 66 | data-position={position} | ||
| 67 | data-slot="toast-viewport" | ||
| 68 | > | ||
| 69 | {toasts.map((toast) => { | ||
| 70 | const Icon = toast.type | ||
| 71 | ? TOAST_ICONS[toast.type as keyof typeof TOAST_ICONS] | ||
| 72 | : null; | ||
| 73 | |||
| 74 | return ( | ||
| 75 | <Toast.Root | ||
| 76 | className={cn( | ||
| 77 | "absolute z-[calc(9999-var(--toast-index))] h-(--toast-calc-height) w-full select-none rounded-lg border bg-popover not-dark:bg-clip-padding text-popover-foreground shadow-lg/5 [transition:transform_.5s_cubic-bezier(.22,1,.36,1),opacity_.5s,height_.15s] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", | ||
| 78 | // Base positioning using data-position | ||
| 79 | "data-[position*=right]:right-0 data-[position*=right]:left-auto", | ||
| 80 | "data-[position*=left]:right-auto data-[position*=left]:left-0", | ||
| 81 | "data-[position*=center]:right-0 data-[position*=center]:left-0", | ||
| 82 | "data-[position*=top]:top-0 data-[position*=top]:bottom-auto data-[position*=top]:origin-top", | ||
| 83 | "data-[position*=bottom]:top-auto data-[position*=bottom]:bottom-0 data-[position*=bottom]:origin-bottom", | ||
| 84 | // Gap fill for hover | ||
| 85 | "after:absolute after:left-0 after:h-[calc(var(--toast-gap)+1px)] after:w-full", | ||
| 86 | "data-[position*=top]:after:top-full", | ||
| 87 | "data-[position*=bottom]:after:bottom-full", | ||
| 88 | // Define some variables | ||
| 89 | "[--toast-calc-height:var(--toast-frontmost-height,var(--toast-height))] [--toast-gap:--spacing(3)] [--toast-peek:--spacing(3)] [--toast-scale:calc(max(0,1-(var(--toast-index)*.1)))] [--toast-shrink:calc(1-var(--toast-scale))]", | ||
| 90 | // Define offset-y variable | ||
| 91 | "data-[position*=top]:[--toast-calc-offset-y:calc(var(--toast-offset-y)+var(--toast-index)*var(--toast-gap)+var(--toast-swipe-movement-y))]", | ||
| 92 | "data-[position*=bottom]:[--toast-calc-offset-y:calc(var(--toast-offset-y)*-1+var(--toast-index)*var(--toast-gap)*-1+var(--toast-swipe-movement-y))]", | ||
| 93 | // Default state transform | ||
| 94 | "data-[position*=top]:transform-[translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)+(var(--toast-index)*var(--toast-peek))+(var(--toast-shrink)*var(--toast-calc-height))))_scale(var(--toast-scale))]", | ||
| 95 | "data-[position*=bottom]:transform-[translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--toast-peek))-(var(--toast-shrink)*var(--toast-calc-height))))_scale(var(--toast-scale))]", | ||
| 96 | // Limited state | ||
| 97 | "data-limited:opacity-0", | ||
| 98 | // Expanded state | ||
| 99 | "data-expanded:h-(--toast-height)", | ||
| 100 | "data-position:data-expanded:transform-[translateX(var(--toast-swipe-movement-x))_translateY(var(--toast-calc-offset-y))]", | ||
| 101 | // Starting and ending animations | ||
| 102 | "data-[position*=top]:data-starting-style:transform-[translateY(calc(-100%-var(--toast-inset)))]", | ||
| 103 | "data-[position*=bottom]:data-starting-style:transform-[translateY(calc(100%+var(--toast-inset)))]", | ||
| 104 | "data-ending-style:opacity-0", | ||
| 105 | // Ending animations (direction-aware) | ||
| 106 | "data-ending-style:not-data-limited:not-data-swipe-direction:transform-[translateY(calc(100%+var(--toast-inset)))]", | ||
| 107 | "data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(var(--toast-swipe-movement-x)-100%-var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]", | ||
| 108 | "data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(var(--toast-swipe-movement-x)+100%+var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]", | ||
| 109 | "data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(var(--toast-swipe-movement-y)-100%-var(--toast-inset)))]", | ||
| 110 | "data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(var(--toast-swipe-movement-y)+100%+var(--toast-inset)))]", | ||
| 111 | // Ending animations (expanded) | ||
| 112 | "data-expanded:data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(var(--toast-swipe-movement-x)-100%-var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]", | ||
| 113 | "data-expanded:data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(var(--toast-swipe-movement-x)+100%+var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]", | ||
| 114 | "data-expanded:data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(var(--toast-swipe-movement-y)-100%-var(--toast-inset)))]", | ||
| 115 | "data-expanded:data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(var(--toast-swipe-movement-y)+100%+var(--toast-inset)))]", | ||
| 116 | )} | ||
| 117 | data-position={position} | ||
| 118 | key={toast.id} | ||
| 119 | swipeDirection={ | ||
| 120 | position.includes("center") | ||
| 121 | ? [isTop ? "up" : "down"] | ||
| 122 | : position.includes("left") | ||
| 123 | ? ["left", isTop ? "up" : "down"] | ||
| 124 | : ["right", isTop ? "up" : "down"] | ||
| 125 | } | ||
| 126 | toast={toast} | ||
| 127 | > | ||
| 128 | <Toast.Content className="pointer-events-auto flex items-center justify-between gap-1.5 overflow-hidden px-3.5 py-3 text-sm transition-opacity duration-250 data-behind:not-data-expanded:pointer-events-none data-behind:opacity-0 data-expanded:opacity-100"> | ||
| 129 | <div className="flex gap-2"> | ||
| 130 | {Icon && ( | ||
| 131 | <div | ||
| 132 | className="[&>svg]:h-lh [&>svg]:w-4 [&_svg]:pointer-events-none [&_svg]:shrink-0" | ||
| 133 | data-slot="toast-icon" | ||
| 134 | > | ||
| 135 | <Icon className="in-data-[type=loading]:animate-spin in-data-[type=error]:text-destructive in-data-[type=info]:text-info in-data-[type=success]:text-success in-data-[type=warning]:text-warning in-data-[type=loading]:opacity-80" /> | ||
| 136 | </div> | ||
| 137 | )} | ||
| 138 | |||
| 139 | <div className="flex flex-col gap-0.5"> | ||
| 140 | <Toast.Title | ||
| 141 | className="font-medium" | ||
| 142 | data-slot="toast-title" | ||
| 143 | /> | ||
| 144 | <Toast.Description | ||
| 145 | className="text-muted-foreground" | ||
| 146 | data-slot="toast-description" | ||
| 147 | /> | ||
| 148 | </div> | ||
| 149 | </div> | ||
| 150 | {toast.actionProps && ( | ||
| 151 | <Toast.Action | ||
| 152 | className={buttonVariants({ size: "xs" })} | ||
| 153 | data-slot="toast-action" | ||
| 154 | > | ||
| 155 | {toast.actionProps.children} | ||
| 156 | </Toast.Action> | ||
| 157 | )} | ||
| 158 | </Toast.Content> | ||
| 159 | </Toast.Root> | ||
| 160 | ); | ||
| 161 | })} | ||
| 162 | </Toast.Viewport> | ||
| 163 | </Toast.Portal> | ||
| 164 | ); | ||
| 165 | } | ||
| 166 | |||
| 167 | function AnchoredToastProvider({ children, ...props }: Toast.Provider.Props) { | ||
| 168 | return ( | ||
| 169 | <Toast.Provider toastManager={anchoredToastManager} {...props}> | ||
| 170 | {children} | ||
| 171 | <AnchoredToasts /> | ||
| 172 | </Toast.Provider> | ||
| 173 | ); | ||
| 174 | } | ||
| 175 | |||
| 176 | function AnchoredToasts() { | ||
| 177 | const { toasts } = Toast.useToastManager(); | ||
| 178 | |||
| 179 | return ( | ||
| 180 | <Toast.Portal data-slot="toast-portal-anchored"> | ||
| 181 | <Toast.Viewport | ||
| 182 | className="outline-none" | ||
| 183 | data-slot="toast-viewport-anchored" | ||
| 184 | > | ||
| 185 | {toasts.map((toast) => { | ||
| 186 | const Icon = toast.type | ||
| 187 | ? TOAST_ICONS[toast.type as keyof typeof TOAST_ICONS] | ||
| 188 | : null; | ||
| 189 | const tooltipStyle = | ||
| 190 | (toast.data as { tooltipStyle?: boolean })?.tooltipStyle ?? false; | ||
| 191 | const positionerProps = toast.positionerProps; | ||
| 192 | |||
| 193 | if (!positionerProps?.anchor) { | ||
| 194 | return null; | ||
| 195 | } | ||
| 196 | |||
| 197 | return ( | ||
| 198 | <Toast.Positioner | ||
| 199 | className="z-50 max-w-[min(--spacing(64),var(--available-width))]" | ||
| 200 | data-slot="toast-positioner" | ||
| 201 | key={toast.id} | ||
| 202 | sideOffset={positionerProps.sideOffset ?? 4} | ||
| 203 | toast={toast} | ||
| 204 | > | ||
| 205 | <Toast.Root | ||
| 206 | className={cn( | ||
| 207 | "relative text-balance border bg-popover not-dark:bg-clip-padding text-popover-foreground text-xs transition-[scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:shadow-[0_1px_--theme(--color-black/4%)] data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", | ||
| 208 | tooltipStyle | ||
| 209 | ? "rounded-md shadow-md/5 before:rounded-[calc(var(--radius-md)-1px)]" | ||
| 210 | : "rounded-lg shadow-lg/5 before:rounded-[calc(var(--radius-lg)-1px)]", | ||
| 211 | )} | ||
| 212 | data-slot="toast-popup" | ||
| 213 | toast={toast} | ||
| 214 | > | ||
| 215 | {tooltipStyle ? ( | ||
| 216 | <Toast.Content className="pointer-events-auto px-2 py-1"> | ||
| 217 | <Toast.Title data-slot="toast-title" /> | ||
| 218 | </Toast.Content> | ||
| 219 | ) : ( | ||
| 220 | <Toast.Content className="pointer-events-auto flex items-center justify-between gap-1.5 overflow-hidden px-3.5 py-3 text-sm"> | ||
| 221 | <div className="flex gap-2"> | ||
| 222 | {Icon && ( | ||
| 223 | <div | ||
| 224 | className="[&>svg]:h-lh [&>svg]:w-4 [&_svg]:pointer-events-none [&_svg]:shrink-0" | ||
| 225 | data-slot="toast-icon" | ||
| 226 | > | ||
| 227 | <Icon className="in-data-[type=loading]:animate-spin in-data-[type=error]:text-destructive in-data-[type=info]:text-info in-data-[type=success]:text-success in-data-[type=warning]:text-warning in-data-[type=loading]:opacity-80" /> | ||
| 228 | </div> | ||
| 229 | )} | ||
| 230 | |||
| 231 | <div className="flex flex-col gap-0.5"> | ||
| 232 | <Toast.Title | ||
| 233 | className="font-medium" | ||
| 234 | data-slot="toast-title" | ||
| 235 | /> | ||
| 236 | <Toast.Description | ||
| 237 | className="text-muted-foreground" | ||
| 238 | data-slot="toast-description" | ||
| 239 | /> | ||
| 240 | </div> | ||
| 241 | </div> | ||
| 242 | {toast.actionProps && ( | ||
| 243 | <Toast.Action | ||
| 244 | className={buttonVariants({ size: "xs" })} | ||
| 245 | data-slot="toast-action" | ||
| 246 | > | ||
| 247 | {toast.actionProps.children} | ||
| 248 | </Toast.Action> | ||
| 249 | )} | ||
| 250 | </Toast.Content> | ||
| 251 | )} | ||
| 252 | </Toast.Root> | ||
| 253 | </Toast.Positioner> | ||
| 254 | ); | ||
| 255 | })} | ||
| 256 | </Toast.Viewport> | ||
| 257 | </Toast.Portal> | ||
| 258 | ); | ||
| 259 | } | ||
| 260 | |||
| 261 | export { | ||
| 262 | ToastProvider, | ||
| 263 | type ToastPosition, | ||
| 264 | toastManager, | ||
| 265 | AnchoredToastProvider, | ||
| 266 | anchoredToastManager, | ||
| 267 | }; | ||
apps/renderer/src/hooks/useTheme.ts created+90| ... | @@ -0,0 +1,90 @@ | ||
| 1 | import { useCallback, useEffect, useSyncExternalStore } from "react"; | ||
| 2 | |||
| 3 | type Theme = "light" | "dark" | "system"; | ||
| 4 | |||
| 5 | const STORAGE_KEY = "codething:theme"; | ||
| 6 | const MEDIA_QUERY = "(prefers-color-scheme: dark)"; | ||
| 7 | |||
| 8 | let listeners: Array<() => void> = []; | ||
| 9 | function emitChange() { | ||
| 10 | for (const listener of listeners) listener(); | ||
| 11 | } | ||
| 12 | |||
| 13 | function getSystemDark(): boolean { | ||
| 14 | return window.matchMedia(MEDIA_QUERY).matches; | ||
| 15 | } | ||
| 16 | |||
| 17 | function getStored(): Theme { | ||
| 18 | const raw = localStorage.getItem(STORAGE_KEY); | ||
| 19 | if (raw === "light" || raw === "dark" || raw === "system") return raw; | ||
| 20 | return "system"; | ||
| 21 | } | ||
| 22 | |||
| 23 | function applyTheme(theme: Theme, suppressTransitions = false) { | ||
| 24 | if (suppressTransitions) { | ||
| 25 | document.documentElement.classList.add("no-transitions"); | ||
| 26 | } | ||
| 27 | const isDark = theme === "dark" || (theme === "system" && getSystemDark()); | ||
| 28 | document.documentElement.classList.toggle("dark", isDark); | ||
| 29 | if (suppressTransitions) { | ||
| 30 | // Force a reflow so the no-transitions class takes effect before removal | ||
| 31 | document.documentElement.offsetHeight; | ||
| 32 | requestAnimationFrame(() => { | ||
| 33 | document.documentElement.classList.remove("no-transitions"); | ||
| 34 | }); | ||
| 35 | } | ||
| 36 | } | ||
| 37 | |||
| 38 | // Apply immediately on module load to prevent flash | ||
| 39 | applyTheme(getStored()); | ||
| 40 | |||
| 41 | function getSnapshot(): Theme { | ||
| 42 | return getStored(); | ||
| 43 | } | ||
| 44 | |||
| 45 | function subscribe(listener: () => void): () => void { | ||
| 46 | listeners.push(listener); | ||
| 47 | |||
| 48 | // Listen for system preference changes | ||
| 49 | const mq = window.matchMedia(MEDIA_QUERY); | ||
| 50 | const handleChange = () => { | ||
| 51 | if (getStored() === "system") applyTheme("system", true); | ||
| 52 | emitChange(); | ||
| 53 | }; | ||
| 54 | mq.addEventListener("change", handleChange); | ||
| 55 | |||
| 56 | // Listen for storage changes from other tabs | ||
| 57 | const handleStorage = (e: StorageEvent) => { | ||
| 58 | if (e.key === STORAGE_KEY) { | ||
| 59 | applyTheme(getStored(), true); | ||
| 60 | emitChange(); | ||
| 61 | } | ||
| 62 | }; | ||
| 63 | window.addEventListener("storage", handleStorage); | ||
| 64 | |||
| 65 | return () => { | ||
| 66 | listeners = listeners.filter((l) => l !== listener); | ||
| 67 | mq.removeEventListener("change", handleChange); | ||
| 68 | window.removeEventListener("storage", handleStorage); | ||
| 69 | }; | ||
| 70 | } | ||
| 71 | |||
| 72 | export function useTheme() { | ||
| 73 | const theme = useSyncExternalStore(subscribe, getSnapshot); | ||
| 74 | |||
| 75 | const resolvedTheme: "light" | "dark" = | ||
| 76 | theme === "system" ? (getSystemDark() ? "dark" : "light") : theme; | ||
| 77 | |||
| 78 | const setTheme = useCallback((next: Theme) => { | ||
| 79 | localStorage.setItem(STORAGE_KEY, next); | ||
| 80 | applyTheme(next, true); | ||
| 81 | emitChange(); | ||
| 82 | }, []); | ||
| 83 | |||
| 84 | // Keep DOM in sync on mount/change | ||
| 85 | useEffect(() => { | ||
| 86 | applyTheme(theme); | ||
| 87 | }, [theme]); | ||
| 88 | |||
| 89 | return { theme, setTheme, resolvedTheme } as const; | ||
| 90 | } | ||
apps/renderer/src/index.css+194-26| ... | @@ -1,7 +1,178 @@ | ... | @@ -1,7 +1,178 @@ |
| 1 | @import "tailwindcss"; | 1 | @import "tailwindcss"; |
| 2 | 2 | ||
| 3 | @custom-variant dark (&:is(.dark, .dark *)); | ||
| 4 | |||
| 5 | @theme inline { | ||
| 6 | --animate-skeleton: skeleton 2s -1s infinite linear; | ||
| 7 | --color-warning-foreground: var(--warning-foreground); | ||
| 8 | --color-warning: var(--warning); | ||
| 9 | --color-success-foreground: var(--success-foreground); | ||
| 10 | --color-success: var(--success); | ||
| 11 | --color-info-foreground: var(--info-foreground); | ||
| 12 | --color-info: var(--info); | ||
| 13 | --color-destructive-foreground: var(--destructive-foreground); | ||
| 14 | --color-sidebar-ring: var(--sidebar-ring); | ||
| 15 | --color-sidebar-border: var(--sidebar-border); | ||
| 16 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); | ||
| 17 | --color-sidebar-accent: var(--sidebar-accent); | ||
| 18 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); | ||
| 19 | --color-sidebar-primary: var(--sidebar-primary); | ||
| 20 | --color-sidebar-foreground: var(--sidebar-foreground); | ||
| 21 | --color-sidebar: var(--sidebar); | ||
| 22 | --color-chart-5: var(--chart-5); | ||
| 23 | --color-chart-4: var(--chart-4); | ||
| 24 | --color-chart-3: var(--chart-3); | ||
| 25 | --color-chart-2: var(--chart-2); | ||
| 26 | --color-chart-1: var(--chart-1); | ||
| 27 | --color-ring: var(--ring); | ||
| 28 | --color-input: var(--input); | ||
| 29 | --color-border: var(--border); | ||
| 30 | --color-destructive: var(--destructive); | ||
| 31 | --color-accent-foreground: var(--accent-foreground); | ||
| 32 | --color-accent: var(--accent); | ||
| 33 | --color-muted-foreground: var(--muted-foreground); | ||
| 34 | --color-muted: var(--muted); | ||
| 35 | --color-secondary-foreground: var(--secondary-foreground); | ||
| 36 | --color-secondary: var(--secondary); | ||
| 37 | --color-primary-foreground: var(--primary-foreground); | ||
| 38 | --color-primary: var(--primary); | ||
| 39 | --color-popover-foreground: var(--popover-foreground); | ||
| 40 | --color-popover: var(--popover); | ||
| 41 | --color-card-foreground: var(--card-foreground); | ||
| 42 | --color-card: var(--card); | ||
| 43 | --color-foreground: var(--foreground); | ||
| 44 | --color-background: var(--background); | ||
| 45 | --radius-sm: calc(var(--radius) - 4px); | ||
| 46 | --radius-md: calc(var(--radius) - 2px); | ||
| 47 | --radius-lg: var(--radius); | ||
| 48 | --radius-xl: calc(var(--radius) + 4px); | ||
| 49 | --radius-2xl: calc(var(--radius) + 8px); | ||
| 50 | --radius-3xl: calc(var(--radius) + 12px); | ||
| 51 | --radius-4xl: calc(var(--radius) + 16px); | ||
| 52 | @keyframes skeleton { | ||
| 53 | to { | ||
| 54 | background-position: -200% 0; | ||
| 55 | } | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | @layer base { | ||
| 60 | * { | ||
| 61 | @apply border-border outline-ring/50; | ||
| 62 | } | ||
| 63 | body { | ||
| 64 | @apply bg-background text-foreground; | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | /* Suppress all transitions during theme changes */ | ||
| 69 | .no-transitions, | ||
| 70 | .no-transitions *, | ||
| 71 | .no-transitions *::before, | ||
| 72 | .no-transitions *::after { | ||
| 73 | transition-duration: 0s !important; | ||
| 74 | animation-duration: 0s !important; | ||
| 75 | } | ||
| 76 | |||
| 3 | :root { | 77 | :root { |
| 4 | color-scheme: dark; | 78 | color-scheme: light; |
| 79 | --radius: 0.625rem; | ||
| 80 | --background: var(--color-white); | ||
| 81 | --foreground: var(--color-neutral-800); | ||
| 82 | --card: var(--color-white); | ||
| 83 | --card-foreground: var(--color-neutral-800); | ||
| 84 | --popover: var(--color-white); | ||
| 85 | --popover-foreground: var(--color-neutral-800); | ||
| 86 | --primary: var(--color-neutral-800); | ||
| 87 | --primary-foreground: var(--color-neutral-50); | ||
| 88 | --secondary: --alpha(var(--color-black) / 4%); | ||
| 89 | --secondary-foreground: var(--color-neutral-800); | ||
| 90 | --muted: --alpha(var(--color-black) / 4%); | ||
| 91 | --muted-foreground: color-mix( | ||
| 92 | in srgb, | ||
| 93 | var(--color-neutral-500) 90%, | ||
| 94 | var(--color-black) | ||
| 95 | ); | ||
| 96 | --accent: --alpha(var(--color-black) / 4%); | ||
| 97 | --accent-foreground: var(--color-neutral-800); | ||
| 98 | --destructive: var(--color-red-500); | ||
| 99 | --border: --alpha(var(--color-black) / 8%); | ||
| 100 | --input: --alpha(var(--color-black) / 10%); | ||
| 101 | --ring: var(--color-neutral-400); | ||
| 102 | --chart-1: oklch(0.646 0.222 41.116); | ||
| 103 | --chart-2: oklch(0.6 0.118 184.704); | ||
| 104 | --chart-3: oklch(0.398 0.07 227.392); | ||
| 105 | --chart-4: oklch(0.828 0.189 84.429); | ||
| 106 | --chart-5: oklch(0.769 0.188 70.08); | ||
| 107 | --sidebar: oklch(0.985 0 0); | ||
| 108 | --sidebar-foreground: oklch(0.141 0.005 285.823); | ||
| 109 | --sidebar-primary: oklch(0.21 0.006 285.885); | ||
| 110 | --sidebar-primary-foreground: oklch(0.985 0 0); | ||
| 111 | --sidebar-accent: oklch(0.967 0.001 286.375); | ||
| 112 | --sidebar-accent-foreground: oklch(0.21 0.006 285.885); | ||
| 113 | --sidebar-border: oklch(0.92 0.004 286.32); | ||
| 114 | --sidebar-ring: oklch(0.705 0.015 286.067); | ||
| 115 | --destructive-foreground: var(--color-red-700); | ||
| 116 | --info: var(--color-blue-500); | ||
| 117 | --info-foreground: var(--color-blue-700); | ||
| 118 | --success: var(--color-emerald-500); | ||
| 119 | --success-foreground: var(--color-emerald-700); | ||
| 120 | --warning: var(--color-amber-500); | ||
| 121 | --warning-foreground: var(--color-amber-700); | ||
| 122 | |||
| 123 | @variant dark { | ||
| 124 | color-scheme: dark; | ||
| 125 | --background: color-mix( | ||
| 126 | in srgb, | ||
| 127 | var(--color-neutral-950) 95%, | ||
| 128 | var(--color-white) | ||
| 129 | ); | ||
| 130 | --foreground: var(--color-neutral-100); | ||
| 131 | --card: color-mix(in srgb, var(--background) 98%, var(--color-white)); | ||
| 132 | --card-foreground: var(--color-neutral-100); | ||
| 133 | --popover: color-mix(in srgb, var(--background) 98%, var(--color-white)); | ||
| 134 | --popover-foreground: var(--color-neutral-100); | ||
| 135 | --primary: var(--color-neutral-100); | ||
| 136 | --primary-foreground: var(--color-neutral-800); | ||
| 137 | --secondary: --alpha(var(--color-white) / 4%); | ||
| 138 | --secondary-foreground: var(--color-neutral-100); | ||
| 139 | --muted: --alpha(var(--color-white) / 4%); | ||
| 140 | --muted-foreground: color-mix( | ||
| 141 | in srgb, | ||
| 142 | var(--color-neutral-500) 90%, | ||
| 143 | var(--color-white) | ||
| 144 | ); | ||
| 145 | --accent: --alpha(var(--color-white) / 4%); | ||
| 146 | --accent-foreground: var(--color-neutral-100); | ||
| 147 | --destructive: color-mix( | ||
| 148 | in srgb, | ||
| 149 | var(--color-red-500) 90%, | ||
| 150 | var(--color-white) | ||
| 151 | ); | ||
| 152 | --border: --alpha(var(--color-white) / 6%); | ||
| 153 | --input: --alpha(var(--color-white) / 8%); | ||
| 154 | --ring: var(--color-neutral-500); | ||
| 155 | --chart-1: oklch(0.488 0.243 264.376); | ||
| 156 | --chart-2: oklch(0.696 0.17 162.48); | ||
| 157 | --chart-3: oklch(0.769 0.188 70.08); | ||
| 158 | --chart-4: oklch(0.627 0.265 303.9); | ||
| 159 | --chart-5: oklch(0.645 0.246 16.439); | ||
| 160 | --sidebar: oklch(0.21 0.006 285.885); | ||
| 161 | --sidebar-foreground: oklch(0.985 0 0); | ||
| 162 | --sidebar-primary: oklch(0.488 0.243 264.376); | ||
| 163 | --sidebar-primary-foreground: oklch(0.985 0 0); | ||
| 164 | --sidebar-accent: oklch(0.274 0.006 286.033); | ||
| 165 | --sidebar-accent-foreground: oklch(0.985 0 0); | ||
| 166 | --sidebar-border: oklch(1 0 0 / 10%); | ||
| 167 | --sidebar-ring: oklch(0.552 0.016 285.938); | ||
| 168 | --destructive-foreground: var(--color-red-400); | ||
| 169 | --info: var(--color-blue-500); | ||
| 170 | --info-foreground: var(--color-blue-400); | ||
| 171 | --success: var(--color-emerald-500); | ||
| 172 | --success-foreground: var(--color-emerald-400); | ||
| 173 | --warning: var(--color-amber-500); | ||
| 174 | --warning-foreground: var(--color-amber-400); | ||
| 175 | } | ||
| 5 | } | 176 | } |
| 6 | 177 | ||
| 7 | body { | 178 | body { |
| ... | @@ -10,8 +181,6 @@ body { | ... | @@ -10,8 +181,6 @@ body { |
| 10 | margin: 0; | 181 | margin: 0; |
| 11 | padding: 0; | 182 | padding: 0; |
| 12 | min-height: 100vh; | 183 | min-height: 100vh; |
| 13 | color: #e0e0e0; | ||
| 14 | background: #0c0c0c; | ||
| 15 | } | 184 | } |
| 16 | 185 | ||
| 17 | body::after { | 186 | body::after { |
| ... | @@ -55,25 +224,20 @@ input { | ... | @@ -55,25 +224,20 @@ input { |
| 55 | } | 224 | } |
| 56 | 225 | ||
| 57 | ::-webkit-scrollbar-thumb { | 226 | ::-webkit-scrollbar-thumb { |
| 58 | background: rgba(255, 255, 255, 0.1); | 227 | background: rgba(0, 0, 0, 0.15); |
| 59 | border-radius: 3px; | 228 | border-radius: 3px; |
| 60 | } | 229 | } |
| 61 | 230 | ||
| 62 | ::-webkit-scrollbar-thumb:hover { | 231 | ::-webkit-scrollbar-thumb:hover { |
| 63 | background: rgba(255, 255, 255, 0.18); | 232 | background: rgba(0, 0, 0, 0.25); |
| 64 | } | 233 | } |
| 65 | 234 | ||
| 66 | /* Reasoning select — clickable label surface */ | 235 | .dark ::-webkit-scrollbar-thumb { |
| 67 | label:has(> select#reasoning-effort) { | 236 | background: rgba(255, 255, 255, 0.1); |
| 68 | position: relative; | ||
| 69 | } | 237 | } |
| 70 | label:has(> select#reasoning-effort) select { | 238 | |
| 71 | position: absolute; | 239 | .dark ::-webkit-scrollbar-thumb:hover { |
| 72 | inset: 0; | 240 | background: rgba(255, 255, 255, 0.18); |
| 73 | opacity: 0; | ||
| 74 | cursor: pointer; | ||
| 75 | width: 100%; | ||
| 76 | height: 100%; | ||
| 77 | } | 241 | } |
| 78 | 242 | ||
| 79 | /* Chat markdown rendering */ | 243 | /* Chat markdown rendering */ |
| ... | @@ -108,34 +272,38 @@ label:has(> select#reasoning-effort) select { | ... | @@ -108,34 +272,38 @@ label:has(> select#reasoning-effort) select { |
| 108 | } | 272 | } |
| 109 | 273 | ||
| 110 | .chat-markdown a { | 274 | .chat-markdown a { |
| 111 | color: #93c5fd; | 275 | color: var(--info-foreground); |
| 112 | text-decoration: underline; | 276 | text-decoration: underline; |
| 113 | text-decoration-color: rgba(147, 197, 253, 0.4); | 277 | text-decoration-color: color-mix( |
| 278 | in srgb, | ||
| 279 | var(--info-foreground) 40%, | ||
| 280 | transparent | ||
| 281 | ); | ||
| 114 | } | 282 | } |
| 115 | 283 | ||
| 116 | .chat-markdown a:hover { | 284 | .chat-markdown a:hover { |
| 117 | color: #bfdbfe; | 285 | opacity: 0.8; |
| 118 | } | 286 | } |
| 119 | 287 | ||
| 120 | .chat-markdown blockquote { | 288 | .chat-markdown blockquote { |
| 121 | border-left: 2px solid rgba(255, 255, 255, 0.2); | 289 | border-left: 2px solid var(--border); |
| 122 | padding-left: 0.8rem; | 290 | padding-left: 0.8rem; |
| 123 | color: rgba(224, 224, 224, 0.75); | 291 | color: var(--muted-foreground); |
| 124 | } | 292 | } |
| 125 | 293 | ||
| 126 | .chat-markdown :not(pre) > code { | 294 | .chat-markdown :not(pre) > code { |
| 127 | border: 1px solid rgba(255, 255, 255, 0.12); | 295 | border: 1px solid var(--border); |
| 128 | border-radius: 0.375rem; | 296 | border-radius: 0.375rem; |
| 129 | background: rgba(255, 255, 255, 0.07); | 297 | background: var(--muted); |
| 130 | padding: 0.1rem 0.35rem; | 298 | padding: 0.1rem 0.35rem; |
| 131 | color: #f4f4f5; | 299 | color: var(--foreground); |
| 132 | } | 300 | } |
| 133 | 301 | ||
| 134 | .chat-markdown pre { | 302 | .chat-markdown pre { |
| 135 | overflow-x: auto; | 303 | overflow-x: auto; |
| 136 | border: 1px solid rgba(255, 255, 255, 0.12); | 304 | border: 1px solid var(--border); |
| 137 | border-radius: 0.75rem; | 305 | border-radius: 0.75rem; |
| 138 | background: rgba(0, 0, 0, 0.3); | 306 | background: var(--muted); |
| 139 | padding: 0.8rem 0.9rem; | 307 | padding: 0.8rem 0.9rem; |
| 140 | } | 308 | } |
| 141 | 309 | ||
| ... | @@ -153,7 +321,7 @@ label:has(> select#reasoning-effort) select { | ... | @@ -153,7 +321,7 @@ label:has(> select#reasoning-effort) select { |
| 153 | 321 | ||
| 154 | .chat-markdown th, | 322 | .chat-markdown th, |
| 155 | .chat-markdown td { | 323 | .chat-markdown td { |
| 156 | border: 1px solid rgba(255, 255, 255, 0.12); | 324 | border: 1px solid var(--border); |
| 157 | padding: 0.35rem 0.45rem; | 325 | padding: 0.35rem 0.45rem; |
| 158 | text-align: left; | 326 | text-align: left; |
| 159 | } | 327 | } |
apps/renderer/src/lib/utils.ts created+6| ... | @@ -0,0 +1,6 @@ | ||
| 1 | import { type ClassValue, clsx } from "clsx"; | ||
| 2 | import { twMerge } from "tailwind-merge"; | ||
| 3 | |||
| 4 | export function cn(...inputs: ClassValue[]) { | ||
| 5 | return twMerge(clsx(inputs)); | ||
| 6 | } | ||
apps/renderer/tsconfig.json+5-1| ... | @@ -4,7 +4,11 @@ | ... | @@ -4,7 +4,11 @@ |
| 4 | "composite": true, | 4 | "composite": true, |
| 5 | "jsx": "react-jsx", | 5 | "jsx": "react-jsx", |
| 6 | "lib": ["ES2023", "DOM", "DOM.Iterable"], | 6 | "lib": ["ES2023", "DOM", "DOM.Iterable"], |
| 7 | "types": ["vite/client"] | 7 | "exactOptionalPropertyTypes": false, |
| 8 | "types": ["vite/client"], | ||
| 9 | "paths": { | ||
| 10 | "#/*": ["./src/*"] | ||
| 11 | } | ||
| 8 | }, | 12 | }, |
| 9 | "include": ["src", "vite.config.ts"] | 13 | "include": ["src", "vite.config.ts"] |
| 10 | } | 14 | } |
apps/renderer/vite.config.ts+6| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | import path from "node:path"; | ||
| 1 | import tailwindcss from "@tailwindcss/vite"; | 2 | import tailwindcss from "@tailwindcss/vite"; |
| 2 | import react from "@vitejs/plugin-react"; | 3 | import react from "@vitejs/plugin-react"; |
| 3 | import { defineConfig } from "vite"; | 4 | import { defineConfig } from "vite"; |
| ... | @@ -11,6 +12,11 @@ export default defineConfig({ | ... | @@ -11,6 +12,11 @@ export default defineConfig({ |
| 11 | }), | 12 | }), |
| 12 | tailwindcss(), | 13 | tailwindcss(), |
| 13 | ], | 14 | ], |
| 15 | resolve: { | ||
| 16 | alias: { | ||
| 17 | "#": path.resolve(__dirname, "src"), | ||
| 18 | }, | ||
| 19 | }, | ||
| 14 | server: { | 20 | server: { |
| 15 | port: Number(process.env.ELECTRON_RENDERER_PORT ?? 5173), | 21 | port: Number(process.env.ELECTRON_RENDERER_PORT ?? 5173), |
| 16 | strictPort: true, | 22 | strictPort: true, |
bun.lock+96-1| ... | @@ -34,12 +34,17 @@ | ... | @@ -34,12 +34,17 @@ |
| 34 | "version": "0.0.0", | 34 | "version": "0.0.0", |
| 35 | "dependencies": { | 35 | "dependencies": { |
| 36 | "@acme/contracts": "workspace:*", | 36 | "@acme/contracts": "workspace:*", |
| 37 | "@base-ui/react": "^1.1.0", | ||
| 38 | "class-variance-authority": "^0.7.1", | ||
| 39 | "clsx": "^2.1.1", | ||
| 37 | "highlight.js": "^11.11.1", | 40 | "highlight.js": "^11.11.1", |
| 41 | "lucide-react": "^0.563.0", | ||
| 38 | "react": "^19.0.0", | 42 | "react": "^19.0.0", |
| 39 | "react-dom": "^19.0.0", | 43 | "react-dom": "^19.0.0", |
| 40 | "react-markdown": "^10.1.0", | 44 | "react-markdown": "^10.1.0", |
| 41 | "rehype-highlight": "^7.0.2", | 45 | "rehype-highlight": "^7.0.2", |
| 42 | "remark-gfm": "^4.0.1", | 46 | "remark-gfm": "^4.0.1", |
| 47 | "tailwind-merge": "^3.4.0", | ||
| 43 | "zod": "^3.24.1", | 48 | "zod": "^3.24.1", |
| 44 | }, | 49 | }, |
| 45 | "devDependencies": { | 50 | "devDependencies": { |
| ... | @@ -104,12 +109,18 @@ | ... | @@ -104,12 +109,18 @@ |
| 104 | 109 | ||
| 105 | "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], | 110 | "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], |
| 106 | 111 | ||
| 112 | "@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="], | ||
| 113 | |||
| 107 | "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], | 114 | "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], |
| 108 | 115 | ||
| 109 | "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], | 116 | "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], |
| 110 | 117 | ||
| 111 | "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], | 118 | "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], |
| 112 | 119 | ||
| 120 | "@base-ui/react": ["@base-ui/react@1.1.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@base-ui/utils": "0.2.4", "@floating-ui/react-dom": "^2.1.6", "@floating-ui/utils": "^0.2.10", "reselect": "^5.1.1", "tabbable": "^6.4.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-ikcJRNj1mOiF2HZ5jQHrXoVoHcNHdBU5ejJljcBl+VTLoYXR6FidjTN86GjO6hyshi6TZFuNvv0dEOgaOFv6Lw=="], | ||
| 121 | |||
| 122 | "@base-ui/utils": ["@base-ui/utils@0.2.4", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@floating-ui/utils": "^0.2.10", "reselect": "^5.1.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-smZwpMhjO29v+jrZusBSc5T+IJ3vBb9cjIiBjtKcvWmRj9Z4DWGVR3efr1eHR56/bqY5a4qyY9ElkOY5ljo3ng=="], | ||
| 123 | |||
| 113 | "@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="], | 124 | "@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="], |
| 114 | 125 | ||
| 115 | "@electron/node-gyp": ["@electron/node-gyp@github:electron/node-gyp#06b29aa", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "glob": "^8.1.0", "graceful-fs": "^4.2.6", "make-fetch-happen": "^10.2.1", "nopt": "^6.0.0", "proc-log": "^2.0.1", "semver": "^7.3.5", "tar": "^6.2.1", "which": "^2.0.2" }, "bin": "./bin/node-gyp.js" }, "electron-node-gyp-06b29aa"], | 126 | "@electron/node-gyp": ["@electron/node-gyp@github:electron/node-gyp#06b29aa", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "glob": "^8.1.0", "graceful-fs": "^4.2.6", "make-fetch-happen": "^10.2.1", "nopt": "^6.0.0", "proc-log": "^2.0.1", "semver": "^7.3.5", "tar": "^6.2.1", "which": "^2.0.2" }, "bin": "./bin/node-gyp.js" }, "electron-node-gyp-06b29aa"], |
| ... | @@ -168,6 +179,14 @@ | ... | @@ -168,6 +179,14 @@ |
| 168 | 179 | ||
| 169 | "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], | 180 | "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], |
| 170 | 181 | ||
| 182 | "@floating-ui/core": ["@floating-ui/core@1.7.4", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg=="], | ||
| 183 | |||
| 184 | "@floating-ui/dom": ["@floating-ui/dom@1.7.5", "", { "dependencies": { "@floating-ui/core": "^1.7.4", "@floating-ui/utils": "^0.2.10" } }, "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg=="], | ||
| 185 | |||
| 186 | "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.7", "", { "dependencies": { "@floating-ui/dom": "^1.7.5" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg=="], | ||
| 187 | |||
| 188 | "@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="], | ||
| 189 | |||
| 171 | "@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="], | 190 | "@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="], |
| 172 | 191 | ||
| 173 | "@hapi/address": ["@hapi/address@5.1.1", "", { "dependencies": { "@hapi/hoek": "^11.0.2" } }, "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA=="], | 192 | "@hapi/address": ["@hapi/address@5.1.1", "", { "dependencies": { "@hapi/hoek": "^11.0.2" } }, "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA=="], |
| ... | @@ -182,6 +201,16 @@ | ... | @@ -182,6 +201,16 @@ |
| 182 | 201 | ||
| 183 | "@hapi/topo": ["@hapi/topo@6.0.2", "", { "dependencies": { "@hapi/hoek": "^11.0.2" } }, "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg=="], | 202 | "@hapi/topo": ["@hapi/topo@6.0.2", "", { "dependencies": { "@hapi/hoek": "^11.0.2" } }, "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg=="], |
| 184 | 203 | ||
| 204 | "@inquirer/ansi": ["@inquirer/ansi@1.0.2", "", {}, "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ=="], | ||
| 205 | |||
| 206 | "@inquirer/confirm": ["@inquirer/confirm@5.1.21", "", { "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ=="], | ||
| 207 | |||
| 208 | "@inquirer/core": ["@inquirer/core@10.3.2", "", { "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/figures": "^1.0.15", "@inquirer/type": "^3.0.10", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A=="], | ||
| 209 | |||
| 210 | "@inquirer/figures": ["@inquirer/figures@1.0.15", "", {}, "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g=="], | ||
| 211 | |||
| 212 | "@inquirer/type": ["@inquirer/type@3.0.10", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA=="], | ||
| 213 | |||
| 185 | "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], | 214 | "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], |
| 186 | 215 | ||
| 187 | "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], | 216 | "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], |
| ... | @@ -194,10 +223,18 @@ | ... | @@ -194,10 +223,18 @@ |
| 194 | 223 | ||
| 195 | "@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@2.0.0", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg=="], | 224 | "@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@2.0.0", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg=="], |
| 196 | 225 | ||
| 226 | "@mswjs/interceptors": ["@mswjs/interceptors@0.41.2", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-7G0Uf0yK3f2bjElBLGHIQzgRgMESczOMyYVasq1XK8P5HaXtlW4eQhz9MBL+TQILZLaruq+ClGId+hH0w4jvWw=="], | ||
| 227 | |||
| 197 | "@npmcli/fs": ["@npmcli/fs@2.1.2", "", { "dependencies": { "@gar/promisify": "^1.1.3", "semver": "^7.3.5" } }, "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ=="], | 228 | "@npmcli/fs": ["@npmcli/fs@2.1.2", "", { "dependencies": { "@gar/promisify": "^1.1.3", "semver": "^7.3.5" } }, "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ=="], |
| 198 | 229 | ||
| 199 | "@npmcli/move-file": ["@npmcli/move-file@2.0.1", "", { "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ=="], | 230 | "@npmcli/move-file": ["@npmcli/move-file@2.0.1", "", { "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ=="], |
| 200 | 231 | ||
| 232 | "@open-draft/deferred-promise": ["@open-draft/deferred-promise@2.2.0", "", {}, "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA=="], | ||
| 233 | |||
| 234 | "@open-draft/logger": ["@open-draft/logger@0.3.0", "", { "dependencies": { "is-node-process": "^1.2.0", "outvariant": "^1.4.0" } }, "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ=="], | ||
| 235 | |||
| 236 | "@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="], | ||
| 237 | |||
| 201 | "@oxfmt/darwin-arm64": ["@oxfmt/darwin-arm64@0.28.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jmUfF7cNJPw57bEK7sMIqrYRgn4LH428tSgtgLTCtjuGuu1ShREyrkeB7y8HtkXRfhBs4lVY+HMLhqElJvZ6ww=="], | 238 | "@oxfmt/darwin-arm64": ["@oxfmt/darwin-arm64@0.28.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jmUfF7cNJPw57bEK7sMIqrYRgn4LH428tSgtgLTCtjuGuu1ShREyrkeB7y8HtkXRfhBs4lVY+HMLhqElJvZ6ww=="], |
| 202 | 239 | ||
| 203 | "@oxfmt/darwin-x64": ["@oxfmt/darwin-x64@0.28.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-S6vlV8S7jbjzJOSjfVg2CimUC0r7/aHDLdUm/3+/B/SU/s1jV7ivqWkMv1/8EB43d1BBwT9JQ60ZMTkBqeXSFA=="], | 240 | "@oxfmt/darwin-x64": ["@oxfmt/darwin-x64@0.28.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-S6vlV8S7jbjzJOSjfVg2CimUC0r7/aHDLdUm/3+/B/SU/s1jV7ivqWkMv1/8EB43d1BBwT9JQ60ZMTkBqeXSFA=="], |
| ... | @@ -358,6 +395,8 @@ | ... | @@ -358,6 +395,8 @@ |
| 358 | 395 | ||
| 359 | "@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="], | 396 | "@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="], |
| 360 | 397 | ||
| 398 | "@types/statuses": ["@types/statuses@2.0.6", "", {}, "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="], | ||
| 399 | |||
| 361 | "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], | 400 | "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], |
| 362 | 401 | ||
| 363 | "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], | 402 | "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], |
| ... | @@ -460,18 +499,24 @@ | ... | @@ -460,18 +499,24 @@ |
| 460 | 499 | ||
| 461 | "chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], | 500 | "chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], |
| 462 | 501 | ||
| 502 | "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], | ||
| 503 | |||
| 463 | "clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="], | 504 | "clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="], |
| 464 | 505 | ||
| 465 | "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="], | 506 | "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="], |
| 466 | 507 | ||
| 467 | "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], | 508 | "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], |
| 468 | 509 | ||
| 510 | "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], | ||
| 511 | |||
| 469 | "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], | 512 | "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], |
| 470 | 513 | ||
| 471 | "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="], | 514 | "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="], |
| 472 | 515 | ||
| 473 | "clone-response": ["clone-response@1.0.3", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA=="], | 516 | "clone-response": ["clone-response@1.0.3", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA=="], |
| 474 | 517 | ||
| 518 | "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], | ||
| 519 | |||
| 475 | "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], | 520 | "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], |
| 476 | 521 | ||
| 477 | "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], | 522 | "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], |
| ... | @@ -490,6 +535,8 @@ | ... | @@ -490,6 +535,8 @@ |
| 490 | 535 | ||
| 491 | "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], | 536 | "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], |
| 492 | 537 | ||
| 538 | "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], | ||
| 539 | |||
| 493 | "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], | 540 | "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], |
| 494 | 541 | ||
| 495 | "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], | 542 | "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], |
| ... | @@ -614,6 +661,8 @@ | ... | @@ -614,6 +661,8 @@ |
| 614 | 661 | ||
| 615 | "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], | 662 | "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], |
| 616 | 663 | ||
| 664 | "graphql": ["graphql@16.12.0", "", {}, "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ=="], | ||
| 665 | |||
| 617 | "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], | 666 | "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], |
| 618 | 667 | ||
| 619 | "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], | 668 | "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], |
| ... | @@ -632,6 +681,8 @@ | ... | @@ -632,6 +681,8 @@ |
| 632 | 681 | ||
| 633 | "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], | 682 | "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], |
| 634 | 683 | ||
| 684 | "headers-polyfill": ["headers-polyfill@4.0.3", "", {}, "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ=="], | ||
| 685 | |||
| 635 | "highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="], | 686 | "highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="], |
| 636 | 687 | ||
| 637 | "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], | 688 | "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], |
| ... | @@ -680,6 +731,8 @@ | ... | @@ -680,6 +731,8 @@ |
| 680 | 731 | ||
| 681 | "is-lambda": ["is-lambda@1.0.1", "", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="], | 732 | "is-lambda": ["is-lambda@1.0.1", "", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="], |
| 682 | 733 | ||
| 734 | "is-node-process": ["is-node-process@1.2.0", "", {}, "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw=="], | ||
| 735 | |||
| 683 | "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], | 736 | "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], |
| 684 | 737 | ||
| 685 | "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], | 738 | "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], |
| ... | @@ -754,6 +807,8 @@ | ... | @@ -754,6 +807,8 @@ |
| 754 | 807 | ||
| 755 | "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], | 808 | "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], |
| 756 | 809 | ||
| 810 | "lucide-react": ["lucide-react@0.563.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA=="], | ||
| 811 | |||
| 757 | "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], | 812 | "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], |
| 758 | 813 | ||
| 759 | "make-fetch-happen": ["make-fetch-happen@10.2.1", "", { "dependencies": { "agentkeepalive": "^4.2.1", "cacache": "^16.1.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^7.7.1", "minipass": "^3.1.6", "minipass-collect": "^1.0.2", "minipass-fetch": "^2.0.3", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.3", "promise-retry": "^2.0.1", "socks-proxy-agent": "^7.0.0", "ssri": "^9.0.0" } }, "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w=="], | 814 | "make-fetch-happen": ["make-fetch-happen@10.2.1", "", { "dependencies": { "agentkeepalive": "^4.2.1", "cacache": "^16.1.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^7.7.1", "minipass": "^3.1.6", "minipass-collect": "^1.0.2", "minipass-fetch": "^2.0.3", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.3", "promise-retry": "^2.0.1", "socks-proxy-agent": "^7.0.0", "ssri": "^9.0.0" } }, "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w=="], |
| ... | @@ -884,6 +939,10 @@ | ... | @@ -884,6 +939,10 @@ |
| 884 | 939 | ||
| 885 | "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], | 940 | "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], |
| 886 | 941 | ||
| 942 | "msw": ["msw@2.12.9", "", { "dependencies": { "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.41.2", "@open-draft/deferred-promise": "^2.2.0", "@types/statuses": "^2.0.6", "cookie": "^1.0.2", "graphql": "^16.12.0", "headers-polyfill": "^4.0.2", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "rettime": "^0.10.1", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.0", "type-fest": "^5.2.0", "until-async": "^3.0.2", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": ">= 4.8.x" }, "optionalPeers": ["typescript"], "bin": { "msw": "cli/index.js" } }, "sha512-NYbi51C6M3dujGmcmuGemu68jy12KqQPoVWGeroKToLGsBgrwG5ErM8WctoIIg49/EV49SEvYM9WSqO4G7kNeQ=="], | ||
| 943 | |||
| 944 | "mute-stream": ["mute-stream@2.0.0", "", {}, "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA=="], | ||
| 945 | |||
| 887 | "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], | 946 | "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], |
| 888 | 947 | ||
| 889 | "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], | 948 | "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], |
| ... | @@ -916,6 +975,8 @@ | ... | @@ -916,6 +975,8 @@ |
| 916 | 975 | ||
| 917 | "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="], | 976 | "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="], |
| 918 | 977 | ||
| 978 | "outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="], | ||
| 979 | |||
| 919 | "oxfmt": ["oxfmt@0.28.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/darwin-arm64": "0.28.0", "@oxfmt/darwin-x64": "0.28.0", "@oxfmt/linux-arm64-gnu": "0.28.0", "@oxfmt/linux-arm64-musl": "0.28.0", "@oxfmt/linux-x64-gnu": "0.28.0", "@oxfmt/linux-x64-musl": "0.28.0", "@oxfmt/win32-arm64": "0.28.0", "@oxfmt/win32-x64": "0.28.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-3+hhBqPE6Kp22KfJmnstrZbl+KdOVSEu1V0ABaFIg1rYLtrMgrupx9znnHgHLqKxAVHebjTdiCJDk30CXOt6cw=="], | 980 | "oxfmt": ["oxfmt@0.28.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/darwin-arm64": "0.28.0", "@oxfmt/darwin-x64": "0.28.0", "@oxfmt/linux-arm64-gnu": "0.28.0", "@oxfmt/linux-arm64-musl": "0.28.0", "@oxfmt/linux-x64-gnu": "0.28.0", "@oxfmt/linux-x64-musl": "0.28.0", "@oxfmt/win32-arm64": "0.28.0", "@oxfmt/win32-x64": "0.28.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-3+hhBqPE6Kp22KfJmnstrZbl+KdOVSEu1V0ABaFIg1rYLtrMgrupx9znnHgHLqKxAVHebjTdiCJDk30CXOt6cw=="], |
| 920 | 981 | ||
| 921 | "oxlint": ["oxlint@1.43.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "1.43.0", "@oxlint/darwin-x64": "1.43.0", "@oxlint/linux-arm64-gnu": "1.43.0", "@oxlint/linux-arm64-musl": "1.43.0", "@oxlint/linux-x64-gnu": "1.43.0", "@oxlint/linux-x64-musl": "1.43.0", "@oxlint/win32-arm64": "1.43.0", "@oxlint/win32-x64": "1.43.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.11.2" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-xiqTCsKZch+R61DPCjyqUVP2MhkQlRRYxLRBeBDi+dtQJ90MOgdcjIktvDCgXz0bgtx94EQzHEndsizZjMX2OA=="], | 982 | "oxlint": ["oxlint@1.43.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "1.43.0", "@oxlint/darwin-x64": "1.43.0", "@oxlint/linux-arm64-gnu": "1.43.0", "@oxlint/linux-arm64-musl": "1.43.0", "@oxlint/linux-x64-gnu": "1.43.0", "@oxlint/linux-x64-musl": "1.43.0", "@oxlint/win32-arm64": "1.43.0", "@oxlint/win32-x64": "1.43.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.11.2" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-xiqTCsKZch+R61DPCjyqUVP2MhkQlRRYxLRBeBDi+dtQJ90MOgdcjIktvDCgXz0bgtx94EQzHEndsizZjMX2OA=="], |
| ... | @@ -930,6 +991,8 @@ | ... | @@ -930,6 +991,8 @@ |
| 930 | 991 | ||
| 931 | "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], | 992 | "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], |
| 932 | 993 | ||
| 994 | "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], | ||
| 995 | |||
| 933 | "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], | 996 | "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], |
| 934 | 997 | ||
| 935 | "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], | 998 | "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], |
| ... | @@ -994,6 +1057,8 @@ | ... | @@ -994,6 +1057,8 @@ |
| 994 | 1057 | ||
| 995 | "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], | 1058 | "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], |
| 996 | 1059 | ||
| 1060 | "reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="], | ||
| 1061 | |||
| 997 | "resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="], | 1062 | "resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="], |
| 998 | 1063 | ||
| 999 | "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], | 1064 | "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], |
| ... | @@ -1004,6 +1069,8 @@ | ... | @@ -1004,6 +1069,8 @@ |
| 1004 | 1069 | ||
| 1005 | "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], | 1070 | "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], |
| 1006 | 1071 | ||
| 1072 | "rettime": ["rettime@0.10.1", "", {}, "sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw=="], | ||
| 1073 | |||
| 1007 | "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], | 1074 | "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], |
| 1008 | 1075 | ||
| 1009 | "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], | 1076 | "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], |
| ... | @@ -1052,8 +1119,12 @@ | ... | @@ -1052,8 +1119,12 @@ |
| 1052 | 1119 | ||
| 1053 | "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], | 1120 | "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], |
| 1054 | 1121 | ||
| 1122 | "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], | ||
| 1123 | |||
| 1055 | "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], | 1124 | "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], |
| 1056 | 1125 | ||
| 1126 | "strict-event-emitter": ["strict-event-emitter@0.5.1", "", {}, "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ=="], | ||
| 1127 | |||
| 1057 | "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], | 1128 | "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], |
| 1058 | 1129 | ||
| 1059 | "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], | 1130 | "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], |
| ... | @@ -1074,6 +1145,12 @@ | ... | @@ -1074,6 +1145,12 @@ |
| 1074 | 1145 | ||
| 1075 | "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], | 1146 | "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], |
| 1076 | 1147 | ||
| 1148 | "tabbable": ["tabbable@6.4.0", "", {}, "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg=="], | ||
| 1149 | |||
| 1150 | "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], | ||
| 1151 | |||
| 1152 | "tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="], | ||
| 1153 | |||
| 1077 | "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="], | 1154 | "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="], |
| 1078 | 1155 | ||
| 1079 | "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], | 1156 | "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], |
| ... | @@ -1096,8 +1173,14 @@ | ... | @@ -1096,8 +1173,14 @@ |
| 1096 | 1173 | ||
| 1097 | "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="], | 1174 | "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="], |
| 1098 | 1175 | ||
| 1176 | "tldts": ["tldts@7.0.23", "", { "dependencies": { "tldts-core": "^7.0.23" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw=="], | ||
| 1177 | |||
| 1178 | "tldts-core": ["tldts-core@7.0.23", "", {}, "sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ=="], | ||
| 1179 | |||
| 1099 | "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], | 1180 | "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], |
| 1100 | 1181 | ||
| 1182 | "tough-cookie": ["tough-cookie@6.0.0", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w=="], | ||
| 1183 | |||
| 1101 | "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], | 1184 | "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], |
| 1102 | 1185 | ||
| 1103 | "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], | 1186 | "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], |
| ... | @@ -1124,7 +1207,7 @@ | ... | @@ -1124,7 +1207,7 @@ |
| 1124 | 1207 | ||
| 1125 | "turbo-windows-arm64": ["turbo-windows-arm64@2.8.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-afTUGKBRmOJU1smQSBnFGcbq0iabAPwh1uXu2BVk7BREg30/1gMnJh9DFEQTah+UD3n3ru8V55J83RQNFfqoyw=="], | 1208 | "turbo-windows-arm64": ["turbo-windows-arm64@2.8.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-afTUGKBRmOJU1smQSBnFGcbq0iabAPwh1uXu2BVk7BREg30/1gMnJh9DFEQTah+UD3n3ru8V55J83RQNFfqoyw=="], |
| 1126 | 1209 | ||
| 1127 | "type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], | 1210 | "type-fest": ["type-fest@5.4.4", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw=="], |
| 1128 | 1211 | ||
| 1129 | "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], | 1212 | "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], |
| 1130 | 1213 | ||
| ... | @@ -1154,8 +1237,12 @@ | ... | @@ -1154,8 +1237,12 @@ |
| 1154 | 1237 | ||
| 1155 | "unixify": ["unixify@1.0.0", "", { "dependencies": { "normalize-path": "^2.1.1" } }, "sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg=="], | 1238 | "unixify": ["unixify@1.0.0", "", { "dependencies": { "normalize-path": "^2.1.1" } }, "sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg=="], |
| 1156 | 1239 | ||
| 1240 | "until-async": ["until-async@3.0.2", "", {}, "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw=="], | ||
| 1241 | |||
| 1157 | "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], | 1242 | "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], |
| 1158 | 1243 | ||
| 1244 | "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], | ||
| 1245 | |||
| 1159 | "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], | 1246 | "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], |
| 1160 | 1247 | ||
| 1161 | "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], | 1248 | "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], |
| ... | @@ -1192,6 +1279,8 @@ | ... | @@ -1192,6 +1279,8 @@ |
| 1192 | 1279 | ||
| 1193 | "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], | 1280 | "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], |
| 1194 | 1281 | ||
| 1282 | "yoctocolors-cjs": ["yoctocolors-cjs@2.1.3", "", {}, "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw=="], | ||
| 1283 | |||
| 1195 | "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], | 1284 | "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], |
| 1196 | 1285 | ||
| 1197 | "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], | 1286 | "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], |
| ... | @@ -1208,6 +1297,10 @@ | ... | @@ -1208,6 +1297,10 @@ |
| 1208 | 1297 | ||
| 1209 | "@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], | 1298 | "@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], |
| 1210 | 1299 | ||
| 1300 | "@inquirer/core/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], | ||
| 1301 | |||
| 1302 | "@inquirer/core/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], | ||
| 1303 | |||
| 1211 | "@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], | 1304 | "@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], |
| 1212 | 1305 | ||
| 1213 | "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], | 1306 | "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], |
| ... | @@ -1254,6 +1347,8 @@ | ... | @@ -1254,6 +1347,8 @@ |
| 1254 | 1347 | ||
| 1255 | "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], | 1348 | "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], |
| 1256 | 1349 | ||
| 1350 | "serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], | ||
| 1351 | |||
| 1257 | "ssri/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], | 1352 | "ssri/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], |
| 1258 | 1353 | ||
| 1259 | "vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], | 1354 | "vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], |