diff --git a/extension/src/popup/components/item-form.ts b/extension/src/popup/components/item-form.ts
index 2176a9b..c9d8485 100644
--- a/extension/src/popup/components/item-form.ts
+++ b/extension/src/popup/components/item-form.ts
@@ -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 = `
+
+
+
+
new item
+
+
+ ${TYPE_OPTIONS.map((opt) => `
+
+ `).join('')}
+
+
+ `;
+
+ document.getElementById('back-btn')?.addEventListener('click', () => navigate('list'));
+
+ document.querySelectorAll('[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 = `
diff --git a/extension/src/popup/components/item-list.ts b/extension/src/popup/components/item-list.ts
index 18b21cf..0d7cfb6 100644
--- a/extension/src/popup/components/item-list.ts
+++ b/extension/src/popup/components/item-list.ts
@@ -67,9 +67,9 @@ export function renderItemList(app: HTMLElement): void {
setState({ searchQuery: searchInput.value, selectedIndex: 0 });
});
- document.getElementById('new-btn')?.addEventListener('click', (e) => {
- e.stopPropagation();
- showNewTypePicker(e.currentTarget as HTMLElement);
+ document.getElementById('new-btn')?.addEventListener('click', () => {
+ setState({ newType: null });
+ navigate('add');
});
document.getElementById('sync-btn')?.addEventListener('click', async () => {
@@ -249,12 +249,20 @@ function showNewTypePicker(anchor: HTMLElement): void {
boxShadow: '0 4px 12px rgba(0,0,0,0.4)',
padding: '4px',
minWidth: '160px',
+ maxHeight: '280px',
+ overflowY: 'auto',
zIndex: '999999',
fontSize: '12px',
});
const rect = anchor.getBoundingClientRect();
- picker.style.top = `${rect.bottom + 4}px`;
+ const dropdownHeight = 220; // approx height for 7 items
+ const spaceBelow = window.innerHeight - rect.bottom;
+ if (spaceBelow < dropdownHeight && rect.top > dropdownHeight) {
+ picker.style.bottom = `${window.innerHeight - rect.top + 4}px`;
+ } else {
+ picker.style.top = `${rect.bottom + 4}px`;
+ }
picker.style.left = `${rect.left}px`;
for (const opt of NEW_TYPE_OPTIONS) {
diff --git a/extension/src/popup/styles.css b/extension/src/popup/styles.css
index 896c583..d498908 100644
--- a/extension/src/popup/styles.css
+++ b/extension/src/popup/styles.css
@@ -1211,3 +1211,36 @@ textarea {
.history-entry__copy:hover {
opacity: 0.8;
}
+
+/* --- Type selection --- */
+
+.type-select-list {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.type-select-row {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 10px 12px;
+ background: #161b22;
+ border: 1px solid transparent;
+ border-radius: 6px;
+ color: #c9d1d9;
+ font-size: 13px;
+ cursor: pointer;
+ text-align: left;
+}
+
+.type-select-row:hover {
+ background: #21262d;
+ border-color: #30363d;
+}
+
+.type-select-icon {
+ font-size: 16px;
+ width: 20px;
+ text-align: center;
+}