refactor: restyle selectors via tokens

This commit is contained in:
Shantur Rathore
2025-10-28 20:03:32 +00:00
parent e3009f1aad
commit 7231adbbb4
3 changed files with 300 additions and 50 deletions

View File

@@ -78,18 +78,18 @@ export default function ModelSelector(props: ModelSelectorProps) {
itemComponent={(itemProps) => (
<Combobox.Item
item={itemProps.item}
class="px-3 py-2 cursor-pointer rounded outline-none transition-colors hover:bg-blue-50 dark:hover:bg-blue-900/40 data-[highlighted]:bg-blue-100 dark:data-[highlighted]:bg-blue-900/60 flex items-start gap-2"
class="selector-option"
>
<div class="flex flex-col flex-1 min-w-0">
<Combobox.ItemLabel class="font-medium text-sm text-gray-900 dark:text-gray-100">
<div class="selector-option-content">
<Combobox.ItemLabel class="selector-option-label">
{itemProps.item.rawValue.name}
</Combobox.ItemLabel>
<Combobox.ItemDescription class="text-xs text-gray-600 dark:text-gray-300">
<Combobox.ItemDescription class="selector-option-description">
{itemProps.item.rawValue.providerName} {itemProps.item.rawValue.providerId}/
{itemProps.item.rawValue.id}
</Combobox.ItemDescription>
</div>
<Combobox.ItemIndicator class="flex-shrink-0 mt-0.5 text-blue-600 dark:text-blue-400">
<Combobox.ItemIndicator class="selector-option-indicator">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
@@ -101,38 +101,38 @@ export default function ModelSelector(props: ModelSelectorProps) {
<Combobox.Input class="sr-only" data-model-selector />
<Combobox.Trigger
ref={triggerRef}
class="inline-flex items-center justify-between gap-2 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 outline-none focus:ring-2 focus:ring-blue-500 text-xs min-w-[180px] transition-colors"
class="selector-trigger"
>
<div class="flex flex-col items-start min-w-0">
<span class="text-gray-700 dark:text-gray-200 font-medium">
<div class="selector-trigger-label">
<span class="selector-trigger-primary">
Model: {currentModelValue()?.name ?? "None"}
</span>
{currentModelValue() && (
<span class="text-gray-500 dark:text-gray-400 text-[10px]">
<span class="selector-trigger-secondary">
{currentModelValue()!.providerId}/{currentModelValue()!.id}
</span>
)}
</div>
<Combobox.Icon class="flex-shrink-0">
<ChevronDown class="w-3 h-3 text-gray-500 dark:text-gray-300" />
<Combobox.Icon class="selector-trigger-icon">
<ChevronDown class="w-3 h-3" />
</Combobox.Icon>
</Combobox.Trigger>
</Combobox.Control>
<Combobox.Portal>
<Combobox.Content class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md shadow-lg overflow-hidden z-50 min-w-[300px]">
<div class="p-2 border-b border-gray-200 dark:border-gray-700">
<Combobox.Content class="selector-popover">
<div class="selector-search-container">
<Combobox.Input
ref={searchInputRef}
class="w-full px-3 py-1.5 text-xs border border-gray-300 dark:border-gray-600 rounded outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"
class="selector-search-input"
placeholder="Search models..."
/>
</div>
<Combobox.Listbox class="max-h-64 overflow-auto p-1 bg-white dark:bg-gray-800" />
<Combobox.Listbox class="selector-listbox" />
</Combobox.Content>
</Combobox.Portal>
</Combobox>
<span class="text-xs text-gray-400 dark:text-gray-500">
<span class="hint">
<Kbd shortcut="cmd+shift+m" />
</span>
</div>

View File

@@ -214,22 +214,30 @@ const OpenCodeBinarySelector: Component<OpenCodeBinarySelectorProps> = (props) =
type="button"
onClick={handleButtonClick}
disabled={props.disabled}
class="w-full px-3 py-2 text-left bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg shadow-sm hover:border-gray-400 dark:hover:border-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-between"
classList={{
"selector-trigger": true,
"w-full": true,
"px-3": true,
"py-2": true,
"text-sm": true,
"shadow-sm": true,
"selector-trigger-disabled": props.disabled
}}
ref={buttonRef}
>
<div class="flex items-center gap-2 flex-1 min-w-0">
<Show when={validating()} fallback={<FolderOpen class="w-4 h-4 text-gray-400 dark:text-gray-500" />}>
<Loader2 class="w-4 h-4 text-blue-500 animate-spin" />
<Show when={validating()} fallback={<FolderOpen class="w-4 h-4 selector-trigger-icon" />}>
<Loader2 class="w-4 h-4 selector-loading-spinner" />
</Show>
<span class="text-sm text-gray-900 dark:text-gray-100 truncate">
<span class="truncate">
{getDisplayName(props.selectedBinary || "opencode")}
</span>
<Show when={versionInfo().get(props.selectedBinary)}>
<span class="text-xs text-gray-500 dark:text-gray-400">v{versionInfo().get(props.selectedBinary)}</span>
<span class="selector-badge-version">v{versionInfo().get(props.selectedBinary)}</span>
</Show>
</div>
<ChevronDown
class={`w-4 h-4 text-gray-400 dark:text-gray-500 transition-transform ${isOpen() ? "rotate-180" : ""}`}
class={`w-4 h-4 selector-trigger-icon transition-transform ${isOpen() ? "rotate-180" : ""}`}
/>
</button>
@@ -237,19 +245,19 @@ const OpenCodeBinarySelector: Component<OpenCodeBinarySelectorProps> = (props) =
<Show when={isOpen()}>
<div
data-binary-dropdown
class="absolute top-full left-0 right-0 z-50 mt-1 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg shadow-lg max-h-96 overflow-y-auto"
class="selector-popover absolute top-full left-0 right-0 mt-1 max-h-96 overflow-y-auto"
style={{
position: "absolute",
"z-index": 500,
"max-height": "24rem",
}}
>
<div class="p-3 border-b border-gray-200 dark:border-gray-700">
<div class="text-xs font-medium text-gray-700 dark:text-gray-300 mb-2">OpenCode Binary Selection</div>
<div class="selector-section">
<div class="selector-section-title mb-2">OpenCode Binary Selection</div>
{/* Custom path input */}
<div class="space-y-2">
<div class="flex gap-2">
<div class="selector-input-group">
<input
type="text"
value={customPath()}
@@ -263,12 +271,12 @@ const OpenCodeBinarySelector: Component<OpenCodeBinarySelectorProps> = (props) =
}
}}
placeholder="Enter path to opencode binary..."
class="flex-1 px-2 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-1 focus:ring-blue-500"
class="selector-input"
/>
<button
onClick={handleCustomPathSubmit}
disabled={!customPath().trim() || validating()}
class="px-3 py-1.5 text-sm bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-300 dark:disabled:bg-gray-600 disabled:cursor-not-allowed"
class="selector-button selector-button-primary"
>
Add
</button>
@@ -278,7 +286,7 @@ const OpenCodeBinarySelector: Component<OpenCodeBinarySelectorProps> = (props) =
<button
onClick={handleBrowseBinary}
disabled={validating()}
class="w-full px-3 py-1.5 text-sm bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded hover:bg-gray-200 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
class="selector-button selector-button-secondary w-full flex items-center justify-center gap-2"
>
<FolderOpen class="w-4 h-4" />
Browse for Binary...
@@ -287,10 +295,10 @@ const OpenCodeBinarySelector: Component<OpenCodeBinarySelectorProps> = (props) =
{/* Validation error */}
<Show when={validationError()}>
<div class="mt-2 p-2 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded">
<div class="flex items-start gap-2">
<AlertCircle class="w-4 h-4 text-red-500 mt-0.5 flex-shrink-0" />
<span class="text-xs text-red-700 dark:text-red-400">{validationError()}</span>
<div class="selector-validation-error mt-2">
<div class="selector-validation-error-content">
<AlertCircle class="selector-validation-error-icon" />
<span class="selector-validation-error-text">{validationError()}</span>
</div>
</div>
</Show>
@@ -301,7 +309,7 @@ const OpenCodeBinarySelector: Component<OpenCodeBinarySelectorProps> = (props) =
<Show
when={binaries().length > 0}
fallback={
<div class="p-4 text-center text-sm text-gray-500 dark:text-gray-400">
<div class="selector-empty-state">
No recent binaries. Add one above or use system PATH.
</div>
}
@@ -317,8 +325,8 @@ const OpenCodeBinarySelector: Component<OpenCodeBinarySelectorProps> = (props) =
return (
<div
class={`px-3 py-2 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer border-b border-gray-100 dark:border-gray-700 last:border-b-0 ${
isSelected() ? "bg-blue-50 dark:bg-blue-900/20" : ""
class={`selector-option border-b last:border-b-0 ${
isSelected() ? "selector-option-selected" : ""
}`}
onClick={() => handleSelectBinary(binary.path)}
>
@@ -326,19 +334,19 @@ const OpenCodeBinarySelector: Component<OpenCodeBinarySelectorProps> = (props) =
<div class="flex items-center gap-2 flex-1 min-w-0">
<Show
when={isSelected()}
fallback={<FolderOpen class="w-4 h-4 text-gray-400 dark:text-gray-500" />}
fallback={<FolderOpen class="w-4 h-4 selector-trigger-icon" />}
>
<Check class="w-4 h-4 text-blue-600 dark:text-blue-400" />
<Check class="w-4 h-4 selector-option-indicator" />
</Show>
<div class="flex-1 min-w-0">
<div class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate">
<div class="selector-option-label truncate">
{getDisplayName(binary.path)}
</div>
<div class="flex items-center gap-2 mt-0.5">
<Show when={version()}>
<span class="text-xs text-gray-500 dark:text-gray-400">v{version()}</span>
<span class="selector-badge-version">v{version()}</span>
</Show>
<span class="text-xs text-gray-400 dark:text-gray-500">
<span class="selector-badge-time">
{formatRelativeTime(binary.lastUsed)}
</span>
</div>
@@ -360,30 +368,30 @@ const OpenCodeBinarySelector: Component<OpenCodeBinarySelectorProps> = (props) =
</div>
{/* Default option */}
<div class="p-2 border-t border-gray-200 dark:border-gray-700">
<div class="selector-section">
<div
class={`px-3 py-2 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer rounded ${
props.selectedBinary === "opencode" ? "bg-blue-50 dark:bg-blue-900/20" : ""
class={`selector-option ${
props.selectedBinary === "opencode" ? "selector-option-selected" : ""
}`}
onClick={() => handleSelectBinary("opencode")}
>
<div class="flex items-center gap-2">
<Show
when={props.selectedBinary === "opencode"}
fallback={<FolderOpen class="w-4 h-4 text-gray-400 dark:text-gray-500" />}
fallback={<FolderOpen class="w-4 h-4 selector-trigger-icon" />}
>
<Check class="w-4 h-4 text-blue-600 dark:text-blue-400" />
<Check class="w-4 h-4 selector-option-indicator" />
</Show>
<div class="flex-1 min-w-0">
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">opencode (system PATH)</div>
<div class="selector-option-label">opencode (system PATH)</div>
<div class="flex items-center gap-2 mt-0.5">
<Show when={versionInfo().get("opencode")}>
<span class="text-xs text-gray-500 dark:text-gray-400">v{versionInfo().get("opencode")}</span>
<span class="selector-badge-version">v{versionInfo().get("opencode")}</span>
</Show>
<Show when={!versionInfo().get("opencode") && validating()}>
<span class="text-xs text-gray-400 dark:text-gray-500">Checking...</span>
<span class="selector-badge-time">Checking...</span>
</Show>
<span class="text-xs text-gray-400 dark:text-gray-500">Use binary from system PATH</span>
<span class="selector-badge-time">Use binary from system PATH</span>
</div>
</div>
</div>