feat(ext/vault): fullscreen form header with dirty-state subtitle
Title left ('new login' / 'edit login'), subtitle below cycles between
'no changes' and 'unsaved · esc to cancel' on input events. Right side
shows the platform-aware save hint ('⌘+S to save' / 'Ctrl+S to save').
The actual ⌘+S keymap arrives in Phase 3 — this is a visual hint only.
This commit is contained in:
45
extension/src/vault/__tests__/form-wrapper.test.ts
Normal file
45
extension/src/vault/__tests__/form-wrapper.test.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
describe('fullscreen form dirty subtitle', () => {
|
||||||
|
const vaultSrc = fs.readFileSync(
|
||||||
|
path.resolve(__dirname, '../vault.ts'),
|
||||||
|
'utf-8',
|
||||||
|
);
|
||||||
|
|
||||||
|
it('contains renderFormWrapped function', () => {
|
||||||
|
expect(vaultSrc).toContain('function renderFormWrapped');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('starts pristine: renders "no changes" subtitle', () => {
|
||||||
|
expect(vaultSrc).toContain("'no changes'");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('switches to dirty on first input event', () => {
|
||||||
|
expect(vaultSrc).toContain("'unsaved · esc to cancel'");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('listens on input and change events on the scroll element', () => {
|
||||||
|
expect(vaultSrc).toContain("scrollEl.addEventListener('input', markDirty, true)");
|
||||||
|
expect(vaultSrc).toContain("scrollEl.addEventListener('change', markDirty, true)");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks clean on save', () => {
|
||||||
|
expect(vaultSrc).toContain('markClean()');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('contains platform-aware SAVE_HINT', () => {
|
||||||
|
expect(vaultSrc).toContain('SAVE_HINT');
|
||||||
|
expect(vaultSrc).toContain('⌘+S to save');
|
||||||
|
expect(vaultSrc).toContain('Ctrl+S to save');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders fullscreen-form-header element', () => {
|
||||||
|
expect(vaultSrc).toContain('fullscreen-form-header');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders form-dirty-sub element', () => {
|
||||||
|
expect(vaultSrc).toContain('form-dirty-sub');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1606,6 +1606,29 @@ textarea {
|
|||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Phase 2B: fullscreen form header */
|
||||||
|
.fullscreen-form-header {
|
||||||
|
padding: 14px 24px;
|
||||||
|
border-bottom: 1px solid var(--border-mid);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
.fullscreen-form-header .title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
.fullscreen-form-header .sub {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
.fullscreen-form-header .hint {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-dim);
|
||||||
|
}
|
||||||
|
|
||||||
/* Phase 2B: sticky save bar + scrollable form pane */
|
/* Phase 2B: sticky save bar + scrollable form pane */
|
||||||
.form-pane {
|
.form-pane {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user