"use client";
import * as React from "react";
import type { ComponentDocSpec } from "@/lib/design-system/component-doc-types";
import {
UtilityBarBackPreview,
UtilityBarBreadcrumbPreview,
UtilityBarComfortPreview,
UtilityBarDensePreview,
UtilityBarProductlessPreview,
} from "@/components/design-system/utility-bar-previews";
function ex(
section: Omit<
ComponentDocSpec["sections"][number],
"children" | "description"
>,
children: React.ReactNode,
description?: string,
) {
return { ...section, children, description };
}
export const utilityBarComponentDoc: ComponentDocSpec = {
slug: "utility-bar",
summary:
"Full-width shell chrome for product context, global actions, Ask Leo, notifications, scope, and identity. Density and page mode determine which actions stay visible.",
sections: [
ex(
{ id: "comfort", title: "Comfort" },
,
"The production order at full width: sidebar control, product, global actions, Ask Leo, scope, and profile.",
),
ex(
{ id: "dense", title: "Dense and reflow" },
,
"Search, updates, help, support, and onboarding move into More. Notifications, Ask Leo, scope, and profile stay pinned.",
),
ex(
{ id: "back", title: "Back mode" },
,
"A parent destination replaces the product cluster while Ask Leo stays pinned.",
),
ex(
{ id: "breadcrumb", title: "Breadcrumb mode" },
,
"Record detail and deep routes show the ancestor trail instead of the back icon. Ask Leo stays pinned.",
),
ex(
{ id: "productless", title: "Workspace without a selected product" },
,
"The Exxat mark and workspace-global controls remain. Product-owned controls are absent.",
),
],
anatomy: [
{
part: "UtilityBarSlot",
description: "Shell mount above the app shell row.",
},
{
part: "UtilityBarProductSwitcher",
description:
"Leading product trigger. Dense mode keeps the product mark.",
},
{
part: "Breadcrumb",
description:
"Portaled from SiteHeader. Dense mode keeps the current location.",
},
{
part: "Search",
description: "Opens the command menu. Moves into More on Dense.",
},
{
part: "Support chat",
description:
"Message icon that dispatches exxat:open-support-chat for the host support provider; moves into More on Dense.",
},
{
part: "AskLeoToggle",
description:
"Labeled on Comfort and icon-only on Dense. Pinned before identity.",
},
{ part: "Onboarding", description: "Icon link to /builder/onboarding." },
{
part: "NotificationBell",
description: "Icon button + dropdown; always visible.",
},
{
part: "UtilityBarWhatsNew",
description: "Release updates; under More on Dense.",
},
{ part: "Get Help", description: "Help destination. Under More on Dense." },
{
part: "UtilityUserMenu",
description:
"Avatar trigger with Profile settings + Workspace settings (same URL as the former Settings gear).",
},
],
ux: {
job: "Give users one persistent place to search, open Ask Leo, check notifications, and reach onboarding without hunting through the sidebar.",
whenToUse: [
"Any signed-in shell route (not exam-lock, not /builder/onboarding).",
"When a surface needs a canonical trigger for a global action instead of a page-local one.",
],
whenNotToUse: [
"Page- or hub-scoped actions — those belong in PageHeader, not the utility bar.",
"One-off promo messaging — use SystemBanner instead (full-bleed strip above the utility bar).",
],
modernReferences: [
"Linear command bar + inbox bell",
"Notion top bar with search + updates bell",
],
patternDoc: "apps/web/docs/shell-utility-bar-pattern.md",
},
guidelines: {
do: [
"Reuse existing triggers (requestOpenCommandMenu, AskLeoToggle, getSecondaryNavForProduct, NAV_USER).",
"Use `utilityBarActionButtonClass` for icon actions.",
"Keep Search icon-only; Ask Leo shows icon + label before identity.",
"Resolve Workspace settings via getSecondaryNavForProduct(product).",
"Use Back mode for focus routes that publish a single parent destination.",
"Use Breadcrumb mode for record detail routes with an ancestor trail (`utilityBarMode: \"breadcrumb\"`, or default on record detail when `breadcrumbs` are set).",
"Use the productless inventory on Products home and sign-in flows.",
"Render the bar full width. Do not place it inside a centered content rail.",
],
dont: [
"Do not mount a Settings gear on the bar — use Workspace settings in the profile menu.",
"Do not add a second Search/Ask Leo/Notifications trigger elsewhere in the shell.",
"Do not give What's new its own dismissal store.",
"Do not invent alternate utility bar layout pickers.",
],
},
accessibility: [
"Every icon-only trigger has an aria-label + Tip (Search, Onboarding, Notifications, What's new, Help, profile).",
"Ask Leo keeps a visible label for recognition.",
"NotificationBell and What's new unread counts are also in the trigger aria-label.",
"Search and Ask Leo keep ⌘K / ⌘⌥K.",
],
extraImports: [
{ label: "ShellLayoutContext", path: "@/contexts/shell-layout-context" },
{
label: "UtilityBarProductSwitcher",
path: "@/components/utility-bar-product-switcher",
},
{ label: "NotificationBell", path: "@/components/notification-bell" },
{ label: "UtilityBarWhatsNew", path: "@/components/utility-bar-whats-new" },
{ label: "WhatsNewSheet", path: "@exxatdesignux/ui/components/ui/whats-new-sheet" },
{ label: "UtilityUserMenu", path: "@/components/utility-user-menu" },
],
relatedSlugs: ["banner", "site-header", "sidebar", "app-shell"],
};