Merge feat/mobile-ux-polish: berth/header/tab/contacts mobile fixes

# Conflicts:
#	src/components/clients/contacts-editor.tsx
This commit is contained in:
Matt Ciaccio
2026-05-03 16:20:12 +02:00
4 changed files with 95 additions and 73 deletions

View File

@@ -223,40 +223,54 @@ function ContactRow({
</div>
</div>
{/* Bottom / right: tag + actions. Hidden while the phone editor is active
to keep focus on the form — no chips fighting for space, no noise. */}
{/* Bottom / right: tag + actions.
Two layers of hiding compose here:
(a) phoneEditing — when the phone editor is open, hide the entire
action cluster (tag + star + trash) so the user can focus on
the form without chips fighting for space.
(b) contact.value — when the value is empty (stale import row,
aborted edit), hide just the tag + Make-primary star;
neither makes sense without a value. The trash icon stays
so the user can clean up the empty entry.
On touch (no hover), trash is always rendered; on desktop it
fades in on hover only (sm:opacity-0 + sm:group-hover:opacity-100). */}
{!phoneEditing ? (
<div className="flex shrink-0 items-center justify-end gap-2">
<div className="w-28 text-right text-xs text-muted-foreground">
<InlineEditableField
value={
contact.label && contact.label.toLowerCase() !== 'primary' ? contact.label : null
}
emptyText="Add tag"
placeholder="work, home…"
onSave={async (v) => {
await onUpdate({ label: v });
}}
/>
</div>
{contact.value ? (
<>
<div className="w-28 text-right text-xs text-muted-foreground">
<InlineEditableField
value={
contact.label && contact.label.toLowerCase() !== 'primary'
? contact.label
: null
}
emptyText="Add tag"
placeholder="work, home…"
onSave={async (v) => {
await onUpdate({ label: v });
}}
/>
</div>
<button
type="button"
onClick={togglePrimary}
title={contact.isPrimary ? 'Primary' : 'Make primary'}
className={cn(
'rounded p-1 transition-colors hover:bg-background/60',
contact.isPrimary ? 'text-primary' : 'text-muted-foreground/50',
)}
>
<Star className={cn('h-3.5 w-3.5', contact.isPrimary && 'fill-current')} />
</button>
<button
type="button"
onClick={togglePrimary}
title={contact.isPrimary ? 'Primary' : 'Make primary'}
className={cn(
'rounded p-1 transition-colors hover:bg-background/60',
contact.isPrimary ? 'text-primary' : 'text-muted-foreground/50',
)}
>
<Star className={cn('h-3.5 w-3.5', contact.isPrimary && 'fill-current')} />
</button>
</>
) : null}
<button
type="button"
onClick={onRemove}
title="Remove"
// Trash is opacity-0 on desktop hover-only; on touch, always show.
className="rounded p-1 text-muted-foreground/50 transition-all hover:bg-background/60 hover:text-destructive sm:opacity-0 sm:group-hover:opacity-100"
>
<Trash2 className="h-3.5 w-3.5" />