feat: add IconGrid, EntityPicker controls and enhance search panel
Port icon grid and entity picker patterns from wled-screen-controller. IconGrid replaces plain <select> elements with visual icon grids for known item sets (widget type, icon type, healthcheck method, permission level). EntityPicker replaces search dropdowns with a command-palette style overlay with keyboard navigation and filtering. Enhance SearchDialog with keyboard navigation (arrow keys, Enter, Escape), grouped results with section headers, active highlight, and a footer with shortcut hints.
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { t } from 'svelte-i18n';
|
||||
import IconGrid from '$lib/components/ui/IconGrid.svelte';
|
||||
import EntityPicker from '$lib/components/ui/EntityPicker.svelte';
|
||||
import type { IconGridItem } from '$lib/components/ui/IconGrid.svelte';
|
||||
import type { EntityPickerItem } from '$lib/components/ui/EntityPicker.svelte';
|
||||
|
||||
interface Props {
|
||||
sectionId: string;
|
||||
@@ -31,6 +35,26 @@
|
||||
let statusLabel = $state('');
|
||||
let statusAppIds = $state<string[]>([]);
|
||||
|
||||
const widgetTypeItems: IconGridItem[] = [
|
||||
{ value: 'app', icon: '🖥️', label: 'App' },
|
||||
{ value: 'bookmark', icon: '🔖', label: 'Bookmark' },
|
||||
{ value: 'note', icon: '📝', label: 'Note' },
|
||||
{ value: 'embed', icon: '🧩', label: 'Embed' },
|
||||
{ value: 'status', icon: '📊', label: 'Status' }
|
||||
];
|
||||
|
||||
const noteFormatItems: IconGridItem[] = [
|
||||
{ value: 'markdown', icon: '📝', label: 'Markdown' },
|
||||
{ value: 'text', icon: '📄', label: 'Plain Text' }
|
||||
];
|
||||
|
||||
const appPickerItems: EntityPickerItem[] = $derived(
|
||||
apps.map((app) => ({
|
||||
value: app.id,
|
||||
label: app.name
|
||||
}))
|
||||
);
|
||||
|
||||
function resetForm() {
|
||||
selectedWidgetType = 'app';
|
||||
selectedAppId = '';
|
||||
@@ -94,40 +118,30 @@
|
||||
</script>
|
||||
|
||||
<div class="mb-3 rounded-lg border border-border bg-muted/50 p-3">
|
||||
<!-- Widget Type Selector -->
|
||||
<!-- Widget Type Selector (Icon Grid) -->
|
||||
<div class="mb-3">
|
||||
<label for="widget-type-{sectionId}" class="mb-1 block text-sm font-medium text-foreground">
|
||||
<label class="mb-1 block text-sm font-medium text-foreground">
|
||||
Widget Type
|
||||
</label>
|
||||
<select
|
||||
id="widget-type-{sectionId}"
|
||||
<IconGrid
|
||||
items={widgetTypeItems}
|
||||
bind:value={selectedWidgetType}
|
||||
class="w-full rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground transition-colors focus:border-primary focus:outline-none focus:ring-2 focus:ring-ring/30"
|
||||
>
|
||||
<option value="app">App</option>
|
||||
<option value="bookmark">Bookmark</option>
|
||||
<option value="note">Note</option>
|
||||
<option value="embed">Embed</option>
|
||||
<option value="status">Status</option>
|
||||
</select>
|
||||
columns={5}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Type-specific config forms -->
|
||||
{#if selectedWidgetType === 'app'}
|
||||
<div>
|
||||
<label for="widget-app-{sectionId}" class="mb-1 block text-sm font-medium text-foreground">
|
||||
<label class="mb-1 block text-sm font-medium text-foreground">
|
||||
{$t('widget.select_app')}
|
||||
</label>
|
||||
<select
|
||||
id="widget-app-{sectionId}"
|
||||
<EntityPicker
|
||||
items={appPickerItems}
|
||||
bind:value={selectedAppId}
|
||||
class="w-full rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground transition-colors focus:border-primary focus:outline-none focus:ring-2 focus:ring-ring/30"
|
||||
>
|
||||
<option value="">{$t('widget.choose_app')}</option>
|
||||
{#each apps as app (app.id)}
|
||||
<option value={app.id}>{app.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
placeholder={$t('widget.choose_app')}
|
||||
searchPlaceholder={$t('widget.choose_app')}
|
||||
/>
|
||||
</div>
|
||||
{:else if selectedWidgetType === 'bookmark'}
|
||||
<div class="grid gap-3 sm:grid-cols-2">
|
||||
@@ -177,15 +191,12 @@
|
||||
{:else if selectedWidgetType === 'note'}
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label for="note-format-{sectionId}" class="mb-1 block text-sm font-medium text-foreground">Format</label>
|
||||
<select
|
||||
id="note-format-{sectionId}"
|
||||
<label class="mb-1 block text-sm font-medium text-foreground">Format</label>
|
||||
<IconGrid
|
||||
items={noteFormatItems}
|
||||
bind:value={noteFormat}
|
||||
class="w-full rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground transition-colors focus:border-primary focus:outline-none focus:ring-2 focus:ring-ring/30"
|
||||
>
|
||||
<option value="markdown">Markdown</option>
|
||||
<option value="text">Plain Text</option>
|
||||
</select>
|
||||
columns={2}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="note-content-{sectionId}" class="mb-1 block text-sm font-medium text-foreground">Content</label>
|
||||
|
||||
Reference in New Issue
Block a user