feat(ui): header slot + SidebarLayout.DefaultHeader for the top bar#344
feat(ui): header slot + SidebarLayout.DefaultHeader for the top bar#344interacsean wants to merge 2 commits into
Conversation
Provides an official extension point for the app-shell top bar: a `headerActions` prop on SidebarLayout renders custom action components (notification bell, user menu, global search, etc.) on the right side, immediately before the appearance switcher. Accepts a single node or an array, laid out in a horizontal, vertically-centered row. Replaces fragile consumer workarounds that queried the header DOM and injected a React portal to place controls next to the appearance switcher. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7d5b787 to
2b2b0e5
Compare
|
WIP API thought: this may already be in flux, but sharing the comment anyway in case it’s useful. My understanding of One reason I’m slightly hesitant about If we want to open up the header as an extension point, I wonder if something like More generally, one thing I’ve been trying to optimize for in this kind of API is to keep extension points like Then, for cases where consumers only want to slightly extend the built-in header, we could provide a helper like Related to that, thinking about symmetry, I’m also starting to feel that |
…ltHeader (#484) Pivots the top-bar extension API per PR review (IzumiSy): instead of a one-off `headerActions` prop, `SidebarLayout` now exposes a full-region `header?: ReactNode` slot (mirroring `sidebar`), defaulting to the built-in `SidebarLayout.DefaultHeader`. - `SidebarLayout.DefaultHeader` (also exported as `DefaultHeader`) renders the trigger + breadcrumb and an `actions` cluster. `actions` defaults to `[<AppearanceSwitcher />]`; passing `actions` replaces the whole right-hand cluster (include `<AppearanceSwitcher />` to keep it) — avoiding a proliferation of one-off header props. - `DefaultSidebar` is now also namespaced as `SidebarLayout.DefaultSidebar` (top-level export retained for compat). - Docs updated across docs/components (sidebar-layout, default-header [new], default-sidebar, appearance-switcher) and the catalogue + generated skill reference. Example app demonstrates the new API. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks @IzumiSy — this steer makes sense, and I've reworked the PR to follow it. Dropped the one-off
// 1. default
<SidebarLayout />
// 2. extend the built-in header (common case)
<SidebarLayout
header={
<SidebarLayout.DefaultHeader
actions={[<NotificationBell key="bell" />, <AppearanceSwitcher key="appearance" />]}
/>
}
/>
// 3. replace entirely
<SidebarLayout header={<MyCustomHeader />} />
Symmetry: Docs updated across Two things I intentionally scoped out of this PR as follow-ups, lmk if you'd rather fold them in:
Does this shape match what you had in mind? |
Summary
Adds an official, composable extension point for the app-shell top bar.
SidebarLayoutgains a full-regionheader?: ReactNodeslot (mirroring the existingsidebarslot), defaulting to the built-inSidebarLayout.DefaultHeader.API
Three tiers of customization:
SidebarLayout.DefaultHeaderThe built-in top bar: sidebar trigger + breadcrumb on the left, an
actionscluster on the right.actions—ReactNode | ReactNode[](optional). The entire right-hand cluster, in an opinionated horizontal, vertically-centered row.[<AppearanceSwitcher />], so the zero-config header is unchanged.actionsreplaces the whole cluster, including the appearance switcher — include<AppearanceSwitcher />explicitly to keep it (AppearanceSwitcheris a public export).actions={[]}renders an empty right side.hideAppearanceSwitcher,headerLeft, …).Namespacing
DefaultHeaderis exposed asSidebarLayout.DefaultHeaderand as a top-levelDefaultHeaderexport.DefaultSidebaris now also exposed asSidebarLayout.DefaultSidebar(top-levelDefaultSidebarexport retained for backwards compatibility).Why
Resolves tailor-inc/platform-planning#484 — Customizable NavBar (header). Previously there was no supported way to extend the top bar, so consumers (e.g. TRM) queried the header DOM, created a portal target with a
MutationObserver, andcreatePortal-ed their controls in — fragile and tied to internal structure. This replaces that with a first-class slot.Changes
packages/core/src/components/sidebar/default-header.tsx— newDefaultHeader(trigger + breadcrumb +actions)packages/core/src/components/sidebar/sidebar-layout.tsx—headerslot; namespaceSidebarLayout.DefaultHeader/.DefaultSidebarpackages/core/src/components/sidebar/index.ts,packages/core/src/index.ts— exportspackages/core/src/components/sidebar/sidebar-layout.test.tsx— 13 tests (header slot,actionsreplace/default/empty, namespace)docs/components/{sidebar-layout,default-header (new),default-sidebar,appearance-switcher}.md,catalogue/src/fundamental/components.md+ regenerated skill referenceexamples/vite-app/src/App.tsx— demonstrates the new API.changeset/—minorbumpFollow-ups (out of scope)
SidebarLayout.DefaultSidebar.Testing
oxlint— 0 warnings/errors ·tsc --noEmit— clean ·vitest— 1206 passed.d.tscarriesnamespace SidebarLayout { DefaultSidebar; DefaultHeader }; example app type-checks as a real consumeractions🤖 Generated with Claude Code