feat(electrobun): project wizard phases 1-5 (WIP)
- sanitize.ts: input sanitization (trim, control chars, path traversal) - provider-scanner.ts: detect Claude/Codex/Ollama/Gemini availability - model-fetcher.ts: live model lists from 4 provider APIs - ModelConfigPanel.svelte: per-provider config (thinking, effort, sandbox, temperature) - WizardStep1-3.svelte: split wizard into composable steps - CustomDropdown/Checkbox/Radio: themed UI components - provider-handlers.ts: provider.scan + provider.models RPC - Wire providers into wizard step 3 (live detection + model lists) - Replace native selects in 5 settings panels with CustomDropdown
This commit is contained in:
parent
b7fc3a0f9b
commit
d4014a193d
25 changed files with 2112 additions and 759 deletions
|
|
@ -9,6 +9,7 @@
|
|||
{ id: 'claude', label: 'Claude', desc: 'Anthropic — claude-opus/sonnet/haiku' },
|
||||
{ id: 'codex', label: 'Codex', desc: 'OpenAI — gpt-5.4' },
|
||||
{ id: 'ollama', label: 'Ollama', desc: 'Local — qwen3, llama3, etc.' },
|
||||
{ id: 'gemini', label: 'Gemini', desc: 'Google — gemini-2.5-pro' },
|
||||
];
|
||||
|
||||
let defaultShell = $state('/bin/bash');
|
||||
|
|
@ -21,6 +22,7 @@
|
|||
claude: { enabled: true, model: 'claude-opus-4-5' },
|
||||
codex: { enabled: false, model: 'gpt-5.4' },
|
||||
ollama: { enabled: false, model: 'qwen3:8b' },
|
||||
gemini: { enabled: false, model: 'gemini-2.5-pro' },
|
||||
});
|
||||
|
||||
let expandedProvider = $state<string | null>(null);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import { fontStore } from '../font-store.svelte.ts';
|
||||
import { t, getLocale, setLocale, AVAILABLE_LOCALES } from '../i18n.svelte.ts';
|
||||
import ThemeEditor from './ThemeEditor.svelte';
|
||||
import CustomDropdown from '../ui/CustomDropdown.svelte';
|
||||
|
||||
const UI_FONTS = [
|
||||
{ value: '', label: 'System Default' },
|
||||
|
|
@ -47,18 +48,13 @@
|
|||
$effect(() => { termFont = fontStore.termFontFamily; });
|
||||
$effect(() => { termFontSize = fontStore.termFontSize; });
|
||||
|
||||
// ── Dropdown open state ────────────────────────────────────────────────────
|
||||
let themeOpen = $state(false);
|
||||
let uiFontOpen = $state(false);
|
||||
let termFontOpen = $state(false);
|
||||
let langOpen = $state(false);
|
||||
// ── Dropdown open state (managed by CustomDropdown now) ────────────────────
|
||||
|
||||
// ── Language ──────────────────────────────────────────────────────────────
|
||||
let currentLocale = $derived(getLocale());
|
||||
let langLabel = $derived(AVAILABLE_LOCALES.find(l => l.tag === currentLocale)?.nativeLabel ?? 'English');
|
||||
|
||||
function selectLang(tag: string): void {
|
||||
langOpen = false;
|
||||
setLocale(tag);
|
||||
}
|
||||
|
||||
|
|
@ -69,26 +65,27 @@
|
|||
]);
|
||||
let allGroups = $derived([...THEME_GROUPS, ...(customThemes.length > 0 ? ['Custom'] : [])]);
|
||||
|
||||
// ── Derived labels ─────────────────────────────────────────────────────────
|
||||
let themeLabel = $derived(allThemes.find(t => t.id === themeId)?.label ?? 'Catppuccin Mocha');
|
||||
let uiFontLabel = $derived(UI_FONTS.find(f => f.value === uiFont)?.label ?? 'System Default');
|
||||
let termFontLabel = $derived(TERM_FONTS.find(f => f.value === termFont)?.label ?? 'Default (JetBrains Mono)');
|
||||
// ── Dropdown items for CustomDropdown ──────────────────────────────────────
|
||||
let themeItems = $derived(allThemes.map(t => ({ value: t.id, label: t.label, group: t.group })));
|
||||
let uiFontItems = UI_FONTS.map(f => ({ value: f.value, label: f.label }));
|
||||
let termFontItems = TERM_FONTS.map(f => ({ value: f.value, label: f.label }));
|
||||
let langItems = AVAILABLE_LOCALES.map(l => ({ value: l.tag, label: l.nativeLabel }));
|
||||
|
||||
// ── Actions ────────────────────────────────────────────────────────────────
|
||||
|
||||
function selectTheme(id: ThemeId): void {
|
||||
themeId = id; themeOpen = false;
|
||||
themeId = id;
|
||||
themeStore.setTheme(id);
|
||||
appRpc?.request['settings.set']({ key: 'theme', value: id }).catch(console.error);
|
||||
}
|
||||
|
||||
function selectUIFont(value: string): void {
|
||||
uiFont = value; uiFontOpen = false;
|
||||
uiFont = value;
|
||||
fontStore.setUIFont(value, uiFontSize);
|
||||
}
|
||||
|
||||
function selectTermFont(value: string): void {
|
||||
termFont = value; termFontOpen = false;
|
||||
termFont = value;
|
||||
fontStore.setTermFont(value, termFontSize);
|
||||
}
|
||||
|
||||
|
|
@ -117,10 +114,7 @@
|
|||
appRpc?.request['settings.set']({ key: 'scrollback', value: String(v) }).catch(console.error);
|
||||
}
|
||||
|
||||
function closeAll(): void { themeOpen = false; uiFontOpen = false; termFontOpen = false; langOpen = false; }
|
||||
function handleOutsideClick(e: MouseEvent): void {
|
||||
if (!(e.target as HTMLElement).closest('.dd-wrap')) closeAll();
|
||||
}
|
||||
// CustomDropdown handles its own open/close state
|
||||
|
||||
async function deleteCustomTheme(id: string) {
|
||||
await appRpc?.request['themes.deleteCustom']({ id }).catch(console.error);
|
||||
|
|
@ -148,8 +142,7 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="section" onclick={handleOutsideClick} onkeydown={e => e.key === 'Escape' && closeAll()}>
|
||||
<div class="section">
|
||||
|
||||
{#if showEditor}
|
||||
<ThemeEditor
|
||||
|
|
@ -162,58 +155,27 @@
|
|||
|
||||
<h3 class="sh">{t('settings.theme')}</h3>
|
||||
<div class="field">
|
||||
<div class="dd-wrap">
|
||||
<button class="dd-btn" onclick={() => { themeOpen = !themeOpen; uiFontOpen = false; termFontOpen = false; }}>
|
||||
{themeLabel}
|
||||
<svg class="chev" class:open={themeOpen} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
{#if themeOpen}
|
||||
<ul class="dd-list" role="listbox">
|
||||
{#each allGroups as group}
|
||||
<li class="dd-group-label" role="presentation">{group}</li>
|
||||
{#each allThemes.filter(t => t.group === group) as t}
|
||||
<li class="dd-item" class:sel={themeId === t.id}
|
||||
role="option" aria-selected={themeId === t.id} tabindex="0"
|
||||
onclick={() => selectTheme(t.id)}
|
||||
onkeydown={e => (e.key === 'Enter' || e.key === ' ') && selectTheme(t.id)}
|
||||
>
|
||||
<span class="dd-item-label">{t.label}</span>
|
||||
{#if t.group === 'Custom'}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<span class="del-btn" title="Delete theme"
|
||||
onclick={e => { e.stopPropagation(); deleteCustomTheme(t.id); }}
|
||||
onkeydown={e => e.key === 'Enter' && (e.stopPropagation(), deleteCustomTheme(t.id))}
|
||||
role="button" tabindex="0" aria-label="Delete {t.label}">✕</span>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
<CustomDropdown
|
||||
items={themeItems}
|
||||
selected={themeId}
|
||||
onSelect={v => selectTheme(v as ThemeId)}
|
||||
groupBy={true}
|
||||
/>
|
||||
<div class="theme-actions">
|
||||
<button class="theme-action-btn" onclick={() => { themeOpen = false; showEditor = true; }}>Edit Theme</button>
|
||||
<button class="theme-action-btn" onclick={() => { themeOpen = false; showEditor = true; }}>+ Custom</button>
|
||||
<button class="theme-action-btn" onclick={() => showEditor = true}>Edit Theme</button>
|
||||
<button class="theme-action-btn" onclick={() => showEditor = true}>+ Custom</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="sh">{t('settings.uiFont')}</h3>
|
||||
<div class="field row">
|
||||
<div class="dd-wrap flex1">
|
||||
<button class="dd-btn" onclick={() => { uiFontOpen = !uiFontOpen; themeOpen = false; termFontOpen = false; }}>
|
||||
{uiFontLabel}
|
||||
<svg class="chev" class:open={uiFontOpen} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
{#if uiFontOpen}
|
||||
<ul class="dd-list" role="listbox">
|
||||
{#each UI_FONTS as f}
|
||||
<li class="dd-item" class:sel={uiFont === f.value} role="option" aria-selected={uiFont === f.value}
|
||||
tabindex="0" onclick={() => selectUIFont(f.value)}
|
||||
onkeydown={e => (e.key === 'Enter' || e.key === ' ') && selectUIFont(f.value)}
|
||||
>{f.label}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
<div class="flex1">
|
||||
<CustomDropdown
|
||||
items={uiFontItems}
|
||||
selected={uiFont}
|
||||
onSelect={v => selectUIFont(v)}
|
||||
placeholder="System Default"
|
||||
/>
|
||||
</div>
|
||||
<div class="stepper">
|
||||
<button onclick={() => adjustUISize(-1)} aria-label="Decrease UI font size">−</button>
|
||||
|
|
@ -224,21 +186,13 @@
|
|||
|
||||
<h3 class="sh">{t('settings.termFont')}</h3>
|
||||
<div class="field row">
|
||||
<div class="dd-wrap flex1">
|
||||
<button class="dd-btn" onclick={() => { termFontOpen = !termFontOpen; themeOpen = false; uiFontOpen = false; }}>
|
||||
{termFontLabel}
|
||||
<svg class="chev" class:open={termFontOpen} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
{#if termFontOpen}
|
||||
<ul class="dd-list" role="listbox">
|
||||
{#each TERM_FONTS as f}
|
||||
<li class="dd-item" class:sel={termFont === f.value} role="option" aria-selected={termFont === f.value}
|
||||
tabindex="0" onclick={() => selectTermFont(f.value)}
|
||||
onkeydown={e => (e.key === 'Enter' || e.key === ' ') && selectTermFont(f.value)}
|
||||
>{f.label}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
<div class="flex1">
|
||||
<CustomDropdown
|
||||
items={termFontItems}
|
||||
selected={termFont}
|
||||
onSelect={v => selectTermFont(v)}
|
||||
placeholder="Default (JetBrains Mono)"
|
||||
/>
|
||||
</div>
|
||||
<div class="stepper">
|
||||
<button onclick={() => adjustTermSize(-1)} aria-label="Decrease terminal font size">−</button>
|
||||
|
|
@ -270,25 +224,11 @@
|
|||
|
||||
<h3 class="sh">{t('settings.language')}</h3>
|
||||
<div class="field">
|
||||
<div class="dd-wrap">
|
||||
<button class="dd-btn" onclick={() => { langOpen = !langOpen; themeOpen = false; uiFontOpen = false; termFontOpen = false; }}>
|
||||
{langLabel}
|
||||
<svg class="chev" class:open={langOpen} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
{#if langOpen}
|
||||
<ul class="dd-list" role="listbox">
|
||||
{#each AVAILABLE_LOCALES as loc}
|
||||
<li class="dd-item" class:sel={currentLocale === loc.tag} role="option" aria-selected={currentLocale === loc.tag}
|
||||
tabindex="0" onclick={() => selectLang(loc.tag)}
|
||||
onkeydown={e => (e.key === 'Enter' || e.key === ' ') && selectLang(loc.tag)}
|
||||
>
|
||||
<span class="dd-item-label">{loc.nativeLabel}</span>
|
||||
<span style="color: var(--ctp-overlay0); font-size: 0.6875rem;">{loc.label}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
<CustomDropdown
|
||||
items={langItems}
|
||||
selected={currentLocale}
|
||||
onSelect={v => selectLang(v)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
|
|
@ -301,39 +241,6 @@
|
|||
.row { display: flex; align-items: center; gap: 0.5rem; }
|
||||
.flex1 { flex: 1; min-width: 0; }
|
||||
|
||||
.dd-wrap { position: relative; }
|
||||
.dd-btn {
|
||||
width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 0.375rem;
|
||||
background: var(--ctp-surface0); border: 1px solid var(--ctp-surface1); border-radius: 0.25rem;
|
||||
padding: 0.3rem 0.5rem; color: var(--ctp-text); font-family: var(--ui-font-family);
|
||||
font-size: 0.8125rem; cursor: pointer; text-align: left;
|
||||
}
|
||||
.dd-btn:hover { border-color: var(--ctp-surface2); }
|
||||
.chev { width: 0.75rem; height: 0.75rem; color: var(--ctp-overlay1); transition: transform 0.15s; flex-shrink: 0; }
|
||||
.chev.open { transform: rotate(180deg); }
|
||||
.dd-list {
|
||||
position: absolute; top: calc(100% + 0.125rem); left: 0; right: 0; z-index: 50;
|
||||
list-style: none; margin: 0; padding: 0.25rem;
|
||||
background: var(--ctp-mantle); border: 1px solid var(--ctp-surface1); border-radius: 0.3rem;
|
||||
max-height: 14rem; overflow-y: auto;
|
||||
box-shadow: 0 0.5rem 1rem color-mix(in srgb, var(--ctp-crust) 60%, transparent);
|
||||
}
|
||||
.dd-group-label {
|
||||
padding: 0.25rem 0.5rem 0.125rem; font-size: 0.625rem; font-weight: 700; text-transform: uppercase;
|
||||
letter-spacing: 0.06em; color: var(--ctp-overlay0); border-top: 1px solid var(--ctp-surface0);
|
||||
}
|
||||
.dd-group-label:first-child { border-top: none; }
|
||||
.dd-item {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 0.3rem 0.5rem; border-radius: 0.2rem; font-size: 0.8125rem; color: var(--ctp-subtext1);
|
||||
cursor: pointer; outline: none; list-style: none;
|
||||
}
|
||||
.dd-item:hover, .dd-item:focus { background: var(--ctp-surface0); color: var(--ctp-text); }
|
||||
.dd-item.sel { background: color-mix(in srgb, var(--ctp-mauve) 15%, transparent); color: var(--ctp-mauve); font-weight: 500; }
|
||||
.dd-item-label { flex: 1; }
|
||||
.del-btn { font-size: 0.7rem; color: var(--ctp-overlay0); padding: 0.1rem 0.2rem; border-radius: 0.15rem; }
|
||||
.del-btn:hover { color: var(--ctp-red); background: color-mix(in srgb, var(--ctp-red) 10%, transparent); }
|
||||
|
||||
.theme-actions { display: flex; gap: 0.375rem; margin-top: 0.25rem; }
|
||||
.theme-action-btn {
|
||||
padding: 0.2rem 0.625rem; background: var(--ctp-surface0); border: 1px solid var(--ctp-surface1);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { appRpc } from '../rpc.ts';
|
||||
import CustomCheckbox from '../ui/CustomCheckbox.svelte';
|
||||
|
||||
type WakeStrategy = 'persistent' | 'on-demand' | 'smart';
|
||||
type AnchorScale = 'small' | 'medium' | 'large' | 'full';
|
||||
|
|
@ -135,11 +136,12 @@
|
|||
</label>
|
||||
|
||||
<div class="notif-types" style="margin-top: 0.375rem;">
|
||||
{#each NOTIF_TYPES as t}
|
||||
<label class="notif-chip" class:active={notifTypes.has(t)}>
|
||||
<input type="checkbox" checked={notifTypes.has(t)} onchange={() => toggleNotif(t)} aria-label="Notify on {t}" />
|
||||
{t}
|
||||
</label>
|
||||
{#each NOTIF_TYPES as nt}
|
||||
<CustomCheckbox
|
||||
checked={notifTypes.has(nt)}
|
||||
label={nt}
|
||||
onChange={() => toggleNotif(nt)}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -167,8 +169,4 @@
|
|||
.toggle.on .thumb { transform: translateX(0.875rem); }
|
||||
|
||||
.notif-types { display: flex; flex-wrap: wrap; gap: 0.375rem; }
|
||||
.notif-chip { display: flex; align-items: center; gap: 0.25rem; padding: 0.2rem 0.5rem; border-radius: 0.25rem; font-size: 0.75rem; cursor: pointer; background: var(--ctp-surface0); border: 1px solid var(--ctp-surface1); color: var(--ctp-subtext0); transition: all 0.12s; }
|
||||
.notif-chip input { display: none; }
|
||||
.notif-chip.active { background: color-mix(in srgb, var(--ctp-blue) 15%, var(--ctp-surface0)); border-color: var(--ctp-blue); color: var(--ctp-blue); }
|
||||
.notif-chip:hover { border-color: var(--ctp-surface2); color: var(--ctp-subtext1); }
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { appRpc } from '../rpc.ts';
|
||||
import { PROVIDER_CAPABILITIES, type ProviderId } from '../provider-capabilities';
|
||||
import { setRetentionConfig } from '../agent-store.svelte.ts';
|
||||
import CustomCheckbox from '../ui/CustomCheckbox.svelte';
|
||||
|
||||
const ANCHOR_SCALES = ['small', 'medium', 'large', 'full'] as const;
|
||||
type AnchorScale = typeof ANCHOR_SCALES[number];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { appRpc } from '../rpc.ts';
|
||||
import CustomDropdown from '../ui/CustomDropdown.svelte';
|
||||
|
||||
const KNOWN_KEYS: Record<string, string> = {
|
||||
ANTHROPIC_API_KEY: 'Anthropic API Key',
|
||||
|
|
@ -16,7 +17,7 @@
|
|||
|
||||
let newKey = $state('');
|
||||
let newValue = $state('');
|
||||
let keyDropOpen = $state(false);
|
||||
// Dropdown state managed by CustomDropdown
|
||||
let saving = $state(false);
|
||||
|
||||
interface BranchPolicy { pattern: string; action: 'block' | 'warn'; }
|
||||
|
|
@ -28,7 +29,7 @@
|
|||
let newAction = $state<'block' | 'warn'>('warn');
|
||||
|
||||
let availableKeys = $derived(Object.keys(KNOWN_KEYS).filter(k => !storedKeys.includes(k)));
|
||||
let newKeyLabel = $derived(newKey ? (KNOWN_KEYS[newKey] ?? newKey) : 'Select key...');
|
||||
let keyDropItems = $derived(availableKeys.map(k => ({ value: k, label: KNOWN_KEYS[k] ?? k })));
|
||||
|
||||
function persistPolicies() {
|
||||
appRpc?.request['settings.set']({ key: 'branch_policies', value: JSON.stringify(branchPolicies) }).catch(console.error);
|
||||
|
|
@ -61,9 +62,7 @@
|
|||
persistPolicies();
|
||||
}
|
||||
|
||||
function handleOutsideClick(e: MouseEvent) {
|
||||
if (!(e.target as HTMLElement).closest('.dd-wrap')) keyDropOpen = false;
|
||||
}
|
||||
// CustomDropdown handles its own outside click
|
||||
|
||||
onMount(async () => {
|
||||
if (!appRpc) return;
|
||||
|
|
@ -74,8 +73,7 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="section" onclick={handleOutsideClick} onkeydown={e => e.key === 'Escape' && (keyDropOpen = false)}>
|
||||
<div class="section">
|
||||
|
||||
<div class="prototype-notice">
|
||||
Prototype — secrets are stored locally in plain SQLite, not in the system keyring.
|
||||
|
|
@ -107,24 +105,13 @@
|
|||
{/if}
|
||||
|
||||
<div class="add-secret">
|
||||
<div class="dd-wrap">
|
||||
<button class="dd-btn small" onclick={() => keyDropOpen = !keyDropOpen}>
|
||||
{newKeyLabel}
|
||||
<svg class="chev" class:open={keyDropOpen} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
{#if keyDropOpen}
|
||||
<ul class="dd-list" role="listbox">
|
||||
{#each availableKeys as k}
|
||||
<li class="dd-item" role="option" aria-selected={newKey === k} tabindex="0"
|
||||
onclick={() => { newKey = k; keyDropOpen = false; }}
|
||||
onkeydown={e => (e.key === 'Enter' || e.key === ' ') && (newKey = k, keyDropOpen = false)}
|
||||
>{KNOWN_KEYS[k]}</li>
|
||||
{/each}
|
||||
{#if availableKeys.length === 0}
|
||||
<li class="dd-item disabled-item">All known keys stored</li>
|
||||
{/if}
|
||||
</ul>
|
||||
{/if}
|
||||
<div class="dd-key-wrap">
|
||||
<CustomDropdown
|
||||
items={keyDropItems}
|
||||
selected={newKey}
|
||||
placeholder="Select key..."
|
||||
onSelect={v => newKey = v}
|
||||
/>
|
||||
</div>
|
||||
<input class="text-in flex1" type="password" bind:value={newValue} placeholder="Value" aria-label="Secret value" />
|
||||
<button class="save-btn" onclick={handleSaveSecret} disabled={!newKey || !newValue || saving}>
|
||||
|
|
@ -183,15 +170,7 @@
|
|||
.add-policy { display: flex; align-items: center; gap: 0.375rem; margin-top: 0.25rem; }
|
||||
.flex1 { flex: 1; min-width: 0; }
|
||||
|
||||
.dd-wrap { position: relative; flex-shrink: 0; }
|
||||
.dd-btn { display: flex; align-items: center; justify-content: space-between; gap: 0.25rem; background: var(--ctp-surface0); border: 1px solid var(--ctp-surface1); border-radius: 0.25rem; color: var(--ctp-subtext1); font-family: var(--ui-font-family); cursor: pointer; white-space: nowrap; }
|
||||
.dd-btn.small { padding: 0.275rem 0.5rem; font-size: 0.75rem; min-width: 8rem; }
|
||||
.chev { width: 0.625rem; height: 0.625rem; color: var(--ctp-overlay1); transition: transform 0.15s; }
|
||||
.chev.open { transform: rotate(180deg); }
|
||||
.dd-list { position: absolute; top: calc(100% + 0.125rem); left: 0; z-index: 50; list-style: none; margin: 0; padding: 0.2rem; background: var(--ctp-mantle); border: 1px solid var(--ctp-surface1); border-radius: 0.25rem; min-width: 10rem; box-shadow: 0 0.5rem 1rem color-mix(in srgb, var(--ctp-crust) 60%, transparent); }
|
||||
.dd-item { padding: 0.3rem 0.5rem; border-radius: 0.2rem; font-size: 0.8rem; color: var(--ctp-subtext1); cursor: pointer; outline: none; }
|
||||
.dd-item:hover, .dd-item:focus { background: var(--ctp-surface0); color: var(--ctp-text); }
|
||||
.disabled-item { opacity: 0.4; cursor: not-allowed; }
|
||||
.dd-key-wrap { flex-shrink: 0; min-width: 10rem; }
|
||||
|
||||
.text-in { padding: 0.275rem 0.5rem; background: var(--ctp-surface0); border: 1px solid var(--ctp-surface1); border-radius: 0.25rem; color: var(--ctp-text); font-size: 0.8rem; font-family: var(--ui-font-family); }
|
||||
.text-in:focus { outline: none; border-color: var(--ctp-blue); }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue