feat(mobile): show entity name in mobile topbar on detail pages
Detail pages (clients, yachts, companies, berths, invoices, expenses)
now push their entity name + a back-button toggle to the mobile
topbar via useMobileChrome, replacing the URL UUID fallback that was
rendering before.
Supporting changes:
- useMobileChrome() no longer throws when called outside the
MobileLayoutProvider — desktop-tree consumers get a no-op
setChrome so callers don't have to branch on shell type.
- setChrome is now stable across renders (useCallback) so callers'
useEffect dependency arrays don't infinite-loop.
- DetailPageShell now also pushes its entityName + cleans up on
unmount, and hides its desktop-only sticky header on mobile so it
doesn't double up with the topbar (no current callers, prep for
Phase 4 migration).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,21 @@
|
||||
'use client';
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
import { useEffect, type ReactNode } from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useMobileChrome } from '@/components/layout/mobile/mobile-layout-provider';
|
||||
|
||||
/**
|
||||
* Wrapper for entity detail pages (clients, yachts, companies, etc.). Renders:
|
||||
* - sticky compact header (entity name + status pill)
|
||||
* - desktop sticky compact header (entity name + status pill)
|
||||
* - the children (existing tab dropdown selector + tab body)
|
||||
* - optional sticky bottom action shelf, pinned above the bottom tab bar on
|
||||
* mobile (`pb-[calc(56px+env(safe-area-inset-bottom))]` content padding).
|
||||
*
|
||||
* Mobile: the desktop sticky header is hidden because the mobile topbar already
|
||||
* shows the entity name (pushed via `useMobileChrome`). The optional back
|
||||
* button is also enabled on mobile so detail pages get an arrow back to the
|
||||
* list.
|
||||
*/
|
||||
export function DetailPageShell({
|
||||
entityName,
|
||||
@@ -24,16 +30,33 @@ export function DetailPageShell({
|
||||
bottomActions?: ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
const { setChrome } = useMobileChrome();
|
||||
|
||||
useEffect(() => {
|
||||
setChrome({ title: entityName, showBackButton: true });
|
||||
return () => {
|
||||
setChrome({ title: null, showBackButton: false });
|
||||
};
|
||||
}, [entityName, setChrome]);
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col min-h-full', className)}>
|
||||
<div className="sticky top-0 z-10 bg-background/95 backdrop-blur border-b border-border px-4 py-3 sm:px-6">
|
||||
{/* Desktop-only sticky header — mobile topbar covers this on small viewports. */}
|
||||
<div className="hidden sm:block sticky top-0 z-10 bg-background/95 backdrop-blur border-b border-border px-4 py-3 sm:px-6">
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<h2 className="truncate text-lg font-semibold text-foreground">{entityName}</h2>
|
||||
{status ? <div className="ml-auto shrink-0">{status}</div> : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cn('flex-1 px-4 py-4 sm:px-6 sm:py-6', bottomActions && 'pb-24 sm:pb-6')}>
|
||||
{/* Mobile inline status row — only shown when the page wants to display a status pill. */}
|
||||
{status ? (
|
||||
<div className="sm:hidden flex items-center justify-end px-1 pt-1">
|
||||
<div className="shrink-0">{status}</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className={cn('flex-1 sm:px-6 sm:py-6', bottomActions && 'pb-24 sm:pb-6')}>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user