web: make the board usable on a phone, not just reachable
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 8s
CI & Build / Python tests (push) Successful in 34s
CI & Build / Build & push image (push) Successful in 50s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m13s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m6s
Desktop (Tauri) / Update manifest (push) Successful in 6s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 8s
CI & Build / Python tests (push) Successful in 34s
CI & Build / Build & push image (push) Successful in 50s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m13s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m6s
Desktop (Tauri) / Update manifest (push) Successful in 6s
The controls a card carries were always-visible overlays on a touch device — correct as far as it went (task 2697: a finger cannot hover, and the pill is the only way to pin or archive), but they were still absolutely positioned, so they sat ON the note's own title. A card reading "thought sync tauri app" rendered as "ught sync tauri app" with the grip parked over the first three characters, and the four-icon pill covering the right half of the first line. Placement is now CSS's decision. One element each, two placements: where a pointer can hover they lift out of flow into the floating top-corner pills they have always been; where nothing can hover they stay in flow as a footer row, which cannot overlap anything by construction. Keyed on hover rather than width, for the same reason `.hover-reveal` already is — a narrow window on a laptop still hovers, a wide tablet still doesn't. The colour popover moved inside the action set so it follows it, and opens into the card from either end. The header was sharing one phone-width row between a menu button, the logo, the lens name, a search field and four icons; everything in it was truncated, the lens down to "N…" and the search box to an empty pill. It wraps now, so search takes its own line below sm, and account / settings / sign-out move into the drawer where there is room to name them rather than guess at a glyph. One input, moved by CSS — duplicating it would have meant two `searchInput` refs and a `/` shortcut that focuses the wrong one. Also closes the other half of task 2706, which was waiting on a device to look at: `viewport-fit=cover` together with the `env(safe-area-inset-*)` padding that makes it safe (sides on body, top on the sticky header, bottom on the board and the drawer), and `100dvh` behind an @supports so the app box follows the visual viewport when the keyboard opens instead of the layout viewport. Both halves in one change, as that task insisted. And the composer no longer tells a phone to "Press Enter".
This commit is contained in:
@@ -252,10 +252,18 @@ async function signOut() {
|
||||
>
|
||||
Skip to notes
|
||||
</a>
|
||||
<!-- `pt-[env(safe-area-inset-top)]`: with `viewport-fit=cover` the page draws
|
||||
under the status bar / notch, and this is the one box that sits there — a
|
||||
sticky header pinned to y=0. Resolves to 0 wherever there is no cutout, so
|
||||
it costs nothing on a desktop or a flat-topped phone. -->
|
||||
<header
|
||||
class="sticky top-0 z-20 border-b border-neutral-200 bg-neutral-50/90 backdrop-blur dark:border-neutral-800 dark:bg-neutral-950/90"
|
||||
class="sticky top-0 z-20 border-b border-neutral-200 bg-neutral-50/90 pt-[env(safe-area-inset-top,0px)] backdrop-blur dark:border-neutral-800 dark:bg-neutral-950/90"
|
||||
>
|
||||
<div class="flex items-center gap-3 px-4 py-3">
|
||||
<!-- Wraps, so the search field can take a line of its own on a narrow screen.
|
||||
In one row it was sharing ~360px with a menu button, the logo, the lens
|
||||
name and four icons, which left every one of them truncated — the lens read
|
||||
as "N…" and the search box as an empty pill. -->
|
||||
<div class="flex flex-wrap items-center gap-x-3 gap-y-2 px-4 py-3">
|
||||
<button
|
||||
type="button"
|
||||
class="icon-btn sm:hidden"
|
||||
@@ -295,19 +303,24 @@ async function signOut() {
|
||||
<span class="truncate font-medium text-neutral-600 dark:text-neutral-300">{{ lensName }}</span>
|
||||
</span>
|
||||
|
||||
<div class="flex flex-1 justify-center">
|
||||
<!-- `order-last w-full` drops this onto its own line below sm (a full-width
|
||||
flex item forces the wrap); from sm it returns to the middle of the row.
|
||||
ONE input either way, moved by CSS rather than duplicated — the `/`
|
||||
shortcut focuses `searchInput`, and two of those would be one ref too
|
||||
many. -->
|
||||
<div class="order-last flex w-full justify-center sm:order-none sm:w-auto sm:flex-1">
|
||||
<input
|
||||
ref="searchInput"
|
||||
:value="searchText"
|
||||
type="search"
|
||||
placeholder="Search notes…"
|
||||
aria-label="Search notes"
|
||||
class="w-full max-w-md rounded-lg border border-neutral-300 bg-white px-3 py-1.5 text-sm outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:bg-neutral-900"
|
||||
class="w-full max-w-md rounded-lg border border-neutral-300 bg-white px-3 py-2 text-base outline-none focus-visible:ring-2 focus-visible:ring-brand sm:py-1.5 sm:text-sm dark:border-neutral-700 dark:bg-neutral-900"
|
||||
@input="onSearch(($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex shrink-0 items-center gap-3">
|
||||
<div class="ml-auto flex shrink-0 items-center gap-1 sm:ml-0 sm:gap-3">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center gap-1 rounded-lg bg-brand px-2.5 py-1.5 text-sm font-semibold text-neutral-900 hover:brightness-95 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand"
|
||||
@@ -341,7 +354,7 @@ async function signOut() {
|
||||
<RouterLink
|
||||
v-if="!desktopApp"
|
||||
to="/account"
|
||||
class="icon-btn"
|
||||
class="icon-btn hidden sm:inline-flex"
|
||||
title="Linked devices"
|
||||
aria-label="Linked devices"
|
||||
>
|
||||
@@ -350,7 +363,7 @@ async function signOut() {
|
||||
<RouterLink
|
||||
v-if="session.user?.is_admin"
|
||||
to="/settings"
|
||||
class="icon-btn"
|
||||
class="icon-btn hidden sm:inline-flex"
|
||||
title="Settings"
|
||||
aria-label="Settings"
|
||||
>
|
||||
@@ -364,7 +377,7 @@ async function signOut() {
|
||||
<button
|
||||
v-if="!desktopApp"
|
||||
type="button"
|
||||
class="icon-btn"
|
||||
class="icon-btn hidden sm:inline-flex"
|
||||
title="Sign out"
|
||||
aria-label="Sign out"
|
||||
@click="signOut"
|
||||
@@ -383,7 +396,7 @@ async function signOut() {
|
||||
@click="drawer = false"
|
||||
></div>
|
||||
<aside
|
||||
class="fixed inset-y-0 left-0 z-40 w-64 -translate-x-full overflow-y-auto border-r border-neutral-200 bg-neutral-50 p-3 transition-transform duration-200 sm:static sm:z-auto sm:w-56 sm:translate-x-0 dark:border-neutral-800 dark:bg-neutral-950"
|
||||
class="fixed inset-y-0 left-0 z-40 w-64 -translate-x-full overflow-y-auto border-r border-neutral-200 bg-neutral-50 p-3 pb-[calc(0.75rem+env(safe-area-inset-bottom,0px))] pt-[calc(0.75rem+env(safe-area-inset-top,0px))] transition-transform duration-200 sm:static sm:z-auto sm:w-56 sm:translate-x-0 sm:pb-3 sm:pt-3 dark:border-neutral-800 dark:bg-neutral-950"
|
||||
:class="drawer ? 'translate-x-0' : ''"
|
||||
>
|
||||
<nav class="flex flex-col gap-0.5 text-sm" @click="drawer = false">
|
||||
@@ -469,6 +482,34 @@ async function signOut() {
|
||||
<Icon name="download" /> Export
|
||||
</a>
|
||||
<ImportNotes />
|
||||
|
||||
<!-- Account, settings and sign-out, for the screens where they are NOT in
|
||||
the header. Four icons plus a search field never fit one phone-width
|
||||
row, and the header is the wrong place to lose: it holds the only way
|
||||
back to the board. Here they get room to be named instead of guessed
|
||||
at from a glyph.
|
||||
Hidden from sm up, where the header carries them again — so they are
|
||||
in exactly one place at any width, never both. -->
|
||||
<div
|
||||
v-if="!desktopApp"
|
||||
class="mt-3 flex flex-col gap-0.5 border-t border-neutral-200 pt-3 sm:hidden dark:border-neutral-800"
|
||||
>
|
||||
<p class="truncate px-3 pb-1 text-xs text-neutral-400">{{ session.user?.display_name }}</p>
|
||||
<RouterLink to="/account" class="nav-link" :class="route.name === 'account' ? 'nav-link-active' : ''">
|
||||
<Icon name="device" /> Linked devices
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
v-if="session.user?.is_admin"
|
||||
to="/settings"
|
||||
class="nav-link"
|
||||
:class="route.name === 'settings' ? 'nav-link-active' : ''"
|
||||
>
|
||||
<Icon name="settings" /> Settings
|
||||
</RouterLink>
|
||||
<button type="button" class="nav-link w-full text-left" @click="signOut">
|
||||
<Icon name="logout" /> Sign out
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user