feat: US-006 - Frontend: Icon picker as compact dropdown

This commit is contained in:
Echo
2026-02-10 19:16:50 +00:00
parent 9a899f94fd
commit e52d38793b
2 changed files with 380 additions and 12 deletions

View File

@@ -626,7 +626,84 @@
box-shadow: 0 0 0 2px var(--bg-surface), 0 0 0 4px var(--accent);
}
/* Icon picker */
/* Icon picker dropdown */
.icon-picker-dropdown {
position: relative;
}
.icon-picker-trigger {
width: 100%;
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-3);
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
cursor: pointer;
transition: all var(--transition-base);
min-height: 44px;
}
.icon-picker-trigger:hover {
background: var(--bg-hover);
border-color: var(--accent);
}
.icon-picker-trigger svg {
width: 20px;
height: 20px;
color: var(--text-primary);
}
.icon-picker-trigger span {
flex: 1;
text-align: left;
color: var(--text-primary);
}
.trigger-chevron {
transition: transform var(--transition-base);
}
.icon-picker-trigger.open .trigger-chevron {
transform: rotate(180deg);
}
.icon-picker-content {
position: absolute;
top: calc(100% + var(--space-1));
left: 0;
right: 0;
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 100;
display: none;
max-height: 300px;
overflow: hidden;
}
.icon-picker-content.visible {
display: block;
}
.icon-search-input {
width: 100%;
padding: var(--space-2);
border: none;
border-bottom: 1px solid var(--border);
background: var(--bg-base);
color: var(--text-primary);
font-size: var(--text-sm);
outline: none;
}
.icon-search-input:focus {
border-bottom-color: var(--accent);
}
.icon-picker-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
@@ -634,9 +711,6 @@
max-height: 250px;
overflow-y: auto;
padding: var(--space-2);
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--bg-base);
}
.icon-option {
@@ -649,6 +723,7 @@
cursor: pointer;
transition: all var(--transition-base);
background: var(--bg-surface);
min-height: 44px;
}
.icon-option:hover {
@@ -1125,7 +1200,17 @@
<!-- Icon -->
<div class="form-field">
<label class="form-label">Icon</label>
<div class="icon-picker-grid" id="iconPicker"></div>
<div class="icon-picker-dropdown">
<button type="button" class="icon-picker-trigger" id="iconPickerTrigger" onclick="toggleIconPicker()">
<i data-lucide="smile" id="selectedIconDisplay"></i>
<span>Select Icon</span>
<i data-lucide="chevron-down" class="trigger-chevron" id="iconPickerChevron"></i>
</button>
<div class="icon-picker-content" id="iconPickerContent">
<input type="text" id="iconSearch" class="icon-search-input" placeholder="Search icons..." oninput="filterIcons()">
<div class="icon-picker-grid" id="iconPicker"></div>
</div>
</div>
</div>
<!-- Priority -->
@@ -1855,6 +1940,69 @@
<i data-lucide="${icon}"></i>
</div>`
).join('');
// Update trigger button with selected icon
const selectedIconDisplay = document.getElementById('selectedIconDisplay');
selectedIconDisplay.setAttribute('data-lucide', selectedIcon);
lucide.createIcons();
}
// Toggle icon picker dropdown
function toggleIconPicker() {
const content = document.getElementById('iconPickerContent');
const trigger = document.getElementById('iconPickerTrigger');
const isOpen = content.classList.contains('visible');
if (isOpen) {
closeIconPicker();
} else {
openIconPicker();
}
}
// Open icon picker dropdown
function openIconPicker() {
const content = document.getElementById('iconPickerContent');
const trigger = document.getElementById('iconPickerTrigger');
const search = document.getElementById('iconSearch');
content.classList.add('visible');
trigger.classList.add('open');
// Reset search
search.value = '';
filterIcons();
// Focus search input
setTimeout(() => search.focus(), 50);
}
// Close icon picker dropdown
function closeIconPicker() {
const content = document.getElementById('iconPickerContent');
const trigger = document.getElementById('iconPickerTrigger');
content.classList.remove('visible');
trigger.classList.remove('open');
}
// Filter icons based on search query
function filterIcons() {
const searchQuery = document.getElementById('iconSearch').value.toLowerCase();
const iconPickerContainer = document.getElementById('iconPicker');
const filteredIcons = commonIcons.filter(icon =>
icon.toLowerCase().includes(searchQuery)
);
iconPickerContainer.innerHTML = filteredIcons.map(icon =>
`<div class="icon-option ${icon === selectedIcon ? 'selected' : ''}"
onclick="selectIcon('${icon}')">
<i data-lucide="${icon}"></i>
</div>`
).join('');
lucide.createIcons();
}
@@ -1864,13 +2012,20 @@
// Update icon options
const iconOptions = document.querySelectorAll('.icon-option');
iconOptions.forEach((option, index) => {
if (commonIcons[index] === icon) {
option.classList.add('selected');
} else {
option.classList.remove('selected');
}
iconOptions.forEach(option => {
option.classList.remove('selected');
});
// Add selected class to clicked option
event.target.closest('.icon-option')?.classList.add('selected');
// Update trigger button display
const selectedIconDisplay = document.getElementById('selectedIconDisplay');
selectedIconDisplay.setAttribute('data-lucide', icon);
lucide.createIcons();
// Close dropdown after selection
closeIconPicker();
}
// Update frequency params based on selected type
@@ -2472,6 +2627,18 @@
}
});
// Click outside icon picker to close
document.addEventListener('click', (e) => {
const dropdown = document.querySelector('.icon-picker-dropdown');
const content = document.getElementById('iconPickerContent');
if (content && content.classList.contains('visible')) {
if (!dropdown.contains(e.target)) {
closeIconPicker();
}
}
});
// Restore collapsed/expanded state from localStorage
restoreWeeklySummaryState();