From ad3923c18cf7315e58ee653b21ec87e81e6a4b5e Mon Sep 17 00:00:00 2001 From: "Digibyte.X" Date: Fri, 2 Oct 2026 17:33:02 +0200 Subject: [PATCH] web-ui: top-align single-column views; reveal toggle on password and PIN fields Login / Import / Create / Settings were vertically centred in the content column (align-self: center), which on tall screens left a dead band above the page heading. Now top-aligned; horizontal centring unchanged. New PasswordInput component wraps every password / PIN / passphrase field with an eye button that toggles the text. Also: wallet-order drag&drop, path-search sweep, settings panel sections, change-address policy. --- packages/web-ui/src/ImportScreen.tsx | 8 ++--- packages/web-ui/src/LoginPage.tsx | 8 ++--- packages/web-ui/src/MasterAuthScreens.tsx | 22 ++++++------ packages/web-ui/src/PasswordInput.tsx | 44 +++++++++++++++++++++++ packages/web-ui/src/PinConfirmModal.tsx | 8 ++--- packages/web-ui/src/SettingsPage.tsx | 14 ++++---- packages/web-ui/src/styles.css | 22 +++++++++++- 7 files changed, 94 insertions(+), 32 deletions(-) create mode 100644 packages/web-ui/src/PasswordInput.tsx diff --git a/packages/web-ui/src/ImportScreen.tsx b/packages/web-ui/src/ImportScreen.tsx index fa30617..c381a8a 100644 --- a/packages/web-ui/src/ImportScreen.tsx +++ b/packages/web-ui/src/ImportScreen.tsx @@ -1,4 +1,5 @@ import { useEffect, useMemo, useState } from 'react'; +import { PasswordInput } from './PasswordInput.js'; import { validateSeedPhrase, validateElectrumPhrase, @@ -313,15 +314,14 @@ export function ImportScreen(): JSX.Element { ) : ( <> - setPassphrase(e.target.value)} autoComplete="off" spellCheck={false} - placeholder="Leave empty if the source wallet didn't set one" - /> + placeholder="Leave empty if the source wallet didn't set one" />
Mixes into the seed derivation. Same phrase + different passphrase = different wallet, different addresses, different balance. Must exactly match what the diff --git a/packages/web-ui/src/LoginPage.tsx b/packages/web-ui/src/LoginPage.tsx index ca245e3..d8fb9c9 100644 --- a/packages/web-ui/src/LoginPage.tsx +++ b/packages/web-ui/src/LoginPage.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react'; +import { PasswordInput } from './PasswordInput.js'; import { Keystore, LocalStorageAdapter, type StoredSeed } from '@dgb-wallet/keystore'; import { useActiveSeed, type WalletKind } from './ActiveSeed.js'; import { useSessionAuth } from './session-auth.js'; @@ -163,14 +164,13 @@ export function LoginPage({ {isLegacy && (
- setLegacyPass({ ...legacyPass, [s.label]: e.target.value })} placeholder="original passphrase" onKeyDown={(e) => { if (e.key === 'Enter') void tryLegacyOpen(s.label); }} - autoFocus - /> + autoFocus /> diff --git a/packages/web-ui/src/PasswordInput.tsx b/packages/web-ui/src/PasswordInput.tsx new file mode 100644 index 0000000..f41c790 --- /dev/null +++ b/packages/web-ui/src/PasswordInput.tsx @@ -0,0 +1,44 @@ +import { forwardRef, useState, type InputHTMLAttributes } from 'react'; + +// Password / PIN / passphrase field with a reveal toggle. A drop-in for +// ``: every other prop passes straight through, +// the ref lands on the . The eye button is type="button" so it +// never submits a surrounding form, and it is excluded from the tab +// order (tabIndex -1) so Enter / Tab behaviour on the field itself is +// unchanged — mouse or touch users get the toggle, keyboard users keep +// their flow. +export const PasswordInput = forwardRef>( + function PasswordInput({ className, style, ...props }, ref) { + const [shown, setShown] = useState(false); + return ( + + + + + ); + }, +); diff --git a/packages/web-ui/src/PinConfirmModal.tsx b/packages/web-ui/src/PinConfirmModal.tsx index fcd32f3..6a5c198 100644 --- a/packages/web-ui/src/PinConfirmModal.tsx +++ b/packages/web-ui/src/PinConfirmModal.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef, useState } from 'react'; +import { PasswordInput } from './PasswordInput.js'; import { useSessionAuth } from './session-auth.js'; // Small modal that asks for the session PIN before letting a @@ -61,9 +62,9 @@ export function PinConfirmModal({

{title}

{message &&

{message}

} - + style={{ padding: '10px 12px', fontSize: 15, letterSpacing: '0.2em' }} /> {err &&
{err}
}
diff --git a/packages/web-ui/src/styles.css b/packages/web-ui/src/styles.css index b76c5ba..aa66bc8 100644 --- a/packages/web-ui/src/styles.css +++ b/packages/web-ui/src/styles.css @@ -89,7 +89,11 @@ body:has(.shell-dashboard) { .shell:not(.shell-dashboard) .content { max-width: 720px; margin: 0 auto; - align-self: center; + /* Horizontal centering only. align-self:center also centred the + column VERTICALLY, which on a tall viewport floated Import / + Login / Settings into the middle of the page with a dead band + above the heading. */ + align-self: flex-start; } .shell-dashboard .content { max-width: none; @@ -994,6 +998,22 @@ textarea, input { transition: border-color 0.15s; } textarea:focus, input:focus { border-color: var(--accent); } + +/* Password / PIN field with reveal toggle (PasswordInput.tsx). The wrap + takes the input's place in the layout; the input keeps every base + style and gains right padding for the eye button. */ +.pw-wrap { position: relative; display: block; width: 100%; } +.pw-wrap .pw-input { padding-right: 42px; } +.pw-toggle { + position: absolute; top: 50%; right: 8px; transform: translateY(-50%); + width: 28px; height: 28px; padding: 0; + display: inline-flex; align-items: center; justify-content: center; + background: transparent; border: 0; border-radius: 6px; + color: var(--text-muted); cursor: pointer; +} +.pw-toggle:hover { color: var(--text); background: rgba(255,255,255,0.06); } +.settings-inline-row .pw-wrap { flex: 1; min-width: 160px; width: auto; } +.wallet-picker-legacy .pw-wrap { flex: 1; min-width: 160px; width: auto; } textarea { min-height: 100px; line-height: 1.5; } button.primary {