feat(ui): add favorite models to selector

This commit is contained in:
Shantur Rathore
2026-01-26 20:24:05 +00:00
parent 562c4b2637
commit 158f6e25cf
11 changed files with 368 additions and 33 deletions

View File

@@ -3,6 +3,6 @@
"version": "0.5.0", "version": "0.5.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@opencode-ai/plugin": "1.1.30" "@opencode-ai/plugin": "1.1.36"
} }
} }

View File

@@ -16,6 +16,7 @@ const PreferencesSchema = z.object({
locale: z.string().optional(), locale: z.string().optional(),
environmentVariables: z.record(z.string()).default({}), environmentVariables: z.record(z.string()).default({}),
modelRecents: z.array(ModelPreferenceSchema).default([]), modelRecents: z.array(ModelPreferenceSchema).default([]),
modelFavorites: z.array(ModelPreferenceSchema).default([]),
modelThinkingSelections: z.record(z.string(), z.string()).default({}), modelThinkingSelections: z.record(z.string(), z.string()).default({}),
diffViewMode: z.enum(["split", "unified"]).default("split"), diffViewMode: z.enum(["split", "unified"]).default("split"),
toolOutputExpansion: z.enum(["expanded", "collapsed"]).default("expanded"), toolOutputExpansion: z.enum(["expanded", "collapsed"]).default("expanded"),

View File

@@ -1,10 +1,11 @@
import { Combobox } from "@kobalte/core/combobox" import { Combobox } from "@kobalte/core/combobox"
import { createEffect, createMemo, createSignal } from "solid-js" import { createEffect, createMemo, createSignal } from "solid-js"
import { providers, fetchProviders } from "../stores/sessions" import { providers, fetchProviders } from "../stores/sessions"
import { ChevronDown } from "lucide-solid" import { ChevronDown, Star } from "lucide-solid"
import type { Model } from "../types/session" import type { Model } from "../types/session"
import { useI18n } from "../lib/i18n" import { useI18n } from "../lib/i18n"
import { getLogger } from "../lib/logger" import { getLogger } from "../lib/logger"
import { preferences, toggleFavoriteModelPreference } from "../stores/preferences"
import Kbd from "./kbd" import Kbd from "./kbd"
const log = getLogger("session") const log = getLogger("session")
@@ -26,8 +27,19 @@ export default function ModelSelector(props: ModelSelectorProps) {
const { t } = useI18n() const { t } = useI18n()
const instanceProviders = () => providers().get(props.instanceId) || [] const instanceProviders = () => providers().get(props.instanceId) || []
const [isOpen, setIsOpen] = createSignal(false) const [isOpen, setIsOpen] = createSignal(false)
const [manualAll, setManualAll] = createSignal(false)
const [explicitFavorites, setExplicitFavorites] = createSignal(false)
const [autoFavoritesEligibleAtOpen, setAutoFavoritesEligibleAtOpen] = createSignal(false)
const [searchDirty, setSearchDirty] = createSignal(false)
const [initialQuery, setInitialQuery] = createSignal("")
const [initialQueryReady, setInitialQueryReady] = createSignal(false)
const [inputValue, setInputValue] = createSignal("")
let triggerRef!: HTMLButtonElement let triggerRef!: HTMLButtonElement
let searchInputRef!: HTMLInputElement let searchInputRef!: HTMLInputElement
let listboxRef!: HTMLUListElement
let suppressNextClose = false
let wasFavoritesOnlyEnabled = false
let wasCurrentModelFavorite = false
createEffect(() => { createEffect(() => {
if (instanceProviders().length === 0) { if (instanceProviders().length === 0) {
@@ -46,61 +58,232 @@ export default function ModelSelector(props: ModelSelectorProps) {
), ),
) )
const favoriteKeySet = createMemo(() => {
const result = new Set<string>()
for (const item of preferences().modelFavorites ?? []) {
if (item.providerId && item.modelId) {
result.add(`${item.providerId}/${item.modelId}`)
}
}
return result
})
const favoriteModels = createMemo<FlatModel[]>(() => {
const keys = favoriteKeySet()
if (keys.size === 0) return []
return allModels().filter((m) => keys.has(m.key))
})
const hasFavorites = createMemo(() => favoriteModels().length > 0)
const currentModelValue = createMemo(() => const currentModelValue = createMemo(() =>
allModels().find((m) => m.providerId === props.currentModel.providerId && m.id === props.currentModel.modelId), allModels().find((m) => m.providerId === props.currentModel.providerId && m.id === props.currentModel.modelId),
) )
const currentModelIsFavorite = createMemo(() => {
const current = props.currentModel
return favoriteKeySet().has(`${current.providerId}/${current.modelId}`)
})
const currentModelKey = createMemo(() => {
const current = props.currentModel
return `${current.providerId}/${current.modelId}`
})
const searchActive = createMemo(() => {
if (!searchDirty()) return false
const next = inputValue().trim()
return next.length > 0
})
const favoritesOnlyEnabled = createMemo(() => {
if (searchActive()) return false
if (manualAll()) return false
if (!hasFavorites()) return false
return explicitFavorites() || autoFavoritesEligibleAtOpen()
})
const visibleOptions = createMemo<FlatModel[]>(() => {
if (!favoritesOnlyEnabled()) {
return allModels()
}
return favoriteModels()
})
const handleChange = async (value: FlatModel | null) => { const handleChange = async (value: FlatModel | null) => {
if (!value) return if (!value) return
await props.onModelChange({ providerId: value.providerId, modelId: value.id }) await props.onModelChange({ providerId: value.providerId, modelId: value.id })
} }
const customFilter = (option: FlatModel, inputValue: string) => { const customFilter = (option: FlatModel, rawInput: string) => {
return option.searchText.toLowerCase().includes(inputValue.toLowerCase()) if (!searchDirty()) return true
return option.searchText.toLowerCase().includes(rawInput.toLowerCase())
} }
createEffect(() => { createEffect(() => {
if (isOpen()) { if (isOpen()) {
setManualAll(false)
setExplicitFavorites(false)
setAutoFavoritesEligibleAtOpen(hasFavorites() && currentModelIsFavorite())
setSearchDirty(false)
setInitialQuery("")
setInputValue("")
setInitialQueryReady(false)
setTimeout(() => { setTimeout(() => {
const seeded = searchInputRef?.value ?? ""
setInitialQuery(seeded)
setInputValue(seeded)
setInitialQueryReady(true)
searchInputRef?.focus() searchInputRef?.focus()
searchInputRef?.select()
}, 100) }, 100)
} else {
setInitialQueryReady(false)
setSearchDirty(false)
setAutoFavoritesEligibleAtOpen(false)
} }
}) })
createEffect(() => {
if (!isOpen()) {
wasFavoritesOnlyEnabled = favoritesOnlyEnabled()
wasCurrentModelFavorite = currentModelIsFavorite()
return
}
const nowFavoritesOnlyEnabled = favoritesOnlyEnabled()
const nowCurrentModelFavorite = currentModelIsFavorite()
if (wasFavoritesOnlyEnabled && !nowFavoritesOnlyEnabled && wasCurrentModelFavorite && !nowCurrentModelFavorite) {
setTimeout(() => {
const key = currentModelKey()
const target = listboxRef?.querySelector(`[data-key="${key}"]`) as HTMLElement | null
target?.scrollIntoView({ block: "nearest" })
}, 0)
}
wasFavoritesOnlyEnabled = nowFavoritesOnlyEnabled
wasCurrentModelFavorite = nowCurrentModelFavorite
})
const handleSearchInput = (event: InputEvent & { currentTarget: HTMLInputElement }) => {
const next = event.currentTarget.value
setInputValue(next)
if (!initialQueryReady()) return
if (searchDirty()) return
if (next !== initialQuery()) {
setSearchDirty(true)
}
}
const preventListboxPress = (event: PointerEvent | MouseEvent) => {
event.preventDefault()
event.stopImmediatePropagation?.()
event.stopPropagation()
suppressNextClose = true
setTimeout(() => {
suppressNextClose = false
}, 0)
}
const toggleFavoritesOnly = () => {
if (!hasFavorites()) return
if (searchActive()) return
if (favoritesOnlyEnabled()) {
setManualAll(true)
setExplicitFavorites(false)
setAutoFavoritesEligibleAtOpen(false)
return
}
setExplicitFavorites(true)
setManualAll(false)
}
const showAllModels = () => {
setManualAll(true)
setExplicitFavorites(false)
setAutoFavoritesEligibleAtOpen(false)
setTimeout(() => searchInputRef?.focus(), 0)
}
return ( return (
<div class="sidebar-selector"> <div class="sidebar-selector">
<Combobox<FlatModel> <Combobox<FlatModel>
open={isOpen()}
value={currentModelValue()} value={currentModelValue()}
onChange={handleChange} onChange={handleChange}
onOpenChange={setIsOpen} onOpenChange={(next) => {
options={allModels()} if (!next && suppressNextClose) return
setIsOpen(next)
}}
options={visibleOptions()}
optionValue="key" optionValue="key"
optionTextValue="searchText" optionTextValue="searchText"
optionLabel="name" optionLabel="name"
placeholder={t("modelSelector.placeholder.search")} placeholder={t("modelSelector.placeholder.search")}
defaultFilter={customFilter} defaultFilter={customFilter}
allowsEmptyCollection allowsEmptyCollection
itemComponent={(itemProps) => ( itemComponent={(itemProps) => {
<Combobox.Item const isFavorite = () => favoriteKeySet().has(itemProps.item.rawValue.key)
item={itemProps.item} return (
class="selector-option" <Combobox.Item
> item={itemProps.item}
<div class="selector-option-content"> class="selector-option"
<Combobox.ItemLabel class="selector-option-label"> >
{itemProps.item.rawValue.name} <>
</Combobox.ItemLabel> <div class="selector-option-content">
<Combobox.ItemDescription class="selector-option-description"> <Combobox.ItemLabel class="selector-option-label">{itemProps.item.rawValue.name}</Combobox.ItemLabel>
{itemProps.item.rawValue.providerName} {itemProps.item.rawValue.providerId}/ <Combobox.ItemDescription class="selector-option-description">
{itemProps.item.rawValue.id} {itemProps.item.rawValue.providerName} {itemProps.item.rawValue.providerId}/{itemProps.item.rawValue.id}
</Combobox.ItemDescription> </Combobox.ItemDescription>
</div> </div>
<Combobox.ItemIndicator class="selector-option-indicator"> <Combobox.ItemIndicator class="selector-option-indicator">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <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" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg> </svg>
</Combobox.ItemIndicator> </Combobox.ItemIndicator>
</Combobox.Item> <button
)} type="button"
class="selector-option-star"
data-active={isFavorite()}
aria-label={
isFavorite()
? t("modelSelector.favorite.remove")
: t("modelSelector.favorite.add")
}
onPointerDown={preventListboxPress}
onPointerUp={preventListboxPress}
onMouseDown={preventListboxPress}
onMouseUp={preventListboxPress}
onKeyDown={(event) => {
if (event.key !== "Enter" && event.key !== " ") return
event.preventDefault()
event.stopPropagation()
suppressNextClose = true
setTimeout(() => {
suppressNextClose = false
}, 0)
}}
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
toggleFavoriteModelPreference({
providerId: itemProps.item.rawValue.providerId,
modelId: itemProps.item.rawValue.id,
})
}}
>
<Star
class="w-4 h-4"
fill={isFavorite() ? "currentColor" : "none"}
/>
</button>
</>
</Combobox.Item>
)
}}
> >
<Combobox.Control class="relative w-full" data-model-selector-control> <Combobox.Control class="relative w-full" data-model-selector-control>
<Combobox.Input class="sr-only" data-model-selector /> <Combobox.Input class="sr-only" data-model-selector />
@@ -130,13 +313,53 @@ export default function ModelSelector(props: ModelSelectorProps) {
<Combobox.Portal> <Combobox.Portal>
<Combobox.Content class="selector-popover"> <Combobox.Content class="selector-popover">
<div class="selector-search-container"> <div class="selector-search-container">
<Combobox.Input <div class="selector-input-group">
ref={searchInputRef} <Combobox.Input
class="selector-search-input" ref={searchInputRef}
placeholder={t("modelSelector.placeholder.search")} class="selector-search-input flex-1 min-w-0"
/> placeholder={t("modelSelector.placeholder.search")}
onInput={handleSearchInput}
/>
<button
type="button"
class="selector-favorites-toggle"
aria-label={t("modelSelector.favoritesOnly.toggle.ariaLabel")}
aria-pressed={favoritesOnlyEnabled()}
disabled={!hasFavorites() || searchActive()}
data-active={favoritesOnlyEnabled()}
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
toggleFavoritesOnly()
}}
>
<Star class="w-4 h-4" fill={favoritesOnlyEnabled() ? "currentColor" : "none"} />
</button>
</div>
</div>
<Combobox.Listbox ref={listboxRef} class="selector-listbox" />
<div class="selector-footer">
<button
type="button"
class="selector-option selector-option-action w-full"
style={{ display: favoritesOnlyEnabled() && !searchActive() ? "flex" : "none" }}
onMouseDown={(event) => {
event.preventDefault()
event.stopPropagation()
}}
onPointerDown={(event) => {
event.preventDefault()
event.stopPropagation()
}}
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
showAllModels()
}}
>
<span class="selector-option-label">{t("modelSelector.favoritesOnly.showAll")}</span>
</button>
</div> </div>
<Combobox.Listbox class="selector-listbox" />
</Combobox.Content> </Combobox.Content>
</Combobox.Portal> </Combobox.Portal>
</Combobox> </Combobox>

View File

@@ -28,6 +28,10 @@ export const settingsMessages = {
"modelSelector.placeholder.search": "Search models...", "modelSelector.placeholder.search": "Search models...",
"modelSelector.none": "None", "modelSelector.none": "None",
"modelSelector.trigger.primary": "Model: {model}", "modelSelector.trigger.primary": "Model: {model}",
"modelSelector.favoritesOnly.toggle.ariaLabel": "Toggle favorites only",
"modelSelector.favoritesOnly.showAll": "Show all models",
"modelSelector.favorite.add": "Add to favorites",
"modelSelector.favorite.remove": "Remove from favorites",
"thinkingSelector.variant.default": "Default", "thinkingSelector.variant.default": "Default",
"thinkingSelector.label": "Thinking: {variant}", "thinkingSelector.label": "Thinking: {variant}",

View File

@@ -28,6 +28,10 @@ export const settingsMessages = {
"modelSelector.placeholder.search": "Buscar modelos...", "modelSelector.placeholder.search": "Buscar modelos...",
"modelSelector.none": "Ninguno", "modelSelector.none": "Ninguno",
"modelSelector.trigger.primary": "Modelo: {model}", "modelSelector.trigger.primary": "Modelo: {model}",
"modelSelector.favoritesOnly.toggle.ariaLabel": "Alternar solo favoritos",
"modelSelector.favoritesOnly.showAll": "Mostrar todos los modelos",
"modelSelector.favorite.add": "Agregar a favoritos",
"modelSelector.favorite.remove": "Quitar de favoritos",
"thinkingSelector.variant.default": "Por defecto", "thinkingSelector.variant.default": "Por defecto",
"thinkingSelector.label": "Pensamiento: {variant}", "thinkingSelector.label": "Pensamiento: {variant}",

View File

@@ -28,6 +28,10 @@ export const settingsMessages = {
"modelSelector.placeholder.search": "Rechercher des modèles...", "modelSelector.placeholder.search": "Rechercher des modèles...",
"modelSelector.none": "Aucun", "modelSelector.none": "Aucun",
"modelSelector.trigger.primary": "Modèle : {model}", "modelSelector.trigger.primary": "Modèle : {model}",
"modelSelector.favoritesOnly.toggle.ariaLabel": "Basculer en favoris uniquement",
"modelSelector.favoritesOnly.showAll": "Afficher tous les modèles",
"modelSelector.favorite.add": "Ajouter aux favoris",
"modelSelector.favorite.remove": "Retirer des favoris",
"thinkingSelector.variant.default": "Par défaut", "thinkingSelector.variant.default": "Par défaut",
"thinkingSelector.label": "Réflexion : {variant}", "thinkingSelector.label": "Réflexion : {variant}",

View File

@@ -28,6 +28,10 @@ export const settingsMessages = {
"modelSelector.placeholder.search": "モデルを検索...", "modelSelector.placeholder.search": "モデルを検索...",
"modelSelector.none": "なし", "modelSelector.none": "なし",
"modelSelector.trigger.primary": "モデル: {model}", "modelSelector.trigger.primary": "モデル: {model}",
"modelSelector.favoritesOnly.toggle.ariaLabel": "お気に入りのみ",
"modelSelector.favoritesOnly.showAll": "すべてのモデルを表示",
"modelSelector.favorite.add": "お気に入りに追加",
"modelSelector.favorite.remove": "お気に入りから削除",
"thinkingSelector.variant.default": "デフォルト", "thinkingSelector.variant.default": "デフォルト",
"thinkingSelector.label": "思考: {variant}", "thinkingSelector.label": "思考: {variant}",

View File

@@ -28,6 +28,10 @@ export const settingsMessages = {
"modelSelector.placeholder.search": "Поиск моделей…", "modelSelector.placeholder.search": "Поиск моделей…",
"modelSelector.none": "Нет", "modelSelector.none": "Нет",
"modelSelector.trigger.primary": "Модель: {model}", "modelSelector.trigger.primary": "Модель: {model}",
"modelSelector.favoritesOnly.toggle.ariaLabel": "Только избранное",
"modelSelector.favoritesOnly.showAll": "Показать все модели",
"modelSelector.favorite.add": "Добавить в избранное",
"modelSelector.favorite.remove": "Удалить из избранного",
"thinkingSelector.variant.default": "По умолчанию", "thinkingSelector.variant.default": "По умолчанию",
"thinkingSelector.label": "Размышления: {variant}", "thinkingSelector.label": "Размышления: {variant}",

View File

@@ -28,6 +28,10 @@ export const settingsMessages = {
"modelSelector.placeholder.search": "搜索模型...", "modelSelector.placeholder.search": "搜索模型...",
"modelSelector.none": "无", "modelSelector.none": "无",
"modelSelector.trigger.primary": "模型:{model}", "modelSelector.trigger.primary": "模型:{model}",
"modelSelector.favoritesOnly.toggle.ariaLabel": "仅显示收藏",
"modelSelector.favoritesOnly.showAll": "显示所有模型",
"modelSelector.favorite.add": "添加到收藏",
"modelSelector.favorite.remove": "从收藏移除",
"thinkingSelector.variant.default": "默认", "thinkingSelector.variant.default": "默认",
"thinkingSelector.label": "思考:{variant}", "thinkingSelector.label": "思考:{variant}",

View File

@@ -40,6 +40,7 @@ export interface Preferences {
locale?: string locale?: string
environmentVariables: Record<string, string> environmentVariables: Record<string, string>
modelRecents: ModelPreference[] modelRecents: ModelPreference[]
modelFavorites: ModelPreference[]
modelThinkingSelections: Record<string, string> modelThinkingSelections: Record<string, string>
diffViewMode: DiffViewMode diffViewMode: DiffViewMode
toolOutputExpansion: ExpansionPreference toolOutputExpansion: ExpansionPreference
@@ -66,6 +67,7 @@ export type ThemePreference = NonNullable<ConfigData["theme"]>
const MAX_RECENT_FOLDERS = 20 const MAX_RECENT_FOLDERS = 20
const MAX_RECENT_MODELS = 5 const MAX_RECENT_MODELS = 5
const MAX_FAVORITE_MODELS = 50
const defaultPreferences: Preferences = { const defaultPreferences: Preferences = {
showThinkingBlocks: false, showThinkingBlocks: false,
@@ -73,6 +75,7 @@ const defaultPreferences: Preferences = {
showTimelineTools: true, showTimelineTools: true,
environmentVariables: {}, environmentVariables: {},
modelRecents: [], modelRecents: [],
modelFavorites: [],
modelThinkingSelections: {}, modelThinkingSelections: {},
diffViewMode: "split", diffViewMode: "split",
toolOutputExpansion: "expanded", toolOutputExpansion: "expanded",
@@ -105,6 +108,9 @@ function normalizePreferences(pref?: Partial<Preferences> & { agentModelSelectio
const sourceModelRecents = sanitized.modelRecents ?? defaultPreferences.modelRecents const sourceModelRecents = sanitized.modelRecents ?? defaultPreferences.modelRecents
const modelRecents = sourceModelRecents.map((item) => ({ ...item })) const modelRecents = sourceModelRecents.map((item) => ({ ...item }))
const sourceModelFavorites = sanitized.modelFavorites ?? defaultPreferences.modelFavorites
const modelFavorites = sourceModelFavorites.map((item) => ({ ...item }))
const modelThinkingSelections = { const modelThinkingSelections = {
...defaultPreferences.modelThinkingSelections, ...defaultPreferences.modelThinkingSelections,
...(sanitized.modelThinkingSelections ?? {}), ...(sanitized.modelThinkingSelections ?? {}),
@@ -118,6 +124,7 @@ function normalizePreferences(pref?: Partial<Preferences> & { agentModelSelectio
locale: sanitized.locale ?? defaultPreferences.locale, locale: sanitized.locale ?? defaultPreferences.locale,
environmentVariables, environmentVariables,
modelRecents, modelRecents,
modelFavorites,
modelThinkingSelections, modelThinkingSelections,
diffViewMode: sanitized.diffViewMode ?? defaultPreferences.diffViewMode, diffViewMode: sanitized.diffViewMode ?? defaultPreferences.diffViewMode,
toolOutputExpansion: sanitized.toolOutputExpansion ?? defaultPreferences.toolOutputExpansion, toolOutputExpansion: sanitized.toolOutputExpansion ?? defaultPreferences.toolOutputExpansion,
@@ -132,6 +139,29 @@ function getModelKey(model: { providerId: string; modelId: string }): string {
return `${model.providerId}/${model.modelId}` return `${model.providerId}/${model.modelId}`
} }
function isFavoriteModelPreference(model: ModelPreference): boolean {
if (!model.providerId || !model.modelId) return false
return (preferences().modelFavorites ?? []).some(
(item) => item.providerId === model.providerId && item.modelId === model.modelId,
)
}
function toggleFavoriteModelPreference(model: ModelPreference): void {
if (!model.providerId || !model.modelId) return
const favorites = preferences().modelFavorites ?? []
const exists = favorites.some((item) => item.providerId === model.providerId && item.modelId === model.modelId)
if (exists) {
const updated = favorites.filter((item) => item.providerId !== model.providerId || item.modelId !== model.modelId)
updatePreferences({ modelFavorites: updated })
return
}
const filtered = favorites.filter((item) => item.providerId !== model.providerId || item.modelId !== model.modelId)
const updated = [model, ...filtered].slice(0, MAX_FAVORITE_MODELS)
updatePreferences({ modelFavorites: updated })
}
function getModelThinkingSelection(model: { providerId: string; modelId: string }): string | undefined { function getModelThinkingSelection(model: { providerId: string; modelId: string }): string | undefined {
if (!model.providerId || !model.modelId) return undefined if (!model.providerId || !model.modelId) return undefined
return preferences().modelThinkingSelections?.[getModelKey(model)] return preferences().modelThinkingSelections?.[getModelKey(model)]
@@ -566,6 +596,8 @@ export {
addEnvironmentVariable, addEnvironmentVariable,
removeEnvironmentVariable, removeEnvironmentVariable,
addRecentModelPreference, addRecentModelPreference,
isFavoriteModelPreference,
toggleFavoriteModelPreference,
getModelThinkingSelection, getModelThinkingSelection,
setModelThinkingSelection, setModelThinkingSelection,
setAgentModelPreference, setAgentModelPreference,

View File

@@ -148,6 +148,61 @@
color: var(--accent-primary); color: var(--accent-primary);
} }
.selector-option-action {
@apply flex items-center justify-center py-1;
color: var(--text-muted);
}
.selector-option-star {
@apply p-1 rounded transition-colors flex-shrink-0 mt-0.5;
color: var(--text-muted);
}
.selector-option-star[data-active="true"] {
color: var(--accent-primary);
}
.selector-option-star:hover {
background-color: var(--surface-hover);
}
.selector-option-star:focus-visible {
@apply ring-2;
ring-color: var(--accent-primary);
}
.selector-favorites-toggle {
@apply p-2 rounded border transition-colors flex items-center justify-center;
background-color: var(--surface-base);
border-color: var(--border-base);
color: var(--text-muted);
}
.selector-favorites-toggle[data-active="true"] {
color: var(--accent-primary);
}
.selector-favorites-toggle:hover {
background-color: var(--surface-hover);
}
.selector-favorites-toggle:focus-visible {
@apply ring-2;
ring-color: var(--accent-primary);
}
.selector-favorites-toggle:disabled {
@apply opacity-50 cursor-not-allowed;
}
.selector-footer {
@apply border-t;
border-color: var(--border-base);
background-color: var(--surface-base);
position: relative;
z-index: 1;
}
.selector-section { .selector-section {
@apply px-3 py-2 border-b; @apply px-3 py-2 border-b;
border-color: var(--border-base); border-color: var(--border-base);