| author | |
| committer | |
| log | 96906805f1677a3b064ebd2c40902cbb48337a23 |
| tree | 98a53b8da83377d5c92e0e697bd6d63251ed62bf |
| parent | 73b2e8fdd47807fcc0fc5bfe4928187035b71242 |
| signature | Signed by PGP key B5690EEEBB952194 |
6 files changed, 232 insertions(+), 119 deletions(-)
apps/web/src/components/WorkspaceBreadcrumb.tsx created+54| ... | ... | @@ -0,0 +1,54 @@ |
| 1 | import type { ReactNode } from "react"; | |
| 2 | ||
| 3 | import { cn } from "../lib/utils"; | |
| 4 | ||
| 5 | interface WorkspaceBreadcrumbProps { | |
| 6 | readonly ariaLabel: string; | |
| 7 | readonly children: ReactNode; | |
| 8 | readonly className?: string; | |
| 9 | } | |
| 10 | ||
| 11 | export function WorkspaceBreadcrumb({ ariaLabel, children, className }: WorkspaceBreadcrumbProps) { | |
| 12 | return ( | |
| 13 | <nav aria-label={ariaLabel} className={cn("min-w-0", className)}> | |
| 14 | {/* Keep the flexible container draggable in Electron. Interactive | |
| 15 | descendants are excluded by the shared .drag-region CSS rules. */} | |
| 16 | <ol className="m-0 flex min-w-0 list-none items-center gap-2 p-0 text-sm sm:gap-3"> | |
| 17 | {children} | |
| 18 | </ol> | |
| 19 | </nav> | |
| 20 | ); | |
| 21 | } | |
| 22 | ||
| 23 | interface WorkspaceBreadcrumbItemProps { | |
| 24 | readonly children: ReactNode; | |
| 25 | readonly className?: string; | |
| 26 | readonly current?: boolean; | |
| 27 | } | |
| 28 | ||
| 29 | export function WorkspaceBreadcrumbItem({ | |
| 30 | children, | |
| 31 | className, | |
| 32 | current = false, | |
| 33 | }: WorkspaceBreadcrumbItemProps) { | |
| 34 | return ( | |
| 35 | <li | |
| 36 | aria-current={current ? "page" : undefined} | |
| 37 | className={cn( | |
| 38 | "flex min-w-0 items-center font-medium", | |
| 39 | current ? "text-foreground" : "shrink-0 text-muted-foreground", | |
| 40 | className, | |
| 41 | )} | |
| 42 | > | |
| 43 | {children} | |
| 44 | </li> | |
| 45 | ); | |
| 46 | } | |
| 47 | ||
| 48 | export function WorkspaceBreadcrumbSeparator() { | |
| 49 | return ( | |
| 50 | <li aria-hidden="true" className="flex shrink-0 items-center text-icon-muted"> | |
| 51 | / | |
| 52 | </li> | |
| 53 | ); | |
| 54 | } |
apps/web/src/components/chat/ChatHeader.tsx+71-69| ... | ... | @@ -36,6 +36,11 @@ import { useThreadActionMenu } from "~/hooks/useThreadActionMenu"; |
| 36 | 36 | import { threadEnvironment } from "../../state/threads"; |
| 37 | 37 | import { useAtomCommand } from "../../state/use-atom-command"; |
| 38 | 38 | import { ProjectFavicon } from "../ProjectFavicon"; |
| 39 | import { | |
| 40 | WorkspaceBreadcrumb, | |
| 41 | WorkspaceBreadcrumbItem, | |
| 42 | WorkspaceBreadcrumbSeparator, | |
| 43 | } from "../WorkspaceBreadcrumb"; | |
| 39 | 44 | import { cn } from "~/lib/utils"; |
| 40 | 45 | |
| 41 | 46 | interface ChatHeaderProps { |
| ... | ... | @@ -211,91 +216,88 @@ export const ChatHeader = memo(function ChatHeader({ |
| 211 | 216 | className="@container/header-actions flex min-w-0 flex-1 items-center gap-2 sm:gap-3" |
| 212 | 217 | onContextMenu={handleHeaderContextMenu} |
| 213 | 218 | > |
| 214 | <div className="flex min-w-0 flex-1 items-center gap-2 overflow-hidden sm:gap-3"> | |
| 219 | <WorkspaceBreadcrumb ariaLabel="Thread breadcrumb" className="flex-1"> | |
| 215 | 220 | {/* The project always leads the header: knowing which project a |
| 216 | 221 | thread lives in is priority zero, and the thread title alone |
| 217 | 222 | doesn't answer it. */} |
| 218 | 223 | {activeProjectName ? ( |
| 219 | <span className="inline-flex shrink-0 items-center gap-2"> | |
| 224 | <> | |
| 225 | <WorkspaceBreadcrumbItem> | |
| 226 | <Tooltip> | |
| 227 | <TooltipTrigger | |
| 228 | render={ | |
| 229 | <button | |
| 230 | type="button" | |
| 231 | aria-label={`New thread in ${activeProjectName}`} | |
| 232 | onClick={onNewThreadInProject} | |
| 233 | className="inline-flex min-w-0 cursor-pointer items-center gap-1.5 rounded-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring" | |
| 234 | /> | |
| 235 | } | |
| 236 | > | |
| 237 | <ProjectFavicon | |
| 238 | environmentId={activeThreadEnvironmentId} | |
| 239 | cwd={activeProjectCwd ?? ""} | |
| 240 | faviconPath={activeProjectFaviconPath} | |
| 241 | className="size-3.5" | |
| 242 | /> | |
| 243 | <span className="max-w-40 truncate">{activeProjectName}</span> | |
| 244 | </TooltipTrigger> | |
| 245 | <TooltipPopup side="top">New thread in {activeProjectName}</TooltipPopup> | |
| 246 | </Tooltip> | |
| 247 | </WorkspaceBreadcrumbItem> | |
| 248 | <WorkspaceBreadcrumbSeparator /> | |
| 249 | </> | |
| 250 | ) : null} | |
| 251 | <WorkspaceBreadcrumbItem current className="flex-1"> | |
| 252 | {renamingTitle !== null ? ( | |
| 253 | <input | |
| 254 | autoFocus | |
| 255 | aria-label="Thread title" | |
| 256 | className="min-w-0 flex-1 rounded-sm bg-transparent text-sm font-medium text-foreground outline-none ring-1 ring-ring/50 focus:ring-ring" | |
| 257 | defaultValue={renamingTitle} | |
| 258 | onBlur={(event) => { | |
| 259 | if (renameCommittedRef.current) return; | |
| 260 | commitRename(event.currentTarget.value); | |
| 261 | }} | |
| 262 | onFocus={(event) => event.currentTarget.select()} | |
| 263 | onKeyDown={handleRenameKeyDown} | |
| 264 | /> | |
| 265 | ) : isServerThread ? ( | |
| 220 | 266 | <Tooltip> |
| 221 | 267 | <TooltipTrigger |
| 222 | 268 | render={ |
| 223 | 269 | <button |
| 270 | ref={titleButtonRef} | |
| 224 | 271 | type="button" |
| 225 | aria-label={`New thread in ${activeProjectName}`} | |
| 226 | onClick={onNewThreadInProject} | |
| 227 | className="inline-flex min-w-0 cursor-pointer items-center gap-1.5 rounded-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring" | |
| 272 | aria-label={`Thread actions for ${activeThreadTitle}`} | |
| 273 | aria-haspopup="menu" | |
| 274 | onClick={openMenuFromTitle} | |
| 275 | className="group/thread-title inline-flex min-w-0 max-w-full cursor-pointer items-center gap-1 rounded-sm text-left focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring" | |
| 228 | 276 | /> |
| 229 | 277 | } |
| 230 | 278 | > |
| 231 | <ProjectFavicon | |
| 232 | environmentId={activeThreadEnvironmentId} | |
| 233 | cwd={activeProjectCwd ?? ""} | |
| 234 | faviconPath={activeProjectFaviconPath} | |
| 235 | className="size-3.5" | |
| 279 | <h2 className="min-w-0 truncate">{activeThreadTitle}</h2> | |
| 280 | <ChevronDownIcon | |
| 281 | aria-hidden | |
| 282 | className="size-3.5 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover/thread-title:opacity-100 group-focus-visible/thread-title:opacity-100" | |
| 236 | 283 | /> |
| 237 | <span className="max-w-40 truncate text-sm font-medium">{activeProjectName}</span> | |
| 238 | 284 | </TooltipTrigger> |
| 239 | <TooltipPopup side="top">New thread in {activeProjectName}</TooltipPopup> | |
| 285 | <TooltipPopup side="top">{activeThreadTitle}</TooltipPopup> | |
| 240 | 286 | </Tooltip> |
| 241 | <span aria-hidden className="text-icon-muted"> | |
| 242 | / | |
| 243 | </span> | |
| 244 | </span> | |
| 245 | ) : null} | |
| 246 | {renamingTitle !== null ? ( | |
| 247 | <input | |
| 248 | autoFocus | |
| 249 | aria-label="Thread title" | |
| 250 | className="min-w-0 flex-1 rounded-sm bg-transparent text-sm font-medium text-foreground outline-none ring-1 ring-ring/50 focus:ring-ring" | |
| 251 | defaultValue={renamingTitle} | |
| 252 | onBlur={(event) => { | |
| 253 | if (renameCommittedRef.current) return; | |
| 254 | commitRename(event.currentTarget.value); | |
| 255 | }} | |
| 256 | onFocus={(event) => event.currentTarget.select()} | |
| 257 | onKeyDown={handleRenameKeyDown} | |
| 258 | /> | |
| 259 | ) : isServerThread ? ( | |
| 260 | <Tooltip> | |
| 261 | <TooltipTrigger | |
| 262 | render={ | |
| 263 | <button | |
| 264 | ref={titleButtonRef} | |
| 265 | type="button" | |
| 266 | aria-label={`Thread actions for ${activeThreadTitle}`} | |
| 267 | aria-haspopup="menu" | |
| 268 | onClick={openMenuFromTitle} | |
| 269 | className="group/thread-title inline-flex min-w-0 cursor-pointer items-center gap-1 rounded-sm text-left focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring" | |
| 270 | /> | |
| 271 | } | |
| 272 | > | |
| 273 | <h2 className="min-w-0 truncate text-sm font-medium text-foreground"> | |
| 274 | {activeThreadTitle} | |
| 275 | </h2> | |
| 276 | <ChevronDownIcon | |
| 277 | aria-hidden | |
| 278 | className="size-3.5 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover/thread-title:opacity-100 group-focus-visible/thread-title:opacity-100" | |
| 287 | ) : ( | |
| 288 | <Tooltip> | |
| 289 | <TooltipTrigger | |
| 290 | render={ | |
| 291 | <h2 aria-label={activeThreadTitle} className="min-w-0 flex-1 truncate"> | |
| 292 | {activeThreadTitle} | |
| 293 | </h2> | |
| 294 | } | |
| 279 | 295 | /> |
| 280 | </TooltipTrigger> | |
| 281 | <TooltipPopup side="top">{activeThreadTitle}</TooltipPopup> | |
| 282 | </Tooltip> | |
| 283 | ) : ( | |
| 284 | <Tooltip> | |
| 285 | <TooltipTrigger | |
| 286 | render={ | |
| 287 | <h2 | |
| 288 | aria-label={activeThreadTitle} | |
| 289 | className="min-w-0 flex-1 truncate text-sm font-medium text-foreground" | |
| 290 | > | |
| 291 | {activeThreadTitle} | |
| 292 | </h2> | |
| 293 | } | |
| 294 | /> | |
| 295 | <TooltipPopup side="top">{activeThreadTitle}</TooltipPopup> | |
| 296 | </Tooltip> | |
| 297 | )} | |
| 298 | </div> | |
| 296 | <TooltipPopup side="top">{activeThreadTitle}</TooltipPopup> | |
| 297 | </Tooltip> | |
| 298 | )} | |
| 299 | </WorkspaceBreadcrumbItem> | |
| 300 | </WorkspaceBreadcrumb> | |
| 299 | 301 | <div |
| 300 | 302 | data-chat-header-actions |
| 301 | 303 | className={cn( |
apps/web/src/components/settings/ProjectSettingsPanel.tsx+56-39| ... | ... | @@ -13,6 +13,7 @@ import { |
| 13 | 13 | selectProjectGroupingSettings, |
| 14 | 14 | } from "../../logicalProject"; |
| 15 | 15 | import type { |
| 16 | ContextMenuItem, | |
| 16 | 17 | ModelSelection, |
| 17 | 18 | ProviderDriverKind, |
| 18 | 19 | SidebarProjectGroupingMode, |
| ... | ... | @@ -25,7 +26,14 @@ import { DEFAULT_RESOLVED_KEYBINDINGS } from "@t3tools/shared/keybindings"; |
| 25 | 26 | import { useCanGoBack, useNavigate } from "@tanstack/react-router"; |
| 26 | 27 | import * as Cause from "effect/Cause"; |
| 27 | 28 | import { ChevronDownIcon, CopyIcon, PlusIcon, SettingsIcon, Trash2Icon } from "lucide-react"; |
| 28 | import { useCallback, useEffect, useMemo, useRef, useState } from "react"; | |
| 29 | import { | |
| 30 | useCallback, | |
| 31 | useEffect, | |
| 32 | useMemo, | |
| 33 | useRef, | |
| 34 | useState, | |
| 35 | type MouseEvent as ReactMouseEvent, | |
| 36 | } from "react"; | |
| 29 | 37 | |
| 30 | 38 | import { useComposerDraftStore } from "../../composerDraftStore"; |
| 31 | 39 | import { isElectron } from "../../env"; |
| ... | ... | @@ -89,6 +97,11 @@ import { |
| 89 | 97 | import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select"; |
| 90 | 98 | import { SidebarInset } from "../ui/sidebar"; |
| 91 | 99 | import { stackedThreadToast, toastManager } from "../ui/toast"; |
| 100 | import { | |
| 101 | WorkspaceBreadcrumb, | |
| 102 | WorkspaceBreadcrumbItem, | |
| 103 | WorkspaceBreadcrumbSeparator, | |
| 104 | } from "../WorkspaceBreadcrumb"; | |
| 92 | 105 | import { |
| 93 | 106 | SettingResetButton, |
| 94 | 107 | SettingsPageContainer, |
| ... | ... | @@ -191,48 +204,52 @@ function ProjectSettingsBreadcrumb({ projectKey }: { projectKey: string }) { |
| 191 | 204 | const groups = useSettingsProjectGroups(); |
| 192 | 205 | const navigate = useNavigate(); |
| 193 | 206 | const selected = groups.find((group) => group.projectKey === projectKey) ?? null; |
| 207 | const openProjectMenu = (event: ReactMouseEvent<HTMLButtonElement>) => { | |
| 208 | const api = readLocalApi(); | |
| 209 | if (!api) return; | |
| 210 | ||
| 211 | const rect = event.currentTarget.getBoundingClientRect(); | |
| 212 | const items: ContextMenuItem<string>[] = groups.map((group) => ({ | |
| 213 | id: group.projectKey, | |
| 214 | label: group.displayName, | |
| 215 | })); | |
| 216 | void settlePromise(() => | |
| 217 | api.contextMenu.show(items, { x: rect.left, y: rect.bottom + 4 }), | |
| 218 | ).then((clicked) => { | |
| 219 | if (clicked._tag === "Failure" || clicked.value === null) return; | |
| 220 | void navigate({ | |
| 221 | to: "/projects/$projectKey", | |
| 222 | params: { projectKey: clicked.value }, | |
| 223 | replace: true, | |
| 224 | hashScrollIntoView: false, | |
| 225 | }); | |
| 226 | }); | |
| 227 | }; | |
| 194 | 228 | |
| 195 | 229 | return ( |
| 196 | <nav | |
| 197 | aria-label="Project settings breadcrumb" | |
| 198 | className="flex min-w-0 items-center gap-2 overflow-hidden [-webkit-app-region:no-drag] sm:gap-3" | |
| 199 | > | |
| 200 | <span className="shrink-0 text-sm font-medium text-muted-foreground">Projects</span> | |
| 201 | <span aria-hidden="true" className="shrink-0 text-icon-muted"> | |
| 202 | / | |
| 203 | </span> | |
| 204 | {selected ? ( | |
| 205 | <Select | |
| 206 | value={selected.projectKey} | |
| 207 | onValueChange={(value) => | |
| 208 | void navigate({ | |
| 209 | to: "/projects/$projectKey", | |
| 210 | params: { projectKey: String(value) }, | |
| 211 | replace: true, | |
| 212 | hashScrollIntoView: false, | |
| 213 | }) | |
| 214 | } | |
| 215 | > | |
| 216 | <SelectTrigger | |
| 230 | <WorkspaceBreadcrumb ariaLabel="Project settings breadcrumb"> | |
| 231 | <WorkspaceBreadcrumbItem>Projects</WorkspaceBreadcrumbItem> | |
| 232 | <WorkspaceBreadcrumbSeparator /> | |
| 233 | <WorkspaceBreadcrumbItem current> | |
| 234 | {selected ? ( | |
| 235 | <button | |
| 236 | type="button" | |
| 237 | aria-haspopup="menu" | |
| 217 | 238 | aria-label="Switch project" |
| 218 | className="h-auto min-h-0 min-w-0 max-w-64 gap-1 border-0 px-0 py-0 text-sm font-medium text-foreground sm:h-auto sm:min-h-0 sm:text-sm" | |
| 219 | size="xs" | |
| 220 | variant="ghost" | |
| 239 | onClick={openProjectMenu} | |
| 240 | className="group/project-title inline-flex min-w-0 max-w-64 cursor-pointer items-center gap-1 rounded-sm text-left focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring" | |
| 221 | 241 | > |
| 222 | <SelectValue>{selected.displayName}</SelectValue> | |
| 223 | </SelectTrigger> | |
| 224 | <SelectPopup align="start" alignItemWithTrigger={false}> | |
| 225 | {groups.map((candidate) => ( | |
| 226 | <SelectItem key={candidate.projectKey} hideIndicator value={candidate.projectKey}> | |
| 227 | {candidate.displayName} | |
| 228 | </SelectItem> | |
| 229 | ))} | |
| 230 | </SelectPopup> | |
| 231 | </Select> | |
| 232 | ) : ( | |
| 233 | <span className="truncate text-sm text-muted-foreground">Unavailable project</span> | |
| 234 | )} | |
| 235 | </nav> | |
| 242 | <span className="min-w-0 truncate">{selected.displayName}</span> | |
| 243 | <ChevronDownIcon | |
| 244 | aria-hidden | |
| 245 | className="size-3.5 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover/project-title:opacity-100 group-focus-visible/project-title:opacity-100" | |
| 246 | /> | |
| 247 | </button> | |
| 248 | ) : ( | |
| 249 | <span className="truncate text-muted-foreground">Unavailable project</span> | |
| 250 | )} | |
| 251 | </WorkspaceBreadcrumbItem> | |
| 252 | </WorkspaceBreadcrumb> | |
| 236 | 253 | ); |
| 237 | 254 | } |
| 238 | 255 |
apps/web/src/components/settings/SettingsBreadcrumb.tsx created+34| ... | ... | @@ -0,0 +1,34 @@ |
| 1 | import { | |
| 2 | WorkspaceBreadcrumb, | |
| 3 | WorkspaceBreadcrumbItem, | |
| 4 | WorkspaceBreadcrumbSeparator, | |
| 5 | } from "../WorkspaceBreadcrumb"; | |
| 6 | import { SETTINGS_SECTION_LABELS } from "./settingsSearch"; | |
| 7 | ||
| 8 | const SETTINGS_BREADCRUMB_LABELS: Readonly<Record<string, string>> = { | |
| 9 | ...SETTINGS_SECTION_LABELS, | |
| 10 | "/settings/diagnostics": "Diagnostics", | |
| 11 | }; | |
| 12 | ||
| 13 | function settingsBreadcrumbLabel(pathname: string): string | null { | |
| 14 | const normalizedPathname = pathname.replace(/\/+$/, "") || "/"; | |
| 15 | return SETTINGS_BREADCRUMB_LABELS[normalizedPathname] ?? null; | |
| 16 | } | |
| 17 | ||
| 18 | export function SettingsBreadcrumb({ pathname }: { pathname: string }) { | |
| 19 | const sectionLabel = settingsBreadcrumbLabel(pathname); | |
| 20 | ||
| 21 | return ( | |
| 22 | <WorkspaceBreadcrumb ariaLabel="Settings breadcrumb"> | |
| 23 | {sectionLabel ? ( | |
| 24 | <> | |
| 25 | <WorkspaceBreadcrumbItem>Settings</WorkspaceBreadcrumbItem> | |
| 26 | <WorkspaceBreadcrumbSeparator /> | |
| 27 | </> | |
| 28 | ) : null} | |
| 29 | <WorkspaceBreadcrumbItem current className="truncate"> | |
| 30 | {sectionLabel ?? "Settings"} | |
| 31 | </WorkspaceBreadcrumbItem> | |
| 32 | </WorkspaceBreadcrumb> | |
| 33 | ); | |
| 34 | } |
apps/web/src/components/usage/UsagePage.tsx+7-2| ... | ... | @@ -16,6 +16,7 @@ import { |
| 16 | 16 | } from "@t3tools/shared/usageFormat"; |
| 17 | 17 | import { ScrollArea } from "../ui/scroll-area"; |
| 18 | 18 | import { SidebarInset } from "../ui/sidebar"; |
| 19 | import { WorkspaceBreadcrumb, WorkspaceBreadcrumbItem } from "../WorkspaceBreadcrumb"; | |
| 19 | 20 | import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "../../workspaceTitlebar"; |
| 20 | 21 | import { UsageChartLegend, UsageProviderChart, type UsageChartMetric } from "./UsageProviderChart"; |
| 21 | 22 | import { PROVIDER_COLOR, PROVIDER_LABEL, PROVIDER_MARK, PROVIDER_ORDER } from "./usageProviders"; |
| ... | ... | @@ -71,7 +72,9 @@ export function UsagePage() { |
| 71 | 72 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, |
| 72 | 73 | )} |
| 73 | 74 | > |
| 74 | <h1 className="text-sm font-medium text-foreground">Usage</h1> | |
| 75 | <WorkspaceBreadcrumb ariaLabel="Usage breadcrumb"> | |
| 76 | <WorkspaceBreadcrumbItem current>Usage</WorkspaceBreadcrumbItem> | |
| 77 | </WorkspaceBreadcrumb> | |
| 75 | 78 | </header> |
| 76 | 79 | )} |
| 77 | 80 | |
| ... | ... | @@ -82,7 +85,9 @@ export function UsagePage() { |
| 82 | 85 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, |
| 83 | 86 | )} |
| 84 | 87 | > |
| 85 | <h1 className="text-sm font-medium text-foreground">Usage</h1> | |
| 88 | <WorkspaceBreadcrumb ariaLabel="Usage breadcrumb"> | |
| 89 | <WorkspaceBreadcrumbItem current>Usage</WorkspaceBreadcrumbItem> | |
| 90 | </WorkspaceBreadcrumb> | |
| 86 | 91 | </div> |
| 87 | 92 | )} |
| 88 | 93 |
apps/web/src/routes/settings.tsx+10-9| ... | ... | @@ -10,6 +10,7 @@ import { |
| 10 | 10 | import { useCallback, useEffect, useState } from "react"; |
| 11 | 11 | |
| 12 | 12 | import { useSettingsRestore } from "../components/settings/SettingsPanels"; |
| 13 | import { SettingsBreadcrumb } from "../components/settings/SettingsBreadcrumb"; | |
| 13 | 14 | import { Button } from "../components/ui/button"; |
| 14 | 15 | import { SidebarInset } from "../components/ui/sidebar"; |
| 15 | 16 | import { isElectron } from "../env"; |
| ... | ... | @@ -79,7 +80,7 @@ function SettingsContentLayout() { |
| 79 | 80 | )} |
| 80 | 81 | > |
| 81 | 82 | <div className="flex w-full items-center gap-2"> |
| 82 | <span className="text-sm font-medium text-foreground">Settings</span> | |
| 83 | <SettingsBreadcrumb pathname={location.pathname} /> | |
| 83 | 84 | {showRestoreDefaults ? ( |
| 84 | 85 | <div className="ms-auto flex items-center gap-2"> |
| 85 | 86 | <RestoreDefaultsButton onRestored={handleRestored} /> |
| ... | ... | @@ -96,14 +97,14 @@ function SettingsContentLayout() { |
| 96 | 97 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, |
| 97 | 98 | )} |
| 98 | 99 | > |
| 99 | <span className="text-xs font-medium tracking-wide text-muted-foreground/70"> | |
| 100 | Settings | |
| 101 | </span> | |
| 102 | {showRestoreDefaults ? ( | |
| 103 | <div className="ms-auto flex items-center gap-2"> | |
| 104 | <RestoreDefaultsButton onRestored={handleRestored} /> | |
| 105 | </div> | |
| 106 | ) : null} | |
| 100 | <div className="flex w-full items-center gap-2"> | |
| 101 | <SettingsBreadcrumb pathname={location.pathname} /> | |
| 102 | {showRestoreDefaults ? ( | |
| 103 | <div className="ms-auto flex items-center gap-2"> | |
| 104 | <RestoreDefaultsButton onRestored={handleRestored} /> | |
| 105 | </div> | |
| 106 | ) : null} | |
| 107 | </div> | |
| 107 | 108 | </div> |
| 108 | 109 | )} |
| 109 | 110 |