From 23759dc163d9ca124191a498c96ad40745359e97 Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Thu, 23 Apr 2026 23:07:33 -0400 Subject: [PATCH] feat(ext/popup): + New picker with all 7 item types (Document disabled) --- extension/src/popup/components/item-list.ts | 94 ++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/extension/src/popup/components/item-list.ts b/extension/src/popup/components/item-list.ts index db62bd7..f532401 100644 --- a/extension/src/popup/components/item-list.ts +++ b/extension/src/popup/components/item-list.ts @@ -67,7 +67,10 @@ export function renderItemList(app: HTMLElement): void { setState({ searchQuery: searchInput.value, selectedIndex: 0 }); }); - document.getElementById('new-btn')?.addEventListener('click', () => navigate('add')); + document.getElementById('new-btn')?.addEventListener('click', (e) => { + e.stopPropagation(); + showNewTypePicker(e.currentTarget as HTMLElement); + }); document.getElementById('sync-btn')?.addEventListener('click', async () => { setState({ loading: true, error: null }); @@ -215,3 +218,92 @@ function handleListKeydown(e: KeyboardEvent): void { return; } } + +// ---------------------------------------------------------------------- +// New-item type picker popover +// ---------------------------------------------------------------------- + +const NEW_TYPE_OPTIONS: Array<{ type: ItemType; icon: string; label: string; disabled?: boolean; tooltip?: string }> = [ + { type: 'login', icon: '🔑', label: 'login' }, + { type: 'secure_note', icon: '📝', label: 'secure note' }, + { type: 'identity', icon: '🪪', label: 'identity' }, + { type: 'card', icon: '💳', label: 'card' }, + { type: 'key', icon: '🗝', label: 'key' }, + { type: 'totp', icon: '⏱', label: 'totp' }, + { type: 'document', icon: '📄', label: 'document', disabled: true, tooltip: 'coming in γ — needs attachment upload' }, +]; + +function showNewTypePicker(anchor: HTMLElement): void { + document.querySelectorAll('.new-type-picker').forEach((el) => el.remove()); + + const picker = document.createElement('div'); + picker.className = 'new-type-picker'; + Object.assign(picker.style, { + position: 'absolute', + background: '#161b22', + border: '1px solid #30363d', + borderRadius: '6px', + boxShadow: '0 4px 12px rgba(0,0,0,0.4)', + padding: '4px', + minWidth: '160px', + zIndex: '999999', + fontSize: '12px', + }); + + const rect = anchor.getBoundingClientRect(); + picker.style.top = `${rect.bottom + 4}px`; + picker.style.left = `${rect.left}px`; + + for (const opt of NEW_TYPE_OPTIONS) { + const row = document.createElement('div'); + Object.assign(row.style, { + padding: '6px 10px', + cursor: opt.disabled ? 'not-allowed' : 'pointer', + color: opt.disabled ? '#484f58' : '#c9d1d9', + borderRadius: '4px', + display: 'flex', alignItems: 'center', gap: '8px', + }); + if (opt.tooltip) row.title = opt.tooltip; + const iconSpan = document.createElement('span'); + Object.assign(iconSpan.style, { fontSize: '14px', width: '16px', display: 'inline-block', textAlign: 'center' }); + iconSpan.textContent = opt.icon; + const labelSpan = document.createElement('span'); + labelSpan.textContent = opt.label; + row.appendChild(iconSpan); + row.appendChild(labelSpan); + if (!opt.disabled) { + row.addEventListener('mouseenter', () => { row.style.background = '#21262d'; }); + row.addEventListener('mouseleave', () => { row.style.background = 'transparent'; }); + row.addEventListener('click', (ev) => { + ev.stopPropagation(); + picker.remove(); + document.removeEventListener('click', closeOnOutside); + document.removeEventListener('keydown', closeOnEsc); + setState({ newType: opt.type }); + navigate('add'); + }); + } + picker.appendChild(row); + } + + document.body.appendChild(picker); + + const closeOnOutside = (ev: MouseEvent) => { + if (!picker.contains(ev.target as Node)) { + picker.remove(); + document.removeEventListener('click', closeOnOutside); + document.removeEventListener('keydown', closeOnEsc); + } + }; + const closeOnEsc = (ev: KeyboardEvent) => { + if (ev.key === 'Escape') { + picker.remove(); + document.removeEventListener('click', closeOnOutside); + document.removeEventListener('keydown', closeOnEsc); + } + }; + setTimeout(() => { + document.addEventListener('click', closeOnOutside); + document.addEventListener('keydown', closeOnEsc); + }, 0); +}