Skip to content

Commit a705696

Browse files
committed
Dropdowns: route debug-panel selects through the shared ui/Select primitive (M3)
Converts all six dev-only native `<select>`s in the Debug window onto the house `ui/Select`, so the debug panels share the macOS-y dropdown look and standardized chevron with the rest of the app and give the new primitive real soak coverage. - `DebugSmbDiagnosticsPanel.svelte`: volume picker and refresh-interval picker now render `ui/Select`. The interval state stays a number (`intervalMs`): items carry `String(ms)` values and `onChange` converts back with `Number(v)`, so the underlying type and polling logic are unchanged. - `DebugErrorPreviewPanel.svelte`: the four per-row cloud-provider pickers now render `ui/Select`, driven by provider-name strings via a small `setProviderSelection` helper that maps the name back to the existing index-keyed `providerSelections` store. - Each select gets a sensible `ariaLabel`; explicit `(v: string)` annotations on the template closures keep the typed-boundary clean. - Kept the `.error-provider-select` class on the new wrapper so the existing `:global` rule in `+page.svelte` (M0-owned, untouched) stays consumed; flattened its old native-`<select>` chrome via a scoped override so it doesn't double up on `ui/Select`'s own trigger.
1 parent 6ac9016 commit a705696

2 files changed

Lines changed: 91 additions & 74 deletions

File tree

apps/desktop/src/routes/debug/DebugErrorPreviewPanel.svelte

Lines changed: 52 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script lang="ts">
22
import { tooltip } from '$lib/tooltip/tooltip'
33
import type { FriendlyError } from '$lib/file-explorer/types'
4+
import Select, { type SelectItem } from '$lib/ui/Select.svelte'
45
56
type ErrorCategory = 'transient' | 'needs_action' | 'serious'
67
@@ -35,6 +36,12 @@
3536
/** Per-row selected provider index, keyed by error name. */
3637
const providerSelections = $state<Record<string, number>>({})
3738
39+
const providerItems: SelectItem[] = providerNames.map((name) => ({ value: name, label: name }))
40+
41+
function setProviderSelection(errorName: string, providerName: string) {
42+
providerSelections[errorName] = providerNames.indexOf(providerName as (typeof providerNames)[number])
43+
}
44+
3845
const errnoErrors: ErrorState[] = [
3946
// Transient
4047
{ code: 4, name: 'EINTR', category: 'transient', title: 'Interrupted' },
@@ -179,18 +186,14 @@
179186
{state.name}{#if state.code !== undefined} ({state.code}){/if}
180187
<span class="error-title">{state.title}</span>
181188
</span>
182-
<select
183-
class="error-provider-select"
184-
value={providerNames[providerSelections[state.name] ?? 0]}
185-
onchange={(e) => {
186-
const target = e.currentTarget
187-
providerSelections[state.name] = providerNames.indexOf(target.value as (typeof providerNames)[number])
188-
}}
189-
>
190-
{#each providerNames as name (name)}
191-
<option value={name}>{name}</option>
192-
{/each}
193-
</select>
189+
<div class="error-provider-select">
190+
<Select
191+
items={providerItems}
192+
value={providerNames[providerSelections[state.name] ?? 0]}
193+
onChange={(v: string) => { setProviderSelection(state.name, v); }}
194+
ariaLabel="Cloud provider"
195+
/>
196+
</div>
194197
<button class="error-trigger-btn" onclick={() => void triggerError('left', state)}>L</button>
195198
<button class="error-trigger-btn" onclick={() => void triggerError('right', state)}>R</button>
196199
</div>
@@ -203,18 +206,14 @@
203206
{state.name}{#if state.code !== undefined} ({state.code}){/if}
204207
<span class="error-title">{state.title}</span>
205208
</span>
206-
<select
207-
class="error-provider-select"
208-
value={providerNames[providerSelections[state.name] ?? 0]}
209-
onchange={(e) => {
210-
const target = e.currentTarget
211-
providerSelections[state.name] = providerNames.indexOf(target.value as (typeof providerNames)[number])
212-
}}
213-
>
214-
{#each providerNames as name (name)}
215-
<option value={name}>{name}</option>
216-
{/each}
217-
</select>
209+
<div class="error-provider-select">
210+
<Select
211+
items={providerItems}
212+
value={providerNames[providerSelections[state.name] ?? 0]}
213+
onChange={(v: string) => { setProviderSelection(state.name, v); }}
214+
ariaLabel="Cloud provider"
215+
/>
216+
</div>
218217
<button class="error-trigger-btn" onclick={() => void triggerError('left', state)}>L</button>
219218
<button class="error-trigger-btn" onclick={() => void triggerError('right', state)}>R</button>
220219
</div>
@@ -227,18 +226,14 @@
227226
{state.name}{#if state.code !== undefined} ({state.code}){/if}
228227
<span class="error-title">{state.title}</span>
229228
</span>
230-
<select
231-
class="error-provider-select"
232-
value={providerNames[providerSelections[state.name] ?? 0]}
233-
onchange={(e) => {
234-
const target = e.currentTarget
235-
providerSelections[state.name] = providerNames.indexOf(target.value as (typeof providerNames)[number])
236-
}}
237-
>
238-
{#each providerNames as name (name)}
239-
<option value={name}>{name}</option>
240-
{/each}
241-
</select>
229+
<div class="error-provider-select">
230+
<Select
231+
items={providerItems}
232+
value={providerNames[providerSelections[state.name] ?? 0]}
233+
onChange={(v: string) => { setProviderSelection(state.name, v); }}
234+
ariaLabel="Cloud provider"
235+
/>
236+
</div>
242237
<button class="error-trigger-btn" onclick={() => void triggerError('left', state)}>L</button>
243238
<button class="error-trigger-btn" onclick={() => void triggerError('right', state)}>R</button>
244239
</div>
@@ -251,18 +246,14 @@
251246
{state.name}
252247
<span class="error-title">{state.title}</span>
253248
</span>
254-
<select
255-
class="error-provider-select"
256-
value={providerNames[providerSelections[state.name] ?? 0]}
257-
onchange={(e) => {
258-
const target = e.currentTarget
259-
providerSelections[state.name] = providerNames.indexOf(target.value as (typeof providerNames)[number])
260-
}}
261-
>
262-
{#each providerNames as name (name)}
263-
<option value={name}>{name}</option>
264-
{/each}
265-
</select>
249+
<div class="error-provider-select">
250+
<Select
251+
items={providerItems}
252+
value={providerNames[providerSelections[state.name] ?? 0]}
253+
onChange={(v: string) => { setProviderSelection(state.name, v); }}
254+
ariaLabel="Cloud provider"
255+
/>
256+
</div>
266257
<button class="error-trigger-btn" onclick={() => void triggerError('left', state)}>L</button>
267258
<button class="error-trigger-btn" onclick={() => void triggerError('right', state)}>R</button>
268259
</div>
@@ -292,3 +283,16 @@
292283
{/each}
293284
</div>
294285
</section>
286+
287+
<style>
288+
/* The `:global(.error-provider-select)` rule in `+page.svelte` styled the old native
289+
`<select>` (border, bg, padding). Now the class wraps a `ui/Select`, which brings its own
290+
trigger chrome, so flatten the wrapper to a plain sizing box and let `Select` own the look. */
291+
.error-provider-select {
292+
display: inline-flex;
293+
width: 150px;
294+
padding: 0;
295+
background: transparent;
296+
border: none;
297+
}
298+
</style>

apps/desktop/src/routes/debug/DebugSmbDiagnosticsPanel.svelte

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { onDestroy, onMount } from 'svelte'
33
import { commands, type SmbDiagnosticsDto, type SmbVolumeRef } from '$lib/ipc/bindings'
44
import { tooltip } from '$lib/tooltip/tooltip'
5+
import Select, { type SelectItem } from '$lib/ui/Select.svelte'
56
67
type Loadable = 'idle' | 'loading' | 'ready' | 'error'
78
@@ -92,9 +93,23 @@
9293
}
9394
}
9495
95-
async function handleVolumeChange(e: Event) {
96-
const target = e.target as HTMLSelectElement
97-
selectedVolumeId = target.value || null
96+
const volumeItems = $derived<SelectItem[]>(
97+
volumes.map((v) => ({
98+
value: v.volume_id,
99+
label: `${v.name} (${v.server})${v.disconnected ? ' — disconnected' : ''}`,
100+
})),
101+
)
102+
103+
const intervalItems: SelectItem[] = [
104+
{ value: '250', label: '250 ms' },
105+
{ value: '500', label: '500 ms' },
106+
{ value: '1000', label: '1 s' },
107+
{ value: '2000', label: '2 s' },
108+
{ value: '5000', label: '5 s' },
109+
]
110+
111+
async function handleVolumeChange(volumeId: string) {
112+
selectedVolumeId = volumeId || null
98113
await poll()
99114
}
100115
@@ -152,13 +167,14 @@
152167
{#if volumes.length === 0}
153168
<span class="smb-empty">No SMB volumes mounted</span>
154169
{:else}
155-
<select class="smb-select" value={selectedVolumeId ?? ''} onchange={handleVolumeChange}>
156-
{#each volumes as v (v.volume_id)}
157-
<option value={v.volume_id}>
158-
{v.name} ({v.server}){v.disconnected ? ' — disconnected' : ''}
159-
</option>
160-
{/each}
161-
</select>
170+
<div class="smb-select-wrap">
171+
<Select
172+
items={volumeItems}
173+
value={selectedVolumeId ?? ''}
174+
onChange={(v: string) => void handleVolumeChange(v)}
175+
ariaLabel="SMB volume"
176+
/>
177+
</div>
162178
{/if}
163179
</label>
164180
<label class="smb-control smb-control-inline">
@@ -167,13 +183,15 @@
167183
</label>
168184
<label class="smb-control smb-control-inline">
169185
<span>every</span>
170-
<select bind:value={intervalMs} class="smb-select smb-select-narrow" disabled={!autoRefresh}>
171-
<option value={250}>250 ms</option>
172-
<option value={500}>500 ms</option>
173-
<option value={1000}>1 s</option>
174-
<option value={2000}>2 s</option>
175-
<option value={5000}>5 s</option>
176-
</select>
186+
<div class="smb-select-wrap smb-select-narrow">
187+
<Select
188+
items={intervalItems}
189+
value={String(intervalMs)}
190+
onChange={(v: string) => (intervalMs = Number(v))}
191+
disabled={!autoRefresh}
192+
ariaLabel="Refresh interval"
193+
/>
194+
</div>
177195
</label>
178196
<button class="index-button" onclick={() => void refreshVolumes()}>Refresh volumes</button>
179197
<button class="index-button" onclick={() => void poll()}>Snapshot now</button>
@@ -669,18 +687,13 @@
669687
color: var(--color-text-tertiary);
670688
}
671689
672-
.smb-select {
673-
padding: 3px 6px;
674-
font-size: var(--font-size-sm);
675-
font-family: var(--font-system), sans-serif;
676-
background: var(--color-bg-tertiary);
677-
color: var(--color-text-primary);
678-
border: 1px solid var(--color-border);
679-
border-radius: var(--radius-sm);
690+
.smb-select-wrap {
691+
display: inline-flex;
692+
min-width: 160px;
680693
}
681694
682695
.smb-select-narrow {
683-
min-width: 70px;
696+
min-width: 90px;
684697
}
685698
686699
.smb-empty {

0 commit comments

Comments
 (0)