feat(ui): header slot + SidebarLayout.DefaultHeader for the top bar#344
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? |
|
Thanks @interacsean — yes, this feels well aligned with what I had in mind. I’m also happy to keep the lower-level header primitives and the namespaced internal migration out of this PR for now 👍 |
|
/review |
|
✅ API Design Review completed successfully! API Design Review complete for PR #344 (feat(ui): header slot + SidebarLayout.DefaultHeader). VERDICT: Approve No High or Medium issues found. The design is clean and consistent with the existing sidebar slot pattern. One Low-severity inline comment posted (Children.toArray legacy API + misleading key-preservation comment). Additional Low observation (unchanged file, can't post inline): All public API changes are additive, all existing call sites are compatible, and the namespace pattern (SidebarLayout.DefaultHeader / .DefaultSidebar) is well-implemented. |
There was a problem hiding this comment.
Generated by API Design Review for issue #344 · 95.8 AIC · ⌖ 8.93 AIC · ⊞ 5.8K
Comment /review to run again
…en.toArray (#484) Addresses PR review: React.Children.toArray is a legacy API and remaps consumer keys (`key="bell"` → `.$bell`). Render `actions` as-is instead — a single node, or an array the consumer keys — falling back to the appearance switcher when omitted. React drops null/false at render, so no normalization is needed, and single-node actions no longer produce a key warning. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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