${capChip('lower', 'lower')}
${capChip('upper', 'upper')}
${capChip('first', 'first_of_each')}
diff --git a/extension/src/popup/components/settings-vault.ts b/extension/src/popup/components/settings-vault.ts
index d7477fa..e819567 100644
--- a/extension/src/popup/components/settings-vault.ts
+++ b/extension/src/popup/components/settings-vault.ts
@@ -6,7 +6,7 @@ import { getState, setState, sendMessage, navigate, escapeHtml } from '../popup'
import type {
VaultSettings, TrashRetention, HistoryRetention, GeneratorRequest,
} from '../../shared/types';
-import { openGeneratorPopover } from './generator-panel';
+import { openGeneratorPanel, closeGeneratorPanel, isGeneratorPanelOpen } from './generator-panel';
let pendingSettings: VaultSettings | null = null;
let activeKeyHandler: ((e: KeyboardEvent) => void) | null = null;
@@ -128,7 +128,7 @@ export function renderVaultSettings(app: HTMLElement): void {
@@ -194,12 +194,18 @@ export function renderVaultSettings(app: HTMLElement): void {
});
document.getElementById('configure-gen')?.addEventListener('click', (e) => {
- if (!pendingSettings) return;
- const anchor = e.currentTarget as HTMLElement;
- openGeneratorPopover({
- anchor,
+ const trigger = e.currentTarget as HTMLElement;
+ if (isGeneratorPanelOpen()) {
+ closeGeneratorPanel();
+ return;
+ }
+ const generatorSection = trigger.closest('.settings-section') as HTMLElement | null;
+ if (!generatorSection || pendingSettings === null) return;
+ openGeneratorPanel({
+ parent: generatorSection,
+ trigger,
initial: pendingSettings.generator_defaults,
- onPicked: () => {/* no-op — user is here to save as default, not pick */},
+ context: 'configure-defaults',
});
});
diff --git a/extension/src/popup/components/types/login.ts b/extension/src/popup/components/types/login.ts
index a055e32..ad617b5 100644
--- a/extension/src/popup/components/types/login.ts
+++ b/extension/src/popup/components/types/login.ts
@@ -14,7 +14,7 @@ import {
renderSectionsEditor,
wireSectionsEditor,
} from '../fields';
-import { openGeneratorPopover, closeGeneratorPopover } from '../generator-panel';
+import { openGeneratorPanel, closeGeneratorPanel, isGeneratorPanelOpen } from '../generator-panel';
/// Called by the dispatcher before each render. Stops any in-flight
/// tickers / intervals / listeners the previous view may have attached.
@@ -29,7 +29,7 @@ export function teardown(): void {
activeFormEscHandler = null;
}
sectionsExpanded = false;
- closeGeneratorPopover();
+ closeGeneratorPanel();
}
// ----------------------------------------------------------------------
@@ -240,7 +240,7 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite
@@ -266,11 +266,19 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite
wireSectionsEditor(app, sectionsDraft, rerender);
document.getElementById('gen-btn')?.addEventListener('click', (e) => {
- const anchor = e.currentTarget as HTMLElement;
+ const trigger = e.currentTarget as HTMLElement;
+ if (isGeneratorPanelOpen()) {
+ closeGeneratorPanel();
+ return;
+ }
+ const passwordRow = trigger.closest('.form-group') as HTMLElement | null;
+ if (!passwordRow) return;
const initial = getState().generatorDefaults ?? DEFAULT_PASSWORD_REQUEST;
- openGeneratorPopover({
- anchor,
+ openGeneratorPanel({
+ parent: passwordRow, // panel mounts inside the password form-group
+ trigger,
initial,
+ context: 'fill-field',
onPicked: (value) => {
const pw = document.getElementById('f-password') as HTMLInputElement | null;
if (pw) { pw.value = value; pw.type = 'text'; }
diff --git a/extension/src/popup/styles.css b/extension/src/popup/styles.css
index a698090..29e2597 100644
--- a/extension/src/popup/styles.css
+++ b/extension/src/popup/styles.css
@@ -595,66 +595,163 @@ textarea {
}
.disclosure__body .add-section:hover { border-color: #484f58; color: #c9d1d9; }
-/* --- generator popover (β₂ slice 4) --- */
-.generator-popover {
- position: absolute; z-index: 9999999;
- background: #161b22; border: 1px solid #30363d; border-radius: 6px;
- box-shadow: 0 4px 16px rgba(0,0,0,0.5);
- padding: 14px; min-width: 300px; max-width: 340px;
- font-size: 11px; font-family: system-ui, sans-serif; color: #c9d1d9;
+/* --- generator panel (gen-UX redesign) --- */
+
+.gen-trigger {
+ background: #7c5719;
+ color: #fff3cf;
+ border: none;
+ border-radius: 4px;
+ padding: 0 12px;
+ font-size: 16px;
+ cursor: pointer;
+ line-height: 1;
+ min-width: 38px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
}
-.generator-popover .gen-header {
- display: flex; justify-content: space-between; align-items: center;
+.gen-trigger:hover { background: #aa812a; }
+.gen-trigger[aria-expanded="true"] { background: #aa812a; }
+
+.gen-panel {
+ background: #161b22;
+ border: 1px solid #aa812a;
+ border-radius: 6px;
+ padding: 11px;
+ margin: 6px 0;
+ font-size: 11px;
+ color: #c9d1d9;
+}
+.gen-panel .panel-toggle {
+ display: flex;
+ gap: 4px;
+ background: #21262d;
+ border-radius: 4px;
+ padding: 2px;
margin-bottom: 8px;
}
-.generator-popover .gen-title { font-size: 11px; font-weight: 600; color: #8b949e; text-transform: lowercase; letter-spacing: 0.08em; }
-.generator-popover .gen-close {
- background: transparent; border: 0; color: #8b949e; cursor: pointer;
- font-size: 14px; padding: 2px 6px;
+.gen-panel .panel-toggle button {
+ flex: 1;
+ background: transparent;
+ border: 0;
+ color: #8b949e;
+ padding: 5px;
+ font-size: 11px;
+ cursor: pointer;
+ border-radius: 3px;
+ font-weight: 600;
}
-.generator-popover .gen-row {
- display: flex; align-items: center; gap: 8px; margin: 6px 0;
+.gen-panel .panel-toggle button.active {
+ background: #aa812a;
+ color: #fff3cf;
}
-.generator-popover .gen-row__label {
- color: #8b949e; width: 70px; flex-shrink: 0;
- font-size: 10px; text-transform: lowercase;
+.gen-panel .knob {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin: 6px 0;
}
-.generator-popover .gen-toggle-group {
- display: flex; gap: 0; border: 1px solid #30363d; border-radius: 3px; overflow: hidden;
+.gen-panel .knob__label {
+ color: #8b949e;
+ width: 56px;
+ flex-shrink: 0;
+ font-size: 10px;
}
-.generator-popover .gen-toggle-group button {
- background: transparent; border: 0; color: #8b949e;
- padding: 3px 10px; cursor: pointer; font: inherit; font-size: 10px;
+.gen-panel .knob__slider { flex: 1; }
+.gen-panel .knob__value {
+ font-family: ui-monospace, monospace;
+ min-width: 24px;
+ text-align: right;
+ color: #c9d1d9;
}
-.generator-popover .gen-toggle-group button.active { background: #7c5719; color: #fff; }
-.generator-popover .gen-slider { flex: 1; }
-.generator-popover .gen-slider + span {
- color: #c9d1d9; font-variant-numeric: tabular-nums;
- font-family: monospace; min-width: 24px; text-align: right;
+.gen-panel .classes {
+ display: flex;
+ gap: 8px;
+ font-size: 10px;
+ margin: 6px 0;
+ flex-wrap: wrap;
+ color: #8b949e;
}
-.generator-popover .gen-check-grid {
- display: grid; grid-template-columns: 1fr 1fr;
- gap: 4px 16px; margin: 6px 0; font-size: 11px;
+.gen-panel .classes label {
+ display: flex;
+ align-items: center;
+ gap: 3px;
+ user-select: none;
+ cursor: pointer;
}
-.generator-popover .gen-check-grid label {
- display: flex; align-items: center; gap: 6px;
+.gen-panel .preview {
+ background: #0d1117;
+ border: 1px solid #30363d;
+ border-radius: 4px;
+ padding: 8px 10px;
+ margin-top: 8px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
}
-.generator-popover .gen-preview {
- margin: 10px 0 8px; padding: 8px 10px;
- background: #0d1117; border: 1px solid #30363d; border-radius: 4px;
- font-family: "SF Mono", "JetBrains Mono", monospace; color: #c9d1d9;
- display: flex; justify-content: space-between; align-items: center; gap: 8px;
- word-break: break-all;
+.gen-panel .preview__value {
+ flex: 1;
+ color: #f1cf6e;
+ font-family: ui-monospace, monospace;
+ font-size: 12px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
-.generator-popover .gen-preview__regen {
- flex-shrink: 0; background: transparent; border: 0;
- color: #d2ab43; cursor: pointer; font-size: 12px;
+.gen-panel .preview__regen {
+ background: transparent;
+ border: 0;
+ color: #8b949e;
+ cursor: pointer;
+ padding: 0 4px;
+ font-size: 14px;
}
-.generator-popover .gen-actions {
- display: grid; grid-template-columns: 1fr 1fr;
- gap: 6px; margin-top: 10px;
+.gen-panel .more {
+ color: #8b949e;
+ font-size: 10px;
+ margin-top: 6px;
+ cursor: pointer;
+ user-select: none;
+ padding: 2px 0;
}
-.generator-popover .gen-actions .btn { font-size: 11px; padding: 5px 10px; }
+.gen-panel .more summary {
+ list-style: none;
+ outline: none;
+}
+.gen-panel .more summary::-webkit-details-marker { display: none; }
+.gen-panel .more:hover { color: #d2ab43; }
+.gen-panel .more__advanced { margin-top: 6px; }
+.gen-panel .actions {
+ display: flex;
+ gap: 6px;
+ margin-top: 10px;
+ align-items: center;
+}
+.gen-panel .actions .save-link {
+ flex: 1;
+ background: transparent;
+ border: 0;
+ color: #8b949e;
+ cursor: pointer;
+ font-size: 10px;
+ text-align: left;
+ padding: 4px 0;
+ text-decoration: underline;
+ text-decoration-color: #30363d;
+ text-underline-offset: 2px;
+}
+.gen-panel .actions .save-link:hover {
+ color: #d2ab43;
+ text-decoration-color: #d2ab43;
+}
+.gen-panel .actions .save-link__toast {
+ color: #3fb950;
+ margin-left: 6px;
+ font-size: 10px;
+}
+
+/* keep .gen-preview-line — it's the summary-text in vault settings, separate from panel */
/* --- settings-vault screen (β₂ slice 5) --- */
.settings-header { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }