feat(*): Collapse the icon picker once an icon is chosen

This commit is contained in:
Josh Creek
2026-07-12 21:18:23 +01:00
parent ec7598e15c
commit fe1e3c2abf
+140 -68
View File
@@ -73,6 +73,8 @@
let newMachineDraft = createEmptyMachine(); let newMachineDraft = createEmptyMachine();
let newDeviceDraft = createEmptyDevice(); let newDeviceDraft = createEmptyDevice();
let iconSearch = ''; let iconSearch = '';
let iconPickerExpanded = false;
let lastIconPickerContext = '';
let isLoadingData = false; let isLoadingData = false;
let loadError: string | null = null; let loadError: string | null = null;
@@ -330,6 +332,14 @@
? selectedVm?.iconKey ?? '' ? selectedVm?.iconKey ?? ''
: ''; : '';
$: selectedTargetIconPath = resolveIconPath(selectedTargetIconKey || undefined); $: selectedTargetIconPath = resolveIconPath(selectedTargetIconKey || undefined);
// collapse the icon picker whenever the modal target changes
$: {
const context = addModalKind ?? (selectedTarget ? JSON.stringify(selectedTarget) : '');
if (context !== lastIconPickerContext) {
lastIconPickerContext = context;
iconPickerExpanded = false;
}
}
$: addModalIconKey = $: addModalIconKey =
addModalKind === 'machine' addModalKind === 'machine'
? newMachineDraft.iconKey ?? '' ? newMachineDraft.iconKey ?? ''
@@ -1315,6 +1325,9 @@
if (!target) { if (!target) {
return; return;
} }
if (normalized) {
iconPickerExpanded = false;
}
if (target.type === 'machine') { if (target.type === 'machine') {
const index = target.index; const index = target.index;
@@ -1350,6 +1363,9 @@
function setAddModalIconKey(nextKey: string) { function setAddModalIconKey(nextKey: string) {
const normalized = nextKey.trim(); const normalized = nextKey.trim();
if (normalized) {
iconPickerExpanded = false;
}
if (addModalKind === 'machine') { if (addModalKind === 'machine') {
newMachineDraft.iconKey = normalized || undefined; newMachineDraft.iconKey = normalized || undefined;
return; return;
@@ -1724,11 +1740,13 @@
function closeSelectedTargetModal() { function closeSelectedTargetModal() {
selectedTarget = null; selectedTarget = null;
rackNewNameTarget = null; rackNewNameTarget = null;
iconPickerExpanded = false;
clearIconSearch(); clearIconSearch();
} }
function closeAddEntityModal() { function closeAddEntityModal() {
addModalKind = null; addModalKind = null;
iconPickerExpanded = false;
clearIconSearch(); clearIconSearch();
} }
@@ -2388,44 +2406,59 @@
maxWidth="880px" maxWidth="880px"
on:close={closeSelectedTargetModal} on:close={closeSelectedTargetModal}
> >
<div class="icon-search"> {#if selectedTargetIconKey && !iconPickerExpanded}
<label> <div class="icon-search-collapsed">
Icon Search
<input
type="text"
bind:value={iconSearch}
placeholder="Search icons..."
autocomplete="off"
/>
</label>
{#if selectedTargetIconPath}
<div class="icon-preview-card"> <div class="icon-preview-card">
<img src={selectedTargetIconPath} alt="Selected icon preview" loading="lazy" /> {#if selectedTargetIconPath}
<img src={selectedTargetIconPath} alt="Selected icon preview" loading="lazy" />
{/if}
<code>{selectedTargetIconKey}</code> <code>{selectedTargetIconKey}</code>
</div> </div>
{/if} <button type="button" on:click={() => (iconPickerExpanded = true)}>Change Icon</button>
<p class="icon-results-meta">
Showing {Math.min(visibleIconDefinitions.length, iconResultLimit)} of {filteredIconDefinitions.length}
matching icons
</p>
<div class="icon-results-grid">
{#each visibleIconDefinitions as icon (icon.key)}
<button
type="button"
class="icon-result-option"
on:click={() => setSelectedTargetIconKey(icon.key)}
title={icon.key}
>
<img src={icon.path} alt={icon.label} loading="lazy" />
<span>{icon.label}</span>
<code>{icon.key}</code>
</button>
{/each}
</div> </div>
{#if filteredIconDefinitions.length === 0} {:else}
<p class="icon-results-empty">No icons match your search.</p> <div class="icon-search">
{/if} <label>
</div> Icon Search
<input
type="text"
bind:value={iconSearch}
placeholder="Search icons..."
autocomplete="off"
/>
</label>
{#if selectedTargetIconPath}
<div class="icon-preview-card">
<img src={selectedTargetIconPath} alt="Selected icon preview" loading="lazy" />
<code>{selectedTargetIconKey}</code>
<button type="button" class="icon-collapse" on:click={() => (iconPickerExpanded = false)}>
Done
</button>
</div>
{/if}
<p class="icon-results-meta">
Showing {Math.min(visibleIconDefinitions.length, iconResultLimit)} of {filteredIconDefinitions.length}
matching icons
</p>
<div class="icon-results-grid">
{#each visibleIconDefinitions as icon (icon.key)}
<button
type="button"
class="icon-result-option"
on:click={() => setSelectedTargetIconKey(icon.key)}
title={icon.key}
>
<img src={icon.path} alt={icon.label} loading="lazy" />
<span>{icon.label}</span>
<code>{icon.key}</code>
</button>
{/each}
</div>
{#if filteredIconDefinitions.length === 0}
<p class="icon-results-empty">No icons match your search.</p>
{/if}
</div>
{/if}
<datalist id="cable-type-options"> <datalist id="cable-type-options">
<option value="Cat5e"></option> <option value="Cat5e"></option>
<option value="Cat6"></option> <option value="Cat6"></option>
@@ -3293,44 +3326,59 @@
maxWidth="760px" maxWidth="760px"
on:close={closeAddEntityModal} on:close={closeAddEntityModal}
> >
<div class="icon-search"> {#if addModalIconKey && !iconPickerExpanded}
<label> <div class="icon-search-collapsed">
Icon Search
<input
type="text"
bind:value={iconSearch}
placeholder="Search icons..."
autocomplete="off"
/>
</label>
{#if addModalIconPath}
<div class="icon-preview-card"> <div class="icon-preview-card">
<img src={addModalIconPath} alt="Selected icon preview" loading="lazy" /> {#if addModalIconPath}
<img src={addModalIconPath} alt="Selected icon preview" loading="lazy" />
{/if}
<code>{addModalIconKey}</code> <code>{addModalIconKey}</code>
</div> </div>
{/if} <button type="button" on:click={() => (iconPickerExpanded = true)}>Change Icon</button>
<p class="icon-results-meta">
Showing {Math.min(visibleIconDefinitions.length, iconResultLimit)} of {filteredIconDefinitions.length}
matching icons
</p>
<div class="icon-results-grid">
{#each visibleIconDefinitions as icon (icon.key)}
<button
type="button"
class="icon-result-option"
on:click={() => setAddModalIconKey(icon.key)}
title={icon.key}
>
<img src={icon.path} alt={icon.label} loading="lazy" />
<span>{icon.label}</span>
<code>{icon.key}</code>
</button>
{/each}
</div> </div>
{#if filteredIconDefinitions.length === 0} {:else}
<p class="icon-results-empty">No icons match your search.</p> <div class="icon-search">
{/if} <label>
</div> Icon Search
<input
type="text"
bind:value={iconSearch}
placeholder="Search icons..."
autocomplete="off"
/>
</label>
{#if addModalIconPath}
<div class="icon-preview-card">
<img src={addModalIconPath} alt="Selected icon preview" loading="lazy" />
<code>{addModalIconKey}</code>
<button type="button" class="icon-collapse" on:click={() => (iconPickerExpanded = false)}>
Done
</button>
</div>
{/if}
<p class="icon-results-meta">
Showing {Math.min(visibleIconDefinitions.length, iconResultLimit)} of {filteredIconDefinitions.length}
matching icons
</p>
<div class="icon-results-grid">
{#each visibleIconDefinitions as icon (icon.key)}
<button
type="button"
class="icon-result-option"
on:click={() => setAddModalIconKey(icon.key)}
title={icon.key}
>
<img src={icon.path} alt={icon.label} loading="lazy" />
<span>{icon.label}</span>
<code>{icon.key}</code>
</button>
{/each}
</div>
{#if filteredIconDefinitions.length === 0}
<p class="icon-results-empty">No icons match your search.</p>
{/if}
</div>
{/if}
{#if addModalKind === 'machine'} {#if addModalKind === 'machine'}
<section class="edit-section"> <section class="edit-section">
<label> <label>
@@ -4176,6 +4224,30 @@
gap: 0.6rem; gap: 0.6rem;
} }
.icon-search-collapsed {
margin-bottom: 0.7rem;
display: flex;
align-items: center;
gap: 0.6rem;
}
.icon-search-collapsed button,
.icon-collapse {
border: 1px solid var(--panel-border);
background: var(--panel-bg);
color: var(--panel-contrast);
padding: 0.35rem 0.55rem;
border-radius: 7px;
font-size: 0.8rem;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
}
.icon-collapse {
margin-left: auto;
}
.icon-search label { .icon-search label {
display: grid; display: grid;
gap: 0.25rem; gap: 0.25rem;