File
Blob: src/client/components/ui/page-chrome.tsx
| 1 | import type { ReactNode } from "react"; |
| 2 | import { EmojiIcon } from "@/client/components/ui/emoji-icon"; |
| 3 | import { PAGE_CONTENT_COLUMN_CLASS, PAGE_ICON_ROW_CLASS } from "@/client/components/ui/page-layout"; |
| 4 | |
| 5 | /** |
| 6 | * Page chrome shared by the workspace surface, share surface, and loading |
| 7 | * skeleton. Owns the layout (cover wrapper -> breadcrumb row -> icon row) and |
| 8 | * exposes named slots for surface-specific content. Consumers cannot reorder |
| 9 | * or restyle the rows -- that's the point of the primitive. |
| 10 | */ |
| 11 | export interface PageChromeProps { |
| 12 | /** PageCover element. Primitive supplies the gutter wrapper, group/cover, and relative positioning. */ |
| 13 | cover?: ReactNode; |
| 14 | /** Optional overlay positioned absolute right-2 top-2 inside the cover wrapper (workspace CoverPicker). */ |
| 15 | coverOverlay?: ReactNode; |
| 16 | /** PageBreadcrumbs (any mode) or breadcrumb skeleton. The row is omitted entirely when this is null/undefined (e.g., share root page); otherwise reserves min-h-8 even when content is shorter than the right-side actions. */ |
| 17 | breadcrumb?: ReactNode; |
| 18 | /** Right-aligned actions on the breadcrumb row. Workspace passes Summarize/Share/AvatarStack/SyncStatusDot. */ |
| 19 | breadcrumbActions?: ReactNode; |
| 20 | /** Icon row content: IconPicker (+ optional CoverPicker), <PageEmojiIcon>, or icon-shaped skeleton. */ |
| 21 | icon?: ReactNode; |
| 22 | } |
| 23 | |
| 24 | export function PageChrome({ cover, coverOverlay, breadcrumb, breadcrumbActions, icon }: PageChromeProps) { |
| 25 | return ( |
| 26 | <div className={PAGE_CONTENT_COLUMN_CLASS}> |
| 27 | {cover ? ( |
| 28 | <div className="group/cover relative -mx-4 -mt-10 mb-6 sm:-mx-8 lg:mx-0"> |
| 29 | {cover} |
| 30 | {coverOverlay ? <div className="absolute right-2 top-2">{coverOverlay}</div> : null} |
| 31 | </div> |
| 32 | ) : null} |
| 33 | |
| 34 | {breadcrumb ? ( |
| 35 | <div className="mb-6 flex min-h-8 items-center gap-3"> |
| 36 | <div className="min-w-0 flex-1">{breadcrumb}</div> |
| 37 | {breadcrumbActions ? <div className="flex shrink-0 items-center gap-3">{breadcrumbActions}</div> : null} |
| 38 | </div> |
| 39 | ) : null} |
| 40 | |
| 41 | {icon ? <div className={PAGE_ICON_ROW_CLASS}>{icon}</div> : null} |
| 42 | </div> |
| 43 | ); |
| 44 | } |
| 45 | |
| 46 | /** |
| 47 | * Read-only counterpart to IconPicker's interactive button. The `px-2 py-1` |
| 48 | * inset matches IconPicker's button (icon-picker.tsx:34, 57) so the painted |
| 49 | * emoji lands at the same horizontal position whether the icon is editable |
| 50 | * or not. If IconPicker's button padding changes, update here in lockstep. |
| 51 | */ |
| 52 | export function PageEmojiIcon({ emoji }: { emoji: string }) { |
| 53 | return ( |
| 54 | <div className="flex items-center px-2 py-1"> |
| 55 | <EmojiIcon emoji={emoji} size={28} /> |
| 56 | </div> |
| 57 | ); |
| 58 | } |