fix(ext/popup): replace item type dropdown with selection view
Clicking "+ new" now navigates to a type selection view instead of showing a dropdown that gets clipped by popup bounds. The selection view displays all item types as buttons in a scrollable list. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,18 @@
|
||||
/// Typed-item add/edit form dispatcher. Each type's renderForm lives in
|
||||
/// its own module under ./types/. Document stays "coming soon" until γ.
|
||||
|
||||
import { navigate, getState } from '../popup';
|
||||
import { navigate, getState, setState, escapeHtml } from '../popup';
|
||||
import type { Item, ItemType } from '../../shared/types';
|
||||
|
||||
const TYPE_OPTIONS: Array<{ type: ItemType; icon: string; label: 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: 'document', icon: '📄', label: 'document' },
|
||||
{ type: 'totp', icon: '⏱️', label: 'totp' },
|
||||
];
|
||||
import * as login from './types/login';
|
||||
import * as secureNote from './types/secure-note';
|
||||
import * as identity from './types/identity';
|
||||
@@ -12,7 +22,7 @@ import * as totp from './types/totp';
|
||||
import * as documentType from './types/document';
|
||||
|
||||
export function renderItemForm(app: HTMLElement, mode: 'add' | 'edit'): void {
|
||||
login.teardown(); // detail-view's ticker/listener don't leak into form
|
||||
login.teardown();
|
||||
secureNote.teardown();
|
||||
identity.teardown();
|
||||
card.teardown();
|
||||
@@ -21,6 +31,13 @@ export function renderItemForm(app: HTMLElement, mode: 'add' | 'edit'): void {
|
||||
documentType.teardown();
|
||||
const state = getState();
|
||||
const existing = mode === 'edit' ? state.selectedItem : null;
|
||||
|
||||
// Show type selection for new items when no type is pre-selected
|
||||
if (mode === 'add' && !state.newType) {
|
||||
renderTypeSelection(app);
|
||||
return;
|
||||
}
|
||||
|
||||
const type: ItemType = existing?.type ?? state.newType ?? 'login';
|
||||
|
||||
switch (type) {
|
||||
@@ -34,6 +51,35 @@ export function renderItemForm(app: HTMLElement, mode: 'add' | 'edit'): void {
|
||||
}
|
||||
}
|
||||
|
||||
function renderTypeSelection(app: HTMLElement): void {
|
||||
app.innerHTML = `
|
||||
<div class="pad">
|
||||
<div style="display:flex; align-items:center; gap:12px; margin-bottom:16px;">
|
||||
<button class="btn" id="back-btn">← back</button>
|
||||
<h3 style="margin:0;">new item</h3>
|
||||
</div>
|
||||
<div class="type-select-list">
|
||||
${TYPE_OPTIONS.map((opt) => `
|
||||
<button class="type-select-row" data-type="${opt.type}">
|
||||
<span class="type-select-icon">${opt.icon}</span>
|
||||
<span>${escapeHtml(opt.label)}</span>
|
||||
</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('back-btn')?.addEventListener('click', () => navigate('list'));
|
||||
|
||||
document.querySelectorAll<HTMLButtonElement>('[data-type]').forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
const type = btn.dataset.type as ItemType;
|
||||
setState({ newType: type });
|
||||
renderItemForm(app, 'add');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderComingSoon(app: HTMLElement, type: ItemType): void {
|
||||
app.innerHTML = `
|
||||
<div class="pad">
|
||||
|
||||
Reference in New Issue
Block a user