/** * Marketplace App Shell — the standard chrome for an app built on top of * AlfaDocs (the kind a partner scaffolds with Claude Code, Lovable, etc.). * * It is a thin, opinionated composition of kit primitives: an `AppFrame` with * a branded `Header` (the maker `ProductLockup` — " by Alfadocs" — * rendered `monochrome` so it rides the header's ink; maker is the lockup * style for external marketplace extensions), a `Sidebar` for app * navigation, `HeaderSettings` (theme/locale/accessibility), and a bare-avatar * account menu (fullscreen + sign-out) matching the main app shell. An * optional `endSidebar` docks a tool rail (for example Alia) on the inline-end. * `sidebarContent`, `sidebarHeader` and `sidebarActions` let the host build * the sidebar from the kit's Sidebar parts instead of the flat `nav`. * `ConnectWithAlfadocs` is the matching pre-auth screen, with the full-colour * maker lockup and `SignInWithAlfadocsButton`. * * Like `AliaSidebar`, this pattern ships a RUNTIME component (surfaced via * `src/patterns/index.ts` and the library bundle) because consuming apps mount * it in production rather than copying the source. * * Auth is intentionally decoupled: the shell takes the signed-in `user` and an * `onSignOut` callback as props, and `ConnectWithAlfadocs` takes a `status` + * `onConnect`. Wire them to `@alfadocs/auth/react`'s `useAlfadocsAuth()` in the * consuming app — the kit never touches OAuth or secrets (those live in the * server-side `@alfadocs/auth` BFF). * * All user-visible copy is consumer-provided (props) so each app localises in * its own i18n; the only kit-owned strings reuse existing translated `ui.*` * keys (sidebar label, skip link, menu button). */ import { type MouseEvent, type ReactNode } from 'react'; import { type SidebarItemMove, type SidebarMode } from '../../components/sidebar'; export interface MarketplaceNavItem { id: string; /** Pre-translated label — apps localise their own nav copy. */ label: string; href: string; /** Optional leading icon (a lucide-react element or equivalent). */ icon?: ReactNode; /** Optional unseen-item count rendered as a pill badge. */ badgeCount?: number; /** Mark the item as the current page (`aria-current='page'`). */ isActive?: boolean; /** * Optional click handler threaded onto the nav link's ``. The lightweight * alternative to `renderLink`: intercept a same-origin click (call * `event.preventDefault()`) and route client-side without a full page load — * no router `` needed. Applies to both the standalone sidebar item and * the embedded tab. When `renderLink` is supplied it's handed through to the * consumer's link element instead. */ onClick?: (event: MouseEvent) => void; } /** * Props the shell hands to `renderLink` for each nav item. The consumer returns * their router's `` carrying the same `className` (the kit's * active styling) and `aria-current` (`'page'` on the active item) so a tab / * sidebar-item click is a client-side route change, not a full page reload. */ export interface MarketplaceRenderLinkProps { /** The item's destination — feed it to your router's `to` / `href`. */ href: string; /** The kit's classes — apply verbatim to the link element. */ className?: string; /** Icon + label + badge — render as the link's children. */ children: ReactNode; /** `'page'` on the active item, otherwise omitted — forward to the link. */ 'aria-current'?: 'page'; /** * The item's stable id, as the agent / automation DOM hook. Spread it onto * your router link so the item stays addressable by `[data-tab-id]` — see * PRS §26 §6. Only supplied on the bottom-tab-bar path. */ 'data-tab-id'?: string; } /** * Render a nav item through the consumer's router instead of the kit's own * ``. Returned by the consumer; the shell forwards `href`, the kit's * `className`, the item children, and `aria-current` so the active styling and * the `aria-current="page"` marker survive a client-side route change. */ export type MarketplaceRenderLink = (props: MarketplaceRenderLinkProps) => ReactNode; export interface MarketplaceAppUser { /** Display name — drives the avatar initials + the account-menu header. */ name: string; /** Optional email shown under the name in the account menu. */ email?: string; /** Optional avatar image URL. */ avatarSrc?: string; } export interface MarketplaceAppShellLabels { /** Accessible name for the account-menu trigger, e.g. "Account menu". */ accountMenu: string; /** The sign-out menu item, e.g. "Sign out". */ signOut: string; /** * Optional fullscreen menu item label, e.g. "Toggle fullscreen". The item * renders whenever `onToggleFullscreen` is supplied; when this label is * omitted it falls back to the translated `ui.*` "Toggle fullscreen" string. */ fullscreen?: string; } export interface MarketplaceAppShellProps { /** Extension name — renders as the maker lockup " by Alfadocs". */ productName: string; /** Link target for the brand lockup. Default `'/'`. */ productHref?: string; /** * Nav items, in order. Standalone ignores them when `sidebarContent` is set; * the embedded tab strip always uses them. Never-embedded apps can pass `[]`. */ nav: MarketplaceNavItem[]; /** * Standalone sidebar body built from the kit's Sidebar parts. When set, even * to `null`, it replaces the flat `nav` in every mode, inside the shell's own * `SidebarBody` and `