feat: better icons and overlay panes
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { Check, Palette } from 'lucide-react'
|
||||
|
||||
import { triggerHaptic } from '@/lib/haptics'
|
||||
import { Check, Palette } from '@/lib/icons'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useTheme } from '@/themes/context'
|
||||
import { BUILTIN_THEMES } from '@/themes/presets'
|
||||
|
||||
@@ -10,8 +10,7 @@ import {
|
||||
Sparkles,
|
||||
Sun,
|
||||
Wrench
|
||||
} from 'lucide-react'
|
||||
|
||||
} from '@/lib/icons'
|
||||
import type { ThemeMode } from '@/themes/context'
|
||||
|
||||
import type { DesktopConfigSection } from './types'
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { Download, KeyRound, Package, RotateCcw, Search, Upload, X } from 'lucide-react'
|
||||
import { IconDownload, IconRefresh, IconUpload } from '@tabler/icons-react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { getHermesConfigDefaults, getHermesConfigRecord, saveHermesConfig } from '@/hermes'
|
||||
import { triggerHaptic } from '@/lib/haptics'
|
||||
import { KeyRound, Package } from '@/lib/icons'
|
||||
import { notifyError } from '@/store/notifications'
|
||||
|
||||
import { OverlayIconButton } from '../overlays/overlay-chrome'
|
||||
import { OverlaySearchInput } from '../overlays/overlay-search-input'
|
||||
import { OverlayMain, OverlayNavItem, OverlaySidebar, OverlaySplitLayout } from '../overlays/overlay-split-layout'
|
||||
import { OverlayView } from '../overlays/overlay-view'
|
||||
|
||||
import { AppearanceSettings } from './appearance-settings'
|
||||
import { ConfigSettings } from './config-settings'
|
||||
import { SEARCH_PLACEHOLDER, SECTIONS } from './constants'
|
||||
import { KeysSettings } from './keys-settings'
|
||||
import { NavLink } from './primitives'
|
||||
import { ToolsSettings } from './tools-settings'
|
||||
import type { SettingsPageProps, SettingsQueryKey, SettingsView as SettingsViewId } from './types'
|
||||
|
||||
@@ -84,55 +87,26 @@ export function SettingsView({ onClose, onConfigSaved }: SettingsPageProps) {
|
||||
}, [onClose])
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-60 flex min-h-0 flex-col bg-background/98 p-0.75 backdrop-blur-xl">
|
||||
<div className="pointer-events-none fixed inset-x-0 top-0 z-10 h-[calc(var(--titlebar-height)+0.1875rem)] [-webkit-app-region:drag]">
|
||||
<div className="pointer-events-auto absolute left-1/2 top-[calc(1rem+var(--titlebar-height)/2)] w-[min(36rem,calc(100vw-32rem))] min-w-80 -translate-x-1/2 -translate-y-1/2 [-webkit-app-region:no-drag]">
|
||||
<Search className="pointer-events-none absolute left-3 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground/80" />
|
||||
<Input
|
||||
className="h-9 rounded-full border-transparent bg-background py-2 pl-8 pr-20 text-sm shadow-header focus-visible:bg-background"
|
||||
onChange={e => setQuery(e.target.value)}
|
||||
placeholder={SEARCH_PLACEHOLDER[queryKey]}
|
||||
ref={searchInputRef}
|
||||
value={query}
|
||||
/>
|
||||
{query ? (
|
||||
<Button
|
||||
aria-label="Clear search"
|
||||
className="absolute right-1.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||
onClick={() => setQuery('')}
|
||||
size="icon-xs"
|
||||
variant="ghost"
|
||||
>
|
||||
<X className="size-3.5" />
|
||||
</Button>
|
||||
) : (
|
||||
<span className="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 rounded-md bg-background/80 px-1.5 py-0.5 text-[0.62rem] leading-none text-muted-foreground shadow-xs">
|
||||
Cmd P
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
aria-label="Close settings"
|
||||
className="pointer-events-auto absolute right-3.75 top-[calc(0.1875rem+var(--titlebar-height)/2)] h-7 w-7 -translate-y-1/2 rounded-lg text-muted-foreground hover:bg-accent/70 hover:text-foreground [-webkit-app-region:no-drag]"
|
||||
onClick={() => {
|
||||
triggerHaptic('close')
|
||||
onClose()
|
||||
}}
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
<X size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid min-h-0 flex-1 grid-cols-[13rem_minmax(0,1fr)] rounded-[1.0625rem] bg-background/90 pt-(--titlebar-height) max-[760px]:grid-cols-1">
|
||||
<aside className="flex min-h-0 flex-col gap-0.5 overflow-y-auto bg-muted/20 px-4 py-5">
|
||||
<OverlayView
|
||||
closeLabel="Close settings"
|
||||
headerContent={
|
||||
<OverlaySearchInput
|
||||
containerClassName="w-[min(36rem,calc(100vw-32rem))] min-w-80"
|
||||
inputRef={searchInputRef}
|
||||
onChange={setQuery}
|
||||
placeholder={SEARCH_PLACEHOLDER[queryKey]}
|
||||
value={query}
|
||||
/>
|
||||
}
|
||||
onClose={onClose}
|
||||
>
|
||||
<OverlaySplitLayout>
|
||||
<OverlaySidebar>
|
||||
{SECTIONS.map(s => {
|
||||
const view = `config:${s.id}` as SettingsViewId
|
||||
|
||||
return (
|
||||
<NavLink
|
||||
<OverlayNavItem
|
||||
active={activeView === view && !queries.config.trim()}
|
||||
icon={s.icon}
|
||||
key={s.id}
|
||||
@@ -142,56 +116,45 @@ export function SettingsView({ onClose, onConfigSaved }: SettingsPageProps) {
|
||||
)
|
||||
})}
|
||||
<div className="my-2 h-px bg-border/30" />
|
||||
<NavLink
|
||||
<OverlayNavItem
|
||||
active={activeView === 'keys'}
|
||||
icon={KeyRound}
|
||||
label="API Keys"
|
||||
onClick={() => setActiveView('keys')}
|
||||
/>
|
||||
<NavLink
|
||||
<OverlayNavItem
|
||||
active={activeView === 'tools'}
|
||||
icon={Package}
|
||||
label="Skills & Tools"
|
||||
onClick={() => setActiveView('tools')}
|
||||
/>
|
||||
<div className="mt-auto flex items-center gap-1 pt-2">
|
||||
<Button
|
||||
className="text-muted-foreground"
|
||||
onClick={() => void exportConfig()}
|
||||
size="icon-xs"
|
||||
title="Export config"
|
||||
variant="ghost"
|
||||
>
|
||||
<Download />
|
||||
</Button>
|
||||
<Button
|
||||
className="text-muted-foreground"
|
||||
<OverlayIconButton onClick={() => void exportConfig()} title="Export config">
|
||||
<IconDownload className="size-3.5" />
|
||||
</OverlayIconButton>
|
||||
<OverlayIconButton
|
||||
onClick={() => {
|
||||
triggerHaptic('open')
|
||||
importInputRef.current?.click()
|
||||
}}
|
||||
size="icon-xs"
|
||||
title="Import config"
|
||||
variant="ghost"
|
||||
>
|
||||
<Upload />
|
||||
</Button>
|
||||
<Button
|
||||
className="text-muted-foreground"
|
||||
<IconUpload className="size-3.5" />
|
||||
</OverlayIconButton>
|
||||
<OverlayIconButton
|
||||
className="hover:text-destructive"
|
||||
onClick={() => {
|
||||
triggerHaptic('warning')
|
||||
void resetConfig()
|
||||
}}
|
||||
size="icon-xs"
|
||||
title="Reset to defaults"
|
||||
variant="ghost"
|
||||
>
|
||||
<RotateCcw />
|
||||
</Button>
|
||||
<IconRefresh className="size-3.5" />
|
||||
</OverlayIconButton>
|
||||
</div>
|
||||
</aside>
|
||||
</OverlaySidebar>
|
||||
|
||||
<main className="flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<OverlayMain className="p-0">
|
||||
{activeView === 'config:appearance' ? (
|
||||
<AppearanceSettings />
|
||||
) : activeView.startsWith('config:') ? (
|
||||
@@ -206,9 +169,9 @@ export function SettingsView({ onClose, onConfigSaved }: SettingsPageProps) {
|
||||
) : (
|
||||
<ToolsSettings query={queries.tools} />
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</OverlayMain>
|
||||
</OverlaySplitLayout>
|
||||
</OverlayView>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Check, Eye, EyeOff, Save, Settings2, Trash2, X, Zap } from 'lucide-react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { deleteEnvVar, getEnvVars, revealEnvVar, setEnvVar } from '@/hermes'
|
||||
import { Check, Eye, EyeOff, Save, Settings2, Trash2, X, Zap } from '@/lib/icons'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { notify, notifyError } from '@/store/notifications'
|
||||
import type { EnvVarInfo } from '@/types/hermes'
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
import { LoadingState, Pill, SectionHeading, SettingsContent } from './primitives'
|
||||
import type { EnvPatch, EnvRowProps, ProviderGroup, SearchProps } from './types'
|
||||
|
||||
const SHOW_ADVANCED_STORAGE_KEY = 'desktop.settings.keys.show_advanced'
|
||||
|
||||
interface EnvActionsProps {
|
||||
varKey: string
|
||||
info: EnvVarInfo
|
||||
@@ -218,7 +220,28 @@ export function KeysSettings({ query }: SearchProps) {
|
||||
const [edits, setEdits] = useState<Record<string, string>>({})
|
||||
const [revealed, setRevealed] = useState<Record<string, string>>({})
|
||||
const [saving, setSaving] = useState<string | null>(null)
|
||||
const [showAdvanced, setShowAdvanced] = useState(true)
|
||||
|
||||
const [showAdvanced, setShowAdvanced] = useState<boolean>(() => {
|
||||
try {
|
||||
const stored = window.localStorage.getItem(SHOW_ADVANCED_STORAGE_KEY)
|
||||
|
||||
if (stored === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
return stored === 'true'
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
window.localStorage.setItem(SHOW_ADVANCED_STORAGE_KEY, showAdvanced ? 'true' : 'false')
|
||||
} catch {
|
||||
// Ignore persistence failures and keep in-memory preference.
|
||||
}
|
||||
}, [showAdvanced])
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { LucideIcon } from 'lucide-react'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
import { PageLoader } from '@/components/page-loader'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import type { LucideIcon } from '@/lib/icons'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export function SettingsContent({ children }: { children: ReactNode }) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Brain, Wrench } from 'lucide-react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { getSkills, getToolsets, toggleSkill } from '@/hermes'
|
||||
import { Brain, Wrench } from '@/lib/icons'
|
||||
import { notify, notifyError } from '@/store/notifications'
|
||||
import type { SkillInfo, ToolsetInfo } from '@/types/hermes'
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { LucideIcon } from 'lucide-react'
|
||||
import type { Dispatch, SetStateAction } from 'react'
|
||||
|
||||
import type { LucideIcon } from '@/lib/icons'
|
||||
import type { EnvVarInfo } from '@/types/hermes'
|
||||
|
||||
export type SettingsView = 'keys' | 'tools' | `config:${string}`
|
||||
|
||||
Reference in New Issue
Block a user