feat: add EmojiPickerButton and replace plain-text emoji inputs

Create a reusable EmojiPickerButton component that wraps EmojiPicker
in a compact dropdown toggle. Replace the plain <input type="text">
emoji fields in quick action icons (EditCollectionModal) and role
icons (roles page) with the new picker for a consistent UX.
This commit is contained in:
xarmian
2026-04-10 19:24:46 +00:00
parent 44f249994e
commit cabb552faf
3 changed files with 95 additions and 17 deletions
@@ -3,6 +3,7 @@
import type { Collection, CollectionUpdate, CollectionSettings, FieldDef, FieldMigration, QuickAction } from '$lib/types';
import { parseSchema, parseSettings } from '$lib/types';
import EmojiPicker from '$lib/components/common/EmojiPicker.svelte';
import EmojiPickerButton from '$lib/components/common/EmojiPickerButton.svelte';
import { toastStore } from '$lib/stores/toast.svelte';
interface Props {
@@ -602,14 +603,7 @@
{#each itemActions as { action, index } (index)}
<div class="action-card">
<div class="action-card-top">
<input
class="action-icon-input"
type="text"
placeholder="⚡"
bind:value={action.icon}
maxlength="2"
title="Icon (emoji)"
/>
<EmojiPickerButton bind:value={action.icon} placeholder="⚡" />
<input
class="action-label-input"
type="text"
@@ -644,14 +638,7 @@
{#each collectionActions as { action, index } (index)}
<div class="action-card">
<div class="action-card-top">
<input
class="action-icon-input"
type="text"
placeholder="⚡"
bind:value={action.icon}
maxlength="2"
title="Icon (emoji)"
/>
<EmojiPickerButton bind:value={action.icon} placeholder="⚡" />
<input
class="action-label-input"
type="text"
@@ -0,0 +1,90 @@
<script lang="ts">
import EmojiPicker from '$lib/components/common/EmojiPicker.svelte';
interface Props {
value: string;
placeholder?: string;
size?: 'sm' | 'md';
}
let { value = $bindable(), placeholder = '⚡', size = 'sm' }: Props = $props();
let open = $state(false);
function handleWindowClick(e: MouseEvent) {
if (open && !(e.target as HTMLElement)?.closest('.emoji-picker-button')) {
open = false;
}
}
function handleSelect(emoji: string) {
value = emoji;
open = false;
}
</script>
<svelte:window onclick={handleWindowClick} />
<div class="emoji-picker-button" class:size-md={size === 'md'}>
<button
type="button"
class="trigger"
class:open
class:empty={!value}
onclick={() => (open = !open)}
>
{value || placeholder}
</button>
{#if open}
<div class="dropdown">
<EmojiPicker selected={value} onselect={handleSelect} />
</div>
{/if}
</div>
<style>
.emoji-picker-button {
position: relative;
display: inline-flex;
}
.trigger {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 1.1em;
line-height: 1;
padding: 0;
}
.size-md .trigger {
width: 42px;
height: 42px;
}
.trigger:hover {
border-color: var(--text-muted);
}
.trigger.open {
border-color: var(--accent-blue);
}
.trigger.empty {
color: var(--text-muted);
}
.dropdown {
position: absolute;
z-index: 50;
top: calc(100% + 4px);
left: 0;
}
</style>
@@ -8,6 +8,7 @@
import { itemUrlId } from '$lib/types';
import type { Item, Collection, RoleBoardLane, AgentRole } from '$lib/types';
import ItemCard from '$lib/components/collections/ItemCard.svelte';
import EmojiPickerButton from '$lib/components/common/EmojiPickerButton.svelte';
import { dndzone, TRIGGERS, SHADOW_ITEM_MARKER_PROPERTY_NAME } from 'svelte-dnd-action';
import type { DndEvent } from 'svelte-dnd-action';
@@ -449,7 +450,7 @@
<div class="role-field-group">
<label class="role-field-label">Icon & Name</label>
<div class="role-edit-row">
<input class="role-input role-input-icon" type="text" bind:value={editIcon} placeholder="🔨" />
<EmojiPickerButton bind:value={editIcon} placeholder="🔨" size="md" />
<input class="role-input" type="text" bind:value={editName} placeholder="Role name" />
</div>
</div>