From 4139db9e927088fd6375fdacc9f4901ab8ac33b4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 01:23:37 +0000 Subject: [PATCH] docs: update pending API docs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CLAUDE.md | 12 + docs/api/use-page-meta.md | 5 +- docs/components/action-panel.md | 9 +- docs/components/ai-chat.md | 18 +- docs/components/alert.md | 5 +- docs/components/app-shell.md | 10 +- docs/components/badge.md | 17 +- docs/components/button.md | 2 +- docs/components/command-palette.md | 48 +-- docs/components/default-sidebar.md | 185 +++++------ docs/components/description-card.md | 2 +- docs/components/global-header-layout.md | 132 ++++++++ docs/components/layout.md | 8 +- docs/components/sheet.md | 12 +- docs/components/sidebar-group.md | 12 +- docs/components/sidebar-item.md | 23 +- docs/components/sidebar-layout.md | 391 +++++++----------------- docs/components/spinner.md | 75 +++++ docs/components/table.md | 2 +- docs/components/timeline.md | 4 +- docs/components/tooltip.md | 2 +- docs/concepts/styling-theming.md | 17 +- docs/quickstart.md | 12 +- 23 files changed, 508 insertions(+), 495 deletions(-) create mode 100644 docs/components/global-header-layout.md create mode 100644 docs/components/spinner.md diff --git a/CLAUDE.md b/CLAUDE.md index a7ffbb8b6..8a52c89bf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,6 +18,18 @@ Tailor Platform AppShell - A React-based framework for building ERP applications **📖 For component and API documentation, see:** [`docs/`](./docs/) +### Documentation Index + +- `docs/components/sidebar-layout.md` — default layout slots, `body`, `topBar`, and sidebar composition +- `docs/components/global-header-layout.md` — opinionated app-wide header layout built on `SidebarLayout` +- `docs/components/default-sidebar.md` — built-in sidebar modes, search entry, and icon-rail behavior +- `docs/components/command-palette.md` — built-in navigation palette and search-source integration +- `docs/components/ai-chat.md` — assistant UI component surface +- `docs/components/spinner.md` — reusable loading indicator +- `docs/api/use-page-meta.md` — route metadata lookup +- `docs/concepts/sidebar-navigation.md` — sidebar composition guidance +- `docs/concepts/styling-theming.md` — styling rules and theme setup + ## Key Architecture Points (LLM Orientation) ### Monorepo Structure diff --git a/docs/api/use-page-meta.md b/docs/api/use-page-meta.md index 3fa84e1c2..76d4195d6 100644 --- a/docs/api/use-page-meta.md +++ b/docs/api/use-page-meta.md @@ -26,7 +26,7 @@ const usePageMeta: (path: string) => { ## Return Value -Returns an object with `title` and optional `icon`, or `null` if path not found. +Returns an object with `title` and optional `icon`, or `null` when the path is external or does not match any AppShell route. ## Usage @@ -84,8 +84,9 @@ function Breadcrumb({ paths }: { paths: string[] }) { ## Notes - Used internally by `SidebarItem` to auto-resolve titles and icons -- Returns `null` if the path doesn't match any defined route +- Returns `null` for external URLs (`http://...` / `https://...`) and for paths that do not match any defined route - Resolves from module and resource `meta` properties +- Prefers literal routes before dynamic siblings, so `/users/new` resolves that route's metadata instead of `/users/:id` ## Related diff --git a/docs/components/action-panel.md b/docs/components/action-panel.md index d8feb15e0..b4cf370d8 100644 --- a/docs/components/action-panel.md +++ b/docs/components/action-panel.md @@ -135,14 +135,7 @@ This keeps `ActionPanel` simple and reusable across entity types. ## Styling -The component uses Tailwind classes with the `astw:` prefix: - -- Full width card (`astw:w-full`) -- Compact spacing for dense action lists -- Fixed icon slot for stable alignment -- Inline spinner for loading state - -Use `className` on `ActionPanel` to add or override card-level styles. +Use `className` on `ActionPanel` to adjust the outer card. The row layout, fixed icon slot, and inline loading spinner are part of the built-in treatment. ## Examples in This Repo diff --git a/docs/components/ai-chat.md b/docs/components/ai-chat.md index ffe7f00c4..5c1dee909 100644 --- a/docs/components/ai-chat.md +++ b/docs/components/ai-chat.md @@ -26,7 +26,7 @@ function Assistant() { return ( // `overflow-hidden` keeps the header rule inside the card's rounded corners. - + @@ -122,7 +122,7 @@ Three attached parts wrap an AppShell component. They expose only the props the `variant` stays open on `ChainOfThoughtSearchResult` because conveying status is what a `Badge` is for. `AIChat.Action`'s `label` becomes both the accessible name and the tooltip text. -One thing to know about `className` on these: it is merged with `cn()`, which is not configured for the `astw:` prefix, so it cannot reliably override a base utility from the wrapped component. If an override appears to do nothing, that is why. +One thing to know about `className` on these: it is merged onto a wrapped AppShell primitive whose core treatment is fixed. Additive styling works well; conflicting utilities may not fully replace the built-in treatment. If you need a materially different control, compose your own button or badge instead. ## Filling the page @@ -134,7 +134,7 @@ Inside a `` column (the column is `flex flex-col`, so the card take - + {/* … */} @@ -145,7 +145,7 @@ Inside a `` column (the column is `flex flex-col`, so the card take ``` -`min-h-0` is what lets the card shrink below its content's natural height so the transcript scrolls instead of pushing the composer off-screen. In a docked panel or `Sheet` whose height is already fixed, `astw:h-full` on the card is enough. +`min-h-0` is what lets the card shrink below its content's natural height so the transcript scrolls instead of pushing the composer off-screen. In a docked panel or `Sheet` whose height is already fixed, `h-full` on the card is enough. ## Header @@ -157,10 +157,10 @@ A 48px strip above the transcript: leading graphic, title, and an open action sl actions={ <> - + - + } @@ -174,7 +174,7 @@ For a docked right panel, put the collapse control in `icon` so it takes the lea title="Assistant" icon={ - + } /> @@ -206,7 +206,7 @@ Renders the markdown subset a streamed LLM response actually emits — bold, inl ```tsx } + icon={} title="Ask the assistant" description="Grounded in your help articles." > @@ -225,7 +225,7 @@ Icon-button row under a finished assistant turn — copy, retry, feedback. Rende ```tsx navigator.clipboard.writeText(message.content)}> - + ``` diff --git a/docs/components/alert.md b/docs/components/alert.md index e2bf74d70..ff1be5984 100644 --- a/docs/components/alert.md +++ b/docs/components/alert.md @@ -172,16 +172,15 @@ function OrderForm() { ## Styling -Alerts use Tailwind CSS classes prefixed with `astw:`. Customize appearance using: +Customize alerts using: 1. **Using `className`** on `Alert.Root` for additional styles 2. **Using `alertVariants`** utility to apply variant classes to custom elements ```tsx import { alertVariants } from "@tailor-platform/app-shell"; -import { cn } from "@/lib/utils"; -
Custom alert container
; +
Custom alert container
; ``` ## Related Components diff --git a/docs/components/app-shell.md b/docs/components/app-shell.md index bfae91bf3..aa7e17de6 100644 --- a/docs/components/app-shell.md +++ b/docs/components/app-shell.md @@ -237,9 +237,9 @@ import { useRouteError } from "@tailor-platform/app-shell"; const GlobalErrorBoundary = () => { const error = useRouteError() as Error; return ( -
-

Something went wrong

-

{error.message}

+
+

Something went wrong

+

{error.message}

); }; @@ -449,8 +449,8 @@ declare module "@tailor-platform/app-shell" { const ErrorBoundary = () => { const error = useRouteError() as Error; return ( -
-

Error

+
+

Error

{error.message}

); diff --git a/docs/components/badge.md b/docs/components/badge.md index 98503fbbb..b5d791403 100644 --- a/docs/components/badge.md +++ b/docs/components/badge.md @@ -134,7 +134,7 @@ const priorities = [ { label: "Critical", variant = "error" }, ]; -
+
{priorities.map((p) => ( {p.label} @@ -148,17 +148,17 @@ const priorities = [ ```tsx import { CheckCircle, AlertCircle, XCircle } from "lucide-react"; -
+
- + Verified - + Review - + Blocked
; @@ -188,7 +188,7 @@ function ProductBadge({ product }: { product: Product }) { ### Custom Styling ```tsx - + Premium ``` @@ -279,7 +279,7 @@ import { DescriptionCard } from "@tailor-platform/app-shell"; ## Styling -Badges use Tailwind CSS classes prefixed with `astw:`. Customize appearance by: +Customize badges by: 1. **Using className prop** for additional styles 2. **Overriding CSS variables** in your theme @@ -287,7 +287,6 @@ Badges use Tailwind CSS classes prefixed with `astw:`. Customize appearance by: ```tsx import { badgeVariants } from "@tailor-platform/app-shell"; -import { cn } from "@/lib/utils"; -
Custom Badge
; +
Custom Badge
; ``` diff --git a/docs/components/button.md b/docs/components/button.md index 37970d368..a68898649 100644 --- a/docs/components/button.md +++ b/docs/components/button.md @@ -76,7 +76,7 @@ This is the Base UI render pattern — the button's class names and event handle ### Form Actions ```tsx -
+
diff --git a/docs/components/command-palette.md b/docs/components/command-palette.md index f57b74e51..be7f2e831 100644 --- a/docs/components/command-palette.md +++ b/docs/components/command-palette.md @@ -30,6 +30,7 @@ The CommandPalette will automatically: - ✅ Index all navigatable routes from your modules - ✅ Provide fuzzy search functionality - ✅ Show breadcrumb paths for context +- ✅ Resolve dynamic params on the current route branch into real paths - ✅ Display module icons - ✅ Respect access control (hidden routes won't appear) @@ -77,6 +78,10 @@ Each result shows its full navigation path for context: 📋 Orders > History > 2024 ``` +When the current URL already fixes a dynamic segment, the palette keeps routes on that branch navigable and displays those resolved values in the path. It only substitutes params on the current route branch, not into sibling routes. + +For example, on `/users/42` the palette can include `/users/42` and `/users/42/profile`, and does **not** incorrectly rewrite sibling routes like `/users/new`. + ### Access Control Integration The CommandPalette automatically respects your route guards: @@ -231,46 +236,15 @@ User types: ORD: alice → search(" alice", { signal }) — leading space preserved ``` -## Customization - -### Custom Palette (Advanced) - -If you need to customize the CommandPalette behavior, you can use the `useCommandPalette` hook: - -```tsx -import { useCommandPalette, navItemsToRoutes } from "@tailor-platform/app-shell"; - -function CustomPalette() { - const navItems = useNavItems(); // Get navigation items - const routes = navItemsToRoutes(navItems); - - const { open, handleOpenChange, search, setSearch, filteredRoutes, handleSelect, handleKeyDown } = - useCommandPalette({ routes }); +## Extending the built-in palette - // Custom UI implementation - return ( - - setSearch(e.target.value)} - onKeyDown={handleKeyDown} - placeholder="Search pages..." - /> - {filteredRoutes.map((route) => ( - - ))} - - ); -} -``` +The supported extension points are: -### Disabling CommandPalette +- `searchSources` on `AppShell` for async, prefix-activated search modes +- [`useRegisterCommandPaletteActions`](../api/use-register-command-palette-actions.md) for page-level actions +- [`ActionPanel`](./action-panel.md), whose enabled rows are registered automatically -Currently, the CommandPalette is always enabled with `SidebarLayout`. To disable it, you would need to create a custom layout without the CommandPalette component. +`CommandPalette` itself is built into `AppShell`; there is no separate public hook for rebuilding the palette UI from scratch. ## Examples diff --git a/docs/components/default-sidebar.md b/docs/components/default-sidebar.md index cf980bcda..83dcab887 100644 --- a/docs/components/default-sidebar.md +++ b/docs/components/default-sidebar.md @@ -1,138 +1,141 @@ --- title: DefaultSidebar -description: Pre-built sidebar component that renders navigation from AppShell modules +description: Built-in sidebar component for auto-generated or composed navigation, with optional search suppression and icon-rail mode --- # DefaultSidebar -The default sidebar component that automatically renders navigation items from the AppShell context. This is a convenience component that provides a ready-to-use sidebar without manual configuration. +`DefaultSidebar` is the built-in sidebar used by [`SidebarLayout`](./sidebar-layout.md). It has two modes: -> Also available as **`SidebarLayout.DefaultSidebar`**. The namespaced form is preferred for discoverability (it pairs with [`SidebarLayout.DefaultHeader`](./default-header.md)); the top-level `DefaultSidebar` export is kept for backwards compatibility. Both refer to the same component. +- **Auto-generation mode** — omit `children` and it renders navigation from your AppShell modules and resources. +- **Composition mode** — pass `children` and build the menu yourself with `SidebarItem`, `SidebarGroup`, `SidebarSeparator`, and the low-level `SidebarMenuItem` / `SidebarMenuButton` primitives. -## Props +> Also available as **`SidebarLayout.DefaultSidebar`**. The namespaced form is preferred for discoverability; the top-level `DefaultSidebar` export is kept for backwards compatibility. -| Prop | Type | Required | Description | -| -------- | ----------------- | -------- | --------------------- | -| `header` | `React.ReactNode` | No | Custom header content | -| `footer` | `React.ReactNode` | No | Custom footer content | +## Props -## Usage +| Prop | Type | Default | Description | +| ------------ | ----------------- | -------- | -------------------------------------------------------------------------------------------------------- | +| `header` | `React.ReactNode` | built-in | Replaces the sidebar header content. | +| `footer` | `React.ReactNode` | - | Footer content rendered after the navigation area. | +| `children` | `React.ReactNode` | - | When provided, enables composition mode and disables auto-generation. | +| `hideHeader` | `boolean` | `false` | Hides the sidebar's own header row. Useful when a full-width top bar already owns the app title/toggle. | +| `hideSearch` | `boolean` | `false` | Hides the built-in **Search** entry that opens the Command Palette. | +| `iconRail` | `boolean` | `false` | Collapses to a persistent icon rail instead of sliding fully off-canvas. | -### Basic Usage +## Basic usage -```typescript -import { DefaultSidebar } from "@tailor-platform/app-shell"; +```tsx +import { SidebarLayout } from "@tailor-platform/app-shell"; function Layout() { - return ; + return ; } ``` -### With Custom Header +## Composition mode -```typescript -import { DefaultSidebar } from "@tailor-platform/app-shell"; +```tsx +import { + SidebarGroup, + SidebarItem, + SidebarLayout, + SidebarSeparator, +} from "@tailor-platform/app-shell"; -function CompanyLogo() { +function Layout() { return ( -
- Company -
+ + + + + + + + ); } - -function Layout() { - return } />; -} ``` -### With Custom Footer +## Search entry -```typescript -function UserProfile() { - const { context } = useAppShell(); +The built-in **Search** entry opens AppShell's [`CommandPalette`](./command-palette.md). It is rendered in both auto-generated and composed navigation. - return ( -
-

{context.currentUser?.name}

- -
- ); -} +Hide it only when search is already exposed elsewhere, such as a dedicated top-bar control: -function Layout() { - return } />; -} +```tsx + ``` -### With Both Header and Footer +## Icon-rail behavior -```typescript -function Layout() { - return ( - } - footer={} - /> - ); -} -``` - -## Features +With `iconRail`, collapsing the sidebar keeps icons visible at every width: -- **Auto-Navigation**: Automatically renders all modules and resources from AppShell, including the root module (path `""` or `"/"`) when defined. When a `rootComponent` is passed to `AppShell` without an explicit root module, a synthetic root entry is injected with a localized `"Home"` title. -- **Search Entry**: Always renders a **Search** button at the top of the navigation menu that opens the CommandPalette (equivalent to `Cmd+K` / `Ctrl+K`). This is available regardless of whether `searchSources` are configured on `AppShell`. -- **Active State**: Highlights the current route -- **Collapsible Groups**: Supports nested resource groups -- **Responsive**: Adapts to mobile and desktop layouts -- **Customizable**: Add header/footer content as needed +- on desktop, the sidebar collapses to a narrow persistent rail +- on mobile, the rail stays visible and its toggle opens the full sidebar as a slide-in drawer +- grouped items reveal child pages from the collapsed icon via a flyout popover +- items without children show their label in a tooltip -## Using in SidebarLayout +[`GlobalHeaderLayout`](./global-header-layout.md) bakes this mode in through `GlobalHeaderLayout.DefaultSidebar`. -The `DefaultSidebar` is commonly used with `SidebarLayout`: +## Working with a global top bar -```typescript -import { SidebarLayout, DefaultSidebar } from "@tailor-platform/app-shell"; +When the app title and primary toggle live in a full-width top bar, hide the sidebar's own header: +```tsx } - footer={} - /> - } -> - - + topBar={} + sidebar={} +/> ``` -## Custom Navigation - -If you need more control over navigation rendering, build a custom sidebar using: +## Custom footer -- [SidebarItem](./sidebar-item.md) - Individual navigation links -- [SidebarGroup](./sidebar-group.md) - Collapsible navigation groups -- [WithGuard](./with-guard.md) - Conditional rendering based on access control - -```typescript -import { SidebarItem, SidebarGroup } from "@tailor-platform/app-shell"; - -function CustomSidebar() { +```tsx +function UserProfile() { return ( -
- - - - - +
+

Ada Lovelace

+
); } + +} />; +``` + +## Low-level custom items + +When `SidebarItem` is too opinionated, compose a custom row with the exported primitives: + +```tsx +import { + SidebarLayout, + SidebarMenuButton, + SidebarMenuItem, +} from "@tailor-platform/app-shell"; +import { BellIcon } from "lucide-react"; + + + + } + onClick={() => openNotifications()} + tooltip="Notifications" + > + + Notifications + + +; ``` ## Related -- [SidebarLayout](./sidebar-layout.md) - Layout wrapper for sidebar + content -- [SidebarItem](./sidebar-item.md) - Individual navigation links -- [SidebarGroup](./sidebar-group.md) - Collapsible navigation groups -- [Sidebar Navigation](../concepts/sidebar-navigation.md) - Complete navigation guide +- [SidebarLayout](./sidebar-layout.md) - layout wrapper for sidebar + content +- [GlobalHeaderLayout](./global-header-layout.md) - opinionated app-wide header mode +- [SidebarItem](./sidebar-item.md) - individual navigation links +- [SidebarGroup](./sidebar-group.md) - collapsible navigation groups +- [Sidebar Navigation](../concepts/sidebar-navigation.md) - navigation concepts and composition patterns diff --git a/docs/components/description-card.md b/docs/components/description-card.md index 0b20bbe10..19d1f69f0 100644 --- a/docs/components/description-card.md +++ b/docs/components/description-card.md @@ -477,7 +477,7 @@ The component uses container queries for responsive layouts: ```tsx // Custom styling diff --git a/docs/components/global-header-layout.md b/docs/components/global-header-layout.md new file mode 100644 index 000000000..29894555d --- /dev/null +++ b/docs/components/global-header-layout.md @@ -0,0 +1,132 @@ +--- +title: GlobalHeaderLayout +description: Opinionated layout with an app-wide header above the whole shell and a sidebar that collapses to a persistent icon rail +--- + +# GlobalHeaderLayout + +`GlobalHeaderLayout` is AppShell's opinionated "global top bar" layout. It is a thin wrapper over [`SidebarLayout`](./sidebar-layout.md) that wires together: + +- an app-wide header above both the sidebar and the content area +- a default sidebar with its own header removed +- icon-rail collapse behavior +- the stock content container and outlet + +Use it when you want the whole app to share one top bar. Reach for `SidebarLayout` directly when you need the more flexible primitive. + +## Import + +```tsx +import { GlobalHeaderLayout } from "@tailor-platform/app-shell"; +``` + +## Basic usage + +```tsx +import { + AppShell, + AppearanceSwitcher, + GlobalHeaderLayout, + SidebarGroup, + SidebarItem, +} from "@tailor-platform/app-shell"; +import { LayersIcon } from "lucide-react"; + +function App() { + return ( + + ]} + /> + } + sidebar={ + + + }> + + + + + } + /> + + ); +} +``` + +## Props + +| Prop | Type | Default | Description | +| ------------- | ---------------------------------------------------------------- | ---------------------------------------- | ---------------------------------------------------------------- | +| `header` | `React.ReactNode` | `` | Replaces the full-width app header. | +| `sidebar` | `React.ReactNode` | `` | Replaces the primary sidebar. | +| `defaultOpen` | `boolean` | `true` | Whether the sidebar starts expanded on desktop. | +| `collapsible` | `boolean` | `true` | Whether the sidebar can be collapsed. | +| `children` | `(props: { Outlet: () => React.ReactNode }) => React.ReactNode` | current route outlet | Custom content renderer for the stock content container. | +| `body` | `React.ReactNode` | - | Escape hatch that replaces the entire region beside the sidebar. | + +`children` and `body` are mutually exclusive. Use `body` when you need to add your own side columns around the main content. + +## Built-in parts + +`GlobalHeaderLayout` exposes the same composition pieces as `SidebarLayout`, plus the opinionated header and sidebar defaults: + +| Export | Description | +| ----------------------------------- | ------------------------------------------------------------------------------------- | +| `GlobalHeaderLayout.DefaultHeader` | The app-wide header (`GlobalHeader`) with app title + breadcrumb on the left and `actions` on the right. | +| `GlobalHeaderLayout.DefaultSidebar` | `DefaultSidebar` with `hideHeader` and `iconRail` forced on. | +| `GlobalHeaderLayout.ContentContainer` | The stock main content column. | +| `GlobalHeaderLayout.Outlet` | The current route outlet. | +| `GlobalHeaderLayout.Trigger` | The built-in sidebar toggle button. | +| `GlobalHeaderLayout.Breadcrumb` | The route-driven breadcrumb component (`DynamicBreadcrumb`). | + +## `GlobalHeader` + +The layout's default header is also exported as a top-level `GlobalHeader` component. It renders: + +- the AppShell `title` and optional `icon` +- the route-driven breadcrumb +- a right-aligned `actions` cluster + +`actions` behaves the same way as on `SidebarLayout.DefaultHeader`: omitting it renders the built-in [`AppearanceSwitcher`](./appearance-switcher.md), and passing it replaces the entire right-hand cluster. + +```tsx +import { AppearanceSwitcher, GlobalHeader } from "@tailor-platform/app-shell"; + +]} />; +``` + +## Owning the body region + +Use `body` when you want custom columns beside the content while still keeping the global header and icon-rail sidebar. + +```tsx + + + + + + + } +/> +``` + +## When to use which layout + +| Goal | Use | +| ---- | --- | +| Built-in sidebar + content header | [`SidebarLayout`](./sidebar-layout.md) | +| Full-width app header above the whole shell | `GlobalHeaderLayout` | +| Completely custom page structure with AppShell routing | `SidebarLayout body={...}` or `GlobalHeaderLayout body={...}` | + +## Related + +- [SidebarLayout](./sidebar-layout.md) - lower-level primitive that GlobalHeaderLayout wraps +- [DefaultSidebar](./default-sidebar.md) - underlying sidebar component used by `GlobalHeaderLayout.DefaultSidebar` +- [DefaultHeader](./default-header.md) - content-column header for `SidebarLayout` diff --git a/docs/components/layout.md b/docs/components/layout.md index 75f623a90..9f670a25e 100644 --- a/docs/components/layout.md +++ b/docs/components/layout.md @@ -263,7 +263,7 @@ Use `fill` for pages whose main content manages its own scrolling (typically a ` Use `className` to control the space between columns: ```tsx - + {/* ... */} {/* ... */} @@ -484,17 +484,17 @@ The Layout component validates its children: Add custom classes to the layout container: ```tsx -{/* ... */} +{/* ... */} ``` Add custom classes to individual columns: ```tsx - + {/* Main content with background */} - {/* Sidebar with extra spacing */} + {/* Sidebar with extra spacing */} ``` diff --git a/docs/components/sheet.md b/docs/components/sheet.md index 1a2ba1701..f0e4c4552 100644 --- a/docs/components/sheet.md +++ b/docs/components/sheet.md @@ -168,7 +168,7 @@ Place action buttons (e.g., save, edit) to the right of the title using the `act Save}> Edit Customer -
+
@@ -187,7 +187,7 @@ function FilterPanel() { Filter Orders Narrow down orders by criteria. -
+
@@ -210,14 +210,14 @@ function FilterPanel() { Actions -
- - -
diff --git a/docs/components/sidebar-group.md b/docs/components/sidebar-group.md index 5bdc8ff6a..9c6633553 100644 --- a/docs/components/sidebar-group.md +++ b/docs/components/sidebar-group.md @@ -325,22 +325,14 @@ When a child item is active, the group: ### Custom Icon Styling ```tsx -}> +}> {/* ... */} ``` ### Group Title Styling -The title styling is controlled by AppShell's theme. You can customize via CSS: - -```css -/* Custom group header styling */ -.astw:sidebar-group-title { - font-weight: 600; - font-size: 0.875rem; -} -``` +The built-in title styling is part of the component's theme. If you need a materially different title treatment, compose your own grouping UI instead of targeting AppShell's internal selectors. ## Accessibility diff --git a/docs/components/sidebar-item.md b/docs/components/sidebar-item.md index f9926b93c..b7280a8af 100644 --- a/docs/components/sidebar-item.md +++ b/docs/components/sidebar-item.md @@ -27,6 +27,8 @@ import { DefaultSidebar, SidebarItem } from "@tailor-platform/app-shell"; When you only provide `to`, the title and icon are automatically resolved from the matching resource's `meta` property. +Literal routes win over dynamic siblings during auto-resolution. For example, if your app has both `/users/new` and `/users/:id`, then `` resolves the literal route's title and icon even if the dynamic route was registered first. + ## Props | Prop | Type | Default | Description | @@ -178,7 +180,7 @@ For full control, use the `render` prop: ( -
+
{icon} {title} {isActive && New} @@ -264,8 +266,8 @@ import { HelpCircle, FileText, ExternalLink } from "lucide-react"; render={({ title, icon, isActive }) => ( <> {icon} - {title} - {isActive && } + {title} + {isActive && } )} /> @@ -317,15 +319,7 @@ import { Package } from "lucide-react"; ### Active State -Active items automatically receive the `astw:bg-sidebar-accent` background class: - -```css -/* Active item styling (automatic) */ -.astw:bg-sidebar-accent { - background: var(--sidebar-accent); - font-weight: 500; -} -``` +Active items automatically receive the sidebar accent treatment and medium font weight. ### Custom Styling @@ -336,10 +330,7 @@ Add custom classes via the `render` prop: to="/special" render={({ title, icon, isActive }) => (
{icon} {title} diff --git a/docs/components/sidebar-layout.md b/docs/components/sidebar-layout.md index ff48941e0..ec42762ed 100644 --- a/docs/components/sidebar-layout.md +++ b/docs/components/sidebar-layout.md @@ -1,11 +1,19 @@ --- title: SidebarLayout -description: The default layout component with sidebar navigation, breadcrumbs, and theme toggle +description: Default AppShell layout with a built-in sidebar, content header, optional app-wide top bar, and an escape-hatch body slot --- # SidebarLayout -`SidebarLayout` is the default layout component that provides a responsive sidebar navigation, breadcrumb trail, and theme toggle. It's designed to work seamlessly with AppShell's module system. +`SidebarLayout` is the default AppShell layout. By default it renders: + +- `SidebarLayout.DefaultSidebar` +- `SidebarLayout.DefaultHeader` +- the current route outlet + +You can replace the sidebar, replace the content header, add a full-width `topBar` above the whole shell, or take over the entire region beside the sidebar with the `body` slot. + +If you want the opinionated "global app header + icon rail" mode, use [`GlobalHeaderLayout`](./global-header-layout.md), which is built on top of `SidebarLayout`. ## Import @@ -13,7 +21,7 @@ description: The default layout component with sidebar navigation, breadcrumbs, import { SidebarLayout } from "@tailor-platform/app-shell"; ``` -## Basic Usage +## Basic usage ```tsx import { AppShell, SidebarLayout } from "@tailor-platform/app-shell"; @@ -31,106 +39,54 @@ This gives you: - ✅ Responsive sidebar with auto-generated navigation from modules - ✅ Breadcrumb navigation -- ✅ Theme toggle (light/dark mode) -- ✅ Mobile-friendly collapsible sidebar +- ✅ Theme toggle via the built-in header +- ✅ Mobile-friendly collapse behavior ## Props -### children - -- **Type:** `(props: { Outlet: () => React.ReactNode }) => React.ReactNode` (optional) -- **Description:** Custom content renderer for adding headers, footers, or wrapping the outlet - -```tsx - - {({ Outlet }) => ( - <> - - - - - )} - -``` - -The `Outlet` component renders your current route's component. +### Common props -### sidebar +| Prop | Type | Default | Description | +| ------------- | ----------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------- | +| `sidebar` | `React.ReactNode` | `` | Replaces the entire sidebar region. | +| `defaultOpen` | `boolean` | `true` | Whether the sidebar starts expanded on desktop. | +| `collapsible` | `boolean` | `true` | Whether the sidebar can be collapsed. When `false`, toggle controls are hidden and `defaultOpen` is ignored. | +| `topBar` | `React.ReactNode` | - | Full-width bar above both the sidebar and the content region. | -- **Type:** `React.ReactNode` (optional) -- **Default:** `` -- **Description:** Replaces the whole sidebar region. Omit it for the built-in sidebar. +### Default content mode -```tsx -import { SidebarLayout, SidebarItem } from "@tailor-platform/app-shell"; +Use this when AppShell should keep owning the content column. - - - - } -/>; -``` +| Prop | Type | Default | Description | +| ---------- | ---------------------------------------------------------------- | --------------------------------- | --------------------------------------------------------------- | +| `header` | `React.ReactNode` | `` | Replaces the content header above the main column. | +| `children` | `(props: { Outlet: () => React.ReactNode }) => React.ReactNode` | current route outlet | Custom content renderer that wraps or surrounds the outlet. | -> `SidebarLayout.DefaultSidebar` is the same component as the top-level -> `DefaultSidebar` export (kept for backwards compatibility). The namespaced form -> is preferred for discoverability — it pairs with -> [`SidebarLayout.DefaultHeader`](#header). +### Body-slot mode -### defaultOpen +Use this when you need to compose your own columns beside the sidebar. -- **Type:** `boolean` (optional) -- **Default:** `true` -- **Description:** Whether the sidebar is open by default on desktop. Has no effect when `collapsible` is `false`. +| Prop | Type | Default | Description | +| ------ | ----------------- | -------- | ------------------------------------------------------------------------------------------- | +| `body` | `React.ReactNode` | required | Replaces everything to the right of the sidebar. Compose it with `ContentContainer`, `Outlet`, and `Trigger`. | -```tsx -// Sidebar closed by default on desktop - -``` +`body` is mutually exclusive with `header` and `children`, because it replaces the region those props normally describe. -### collapsible +## Extending the built-in header -- **Type:** `boolean` (optional) -- **Default:** `true` -- **Description:** Whether the sidebar can be collapsed. When set to `false`, the sidebar is always visible and toggle buttons are hidden. `defaultOpen` is ignored when this is `false`. +The supported "small customization" path is to pass `SidebarLayout.DefaultHeader` into the `header` slot and fill its `actions` cluster. ```tsx -// Non-collapsible sidebar (always visible, toggle buttons hidden) - -``` - -### header - -- **Type:** `React.ReactNode` (optional) -- **Default:** `` -- **Description:** Replaces the whole top-bar region. Omit it for the built-in header. - -Like `sidebar`, `header` is a full-region slot. There are three levels of customization: - -**1. Default** — omit `header` entirely: - -```tsx - -``` - -**2. Extend the built-in header** — pass `SidebarLayout.DefaultHeader` and use its -`actions` slot. This is the common case (e.g. adding a notification bell) and -keeps the trigger + breadcrumb without reconstructing them: - -```tsx -import { SidebarLayout, AppearanceSwitcher, Button } from "@tailor-platform/app-shell"; +import { AppearanceSwitcher, Button, SidebarLayout } from "@tailor-platform/app-shell"; import { BellIcon } from "lucide-react"; - + , - // `actions` REPLACES the default right-hand cluster, so include the - // appearance switcher explicitly to keep it. , ]} /> @@ -138,194 +94,99 @@ import { BellIcon } from "lucide-react"; />; ``` -**3. Replace it entirely** — supply your own node: - -```tsx -} /> -``` - -#### `SidebarLayout.DefaultHeader` - -The built-in header: sidebar trigger + breadcrumb on the left, and the `actions` -cluster on the right. +`actions` replaces the entire right-hand cluster. If you still want the built-in appearance switcher, include `` yourself. -- **`actions`** — `React.ReactNode | React.ReactNode[]` (optional). The entire - right-hand cluster, laid out in a horizontal, vertically-centered row with - consistent spacing. - - **Default:** `[]` — so out-of-the-box behavior is - unchanged. - - ⚠️ **`actions` replaces the whole right-hand cluster, including the - appearance switcher.** If you pass your own actions and still want the - switcher, include `` in the array (it is a public - export). `actions={[]}` renders an empty right side. +## Replacing the sidebar -This is the supported extension point for the top bar — it replaces fragile -workarounds that queried the header DOM and injected a React portal. +Pass `sidebar` to replace the whole left-side region. The built-in component is [`SidebarLayout.DefaultSidebar`](./default-sidebar.md), also exported as `DefaultSidebar`. -## Features - -### Responsive Sidebar - -The sidebar automatically adapts to different screen sizes: - -- **Desktop** (≥ 768px): Full sidebar visible by default -- **Tablet/Mobile** (< 768px): Collapsible sidebar with hamburger menu - -Users can toggle the sidebar using: +```tsx +import { SidebarGroup, SidebarItem, SidebarLayout, SidebarSeparator } from "@tailor-platform/app-shell"; -- Hamburger menu button (top left) -- Keyboard shortcut: `Cmd + B` / `Ctrl + B` + + + + + + + + + } +/>; +``` -### Breadcrumb Navigation +## Adding an app-wide top bar -Breadcrumbs are automatically generated based on the current route: +`topBar` renders above the whole shell, spanning both the sidebar and the content region. -``` -Dashboard > Products > Product Details +```tsx +} sidebar={} /> ``` -Breadcrumbs update automatically as users navigate through your application. +When `topBar` is present, the fixed sidebar is offset below it. The bar should be `3.5rem` tall (`h-14`) to match the built-in layout. -### Theme Toggle +If you specifically want AppShell's opinionated global-header mode, use [`GlobalHeaderLayout`](./global-header-layout.md), which wires the top bar, icon rail, and matching default sidebar for you. -The built-in header renders an [`AppearanceSwitcher`](./appearance-switcher.md) — a palette-icon button whose dropdown switches the color theme (persisted to localStorage). To add your own controls (notifications, user menu, etc.) alongside it, pass [`SidebarLayout.DefaultHeader`](#header) with an `actions` array that includes ``. +## Owning the region beside the sidebar -## Customization Examples - -### Custom Header and Footer +Use `body` when the stock content column is too restrictive and you need your own side columns. ```tsx -import { SidebarLayout } from "@tailor-platform/app-shell"; - -const CustomHeader = () => ( -
-

Welcome to My App

-
-); + + + }> + + + + + } +/> +``` -const CustomFooter = () => ( -
© 2026 My Company
-); +`SidebarLayout.ContentContainer` is the stock main column: inset padding, pinned header slot, scroll region, and `useAppShellScrollContainer()` support. `SidebarLayout.Trigger` exposes the built-in sidebar toggle so custom headers do not need DOM workarounds. -function App() { - return ( - - - {({ Outlet }) => ( -
- -
- -
- -
- )} -
-
- ); -} -``` +## Reading and controlling the sidebar -### Custom Sidebar +Use `useAppShellSidebar()` to read and control the sidebar state instead of observing internal DOM attributes or clicking the trigger through the DOM. ```tsx -import { - SidebarLayout, - DefaultSidebar, - SidebarItem, - SidebarGroup, - SidebarSeparator, -} from "@tailor-platform/app-shell"; -import { HelpCircle, ExternalLink } from "lucide-react"; +import { useAppShellSidebar } from "@tailor-platform/app-shell"; + +function CustomHeader() { + const { open, isMobile, toggle } = useAppShellSidebar(); -function App() { return ( - - - {/* Auto-generated navigation from modules */} - - {/* Add custom items */} - - - } - href="https://docs.example.com" - external - /> - } - href="https://support.example.com" - external - /> - - - } - /> - +
+ + {isMobile ? "Mobile" : "Desktop"} +
); } ``` -### Wrapping Content in a Container +Outside `SidebarLayout`, the hook returns a safe fallback (`open: true`, `isMobile: false`, no-op setters) rather than throwing. -```tsx - - {({ Outlet }) => ( -
- -
- )} -
-``` +## Reusing the built-in breadcrumb -### Adding a Global Banner +`DynamicBreadcrumb` is exported when you want the same route-driven breadcrumb in a custom header or top bar: ```tsx - - {({ Outlet }) => ( - <> -
-

🎉 New features available! Check out our latest updates.

-
- - - )} -
-``` - -## Layout Structure +import { DynamicBreadcrumb, SidebarLayout } from "@tailor-platform/app-shell"; -The SidebarLayout component creates the following structure: - -``` -┌─────────────────────────────────────────────┐ -│ Sidebar │ Header (Breadcrumbs + │ -│ │ Theme Toggle) │ -│ - Dashboard ├─────────────────────────┤ -│ - Products │ │ -│ - Orders │ │ -│ │ Page Content │ -│ [Settings ▼] │ (Outlet) │ -│ │ │ -│ │ │ -└─────────────────────────────────────────────┘ -``` - -Mobile view (sidebar collapsed): - -``` -┌─────────────────────────────────┐ -│ [☰] Breadcrumbs [Theme] │ -├─────────────────────────────────┤ -│ │ -│ Page Content │ -│ (Outlet) │ -│ │ -└─────────────────────────────────┘ +
+ + +
; ``` ## Accessing the content scroll container @@ -363,52 +224,24 @@ The same element handles imperative scrolling (`scrollRef.current?.scrollTo({ to Notes: - The element mounts with the layout, above your page, so read `ref.current` inside an effect — it is populated by the time effects run, not during render. -- On a [``](./layout.md#fill-mode) page this element does **not** scroll; its children (e.g. a `DataTable`) manage their own scrolling. +- On a [``](./layout.md#fill-mode) page this element does **not** scroll; its children (for example a `DataTable`) manage their own scrolling. - Outside a `SidebarLayout` (a fully custom layout) the returned ref's `current` is always `null` — such layouts own their own scroll region. - For non-React access (CSS, tests, plain DOM) the container also carries a `data-appshell-scroll-container` attribute: `document.querySelector("[data-appshell-scroll-container]")`. -## Styling - -The sidebar and layout use Tailwind CSS classes prefixed with `astw:` to avoid conflicts with your application styles. - -To customize the appearance, you can: - -1. **Override CSS variables** in your theme.css: - - ```css - :root { - --sidebar-width: 280px; /* Default: 256px */ - } - ``` - -2. **Use custom sidebar component** with your own styling - -3. **Wrap Outlet** with container classes as shown in examples above - ## Accessibility SidebarLayout includes built-in accessibility features: -- **Keyboard navigation**: Navigate sidebar items with arrow keys -- **ARIA labels**: Proper labels for screen readers -- **Focus management**: Focus trap when sidebar is open on mobile -- **Responsive**: Works with keyboard and touch inputs - -## Related Components - -- [AppShell](./app-shell.md) - Root component -- [DefaultHeader](./default-header.md) - Built-in header (`SidebarLayout.DefaultHeader`) -- [DefaultSidebar](./default-sidebar.md) - Built-in sidebar (`SidebarLayout.DefaultSidebar`) -- [AppearanceSwitcher](./appearance-switcher.md) - Color-theme dropdown, composable into header `actions` -- [SidebarItem](./sidebar-item.md) - Individual sidebar navigation items -- [SidebarGroup](./sidebar-group.md) - Group sidebar items - -## Related Concepts - -- [Modules and Resources](../concepts/modules-and-resources.md) - How navigation is generated -- [Routing and Navigation](../concepts/routing-navigation.md) - Navigation between pages -- [Styling and Theming](../concepts/styling-theming.md) - Customize appearance +- **Keyboard navigation** for sidebar interaction +- **ARIA labels** on built-in controls +- **Focus management** when the sidebar opens on mobile +- **Responsive behavior** that works with keyboard, mouse, and touch input -## API Reference +## Related -- [useTheme](../api/use-theme.md) - Access theme context +- [AppShell](./app-shell.md) - root component +- [DefaultHeader](./default-header.md) - built-in content header (`SidebarLayout.DefaultHeader`) +- [DefaultSidebar](./default-sidebar.md) - built-in sidebar (`SidebarLayout.DefaultSidebar`) +- [GlobalHeaderLayout](./global-header-layout.md) - opinionated app-wide header mode built on SidebarLayout +- [AppearanceSwitcher](./appearance-switcher.md) - color-theme dropdown for header actions +- [usePageMeta](../api/use-page-meta.md) - route metadata lookup used by sidebar items diff --git a/docs/components/spinner.md b/docs/components/spinner.md new file mode 100644 index 000000000..ce09780de --- /dev/null +++ b/docs/components/spinner.md @@ -0,0 +1,75 @@ +--- +title: Spinner +description: Lightweight loading indicator component with accessible defaults and preset sizes +--- + +# Spinner + +`Spinner` is AppShell's reusable loading indicator. It renders a small rotating SVG and is used throughout the built-in UI for loading states such as `ActionPanel`, command palette search, and CSV import progress. + +## Import + +```tsx +import { Spinner } from "@tailor-platform/app-shell"; +``` + +## Basic usage + +```tsx + +``` + +## Props + +`Spinner` accepts all standard `` props plus one AppShell prop: + +| Prop | Type | Default | Description | +| ------ | -------------------------------------- | ----------- | --------------------------------------------------------------------- | +| `size` | `"xs" \| "sm" \| "default" \| "lg"` | `"default"` | Preset square size. Ignored when `width` or `height` is passed explicitly. | + +Preset sizes map to: + +| `size` | Pixels | +| ----------- | ------ | +| `"xs"` | `12` | +| `"sm"` | `14` | +| `"default"` | `16` | +| `"lg"` | `20` | + +## Accessibility + +`Spinner` defaults to decorative output unless you give it an accessible name: + +- with `aria-label` or `aria-labelledby`, it renders with `role="status"` +- with `aria-hidden={true}`, it stays decorative +- with neither, it is treated as decorative and gets `aria-hidden="true"` + +Use a label when the spinner is the primary visible loading affordance: + +```tsx + +``` + +When the surrounding control already has its own accessible name, keep the spinner decorative: + +```tsx + +``` + +## Sizing and styling + +Pass ordinary Tailwind utilities through `className` for color and layout, or override the dimensions directly with `width` / `height`. + +```tsx + + + +``` + +## Related + +- [ActionPanel](./action-panel.md) - uses `Spinner` for per-row loading states +- [WithGuard](./with-guard.md) - common place to render a loading spinner while guards resolve diff --git a/docs/components/table.md b/docs/components/table.md index c3daf2c1d..690d4af7e 100644 --- a/docs/components/table.md +++ b/docs/components/table.md @@ -160,7 +160,7 @@ All other sub-components (`Table.Header`, `Table.Body`, `Table.Footer`, `Table.R ### Constrained Height with Scroll ```tsx - + Name diff --git a/docs/components/timeline.md b/docs/components/timeline.md index d1599e3e1..2d0cea7b4 100644 --- a/docs/components/timeline.md +++ b/docs/components/timeline.md @@ -286,14 +286,14 @@ Use `decorations` to add background highlights and vertical marker lines: start: freezeStart, end: freezeEnd, color: "var(--destructive/10)", - className: "astw:border-x astw:border-destructive/30", + className: "border-x border-destructive/30", }, ], markers: [ { at: today, color: "var(--primary)", - label: Today, + label: Today, }, ], }} diff --git a/docs/components/tooltip.md b/docs/components/tooltip.md index 719c7c00e..7487aab1d 100644 --- a/docs/components/tooltip.md +++ b/docs/components/tooltip.md @@ -110,7 +110,7 @@ import { PencilIcon } from "lucide-react"; }> - Edit + Edit Edit order ; diff --git a/docs/concepts/styling-theming.md b/docs/concepts/styling-theming.md index 397da926d..a5548214a 100644 --- a/docs/concepts/styling-theming.md +++ b/docs/concepts/styling-theming.md @@ -78,13 +78,22 @@ Note that the `background` and `border` slots are already semi-transparent (a ~1 ## A note on AppShell component class names -AppShell components use Tailwind utility classes for their styling. Tailwind classes are generated at build-time, so stylesheet for AppShell components is already built and is separate to the Tailwind stylesheet generated for your application. +AppShell's own compiled CSS uses the `astw:` prefix internally to avoid collisions with an application's Tailwind output. Treat that prefix as an implementation detail, not as an application styling API. -In CSS, the order of style-definition affects the final styles which are computed for an element. Tailwind takes this into account when generating its stylesheet, however because it does not know that there's already a Tailwind-generated stylesheet included in the browser (AppShell's styles), there would be incorrect ordering of style definitions, and clashes can (though do not always) occur. +For application-authored markup, use ordinary Tailwind utilities: -To avoid this situation, and to ensure correct style resolution, AppShell components use a class prefix "astw" (AppShell TailWind) to avoid clashes. +```tsx +
+``` + +For AppShell components, prefer documented props and composition points over reaching for internal classes: + +```tsx + + +``` -This is important to note for developing in AppShell. +If you need to change behavior or structure, use the public escape hatches — such as `SidebarLayout` slots, `className` props, or component variants — rather than targeting AppShell's internal selectors. ## Color Themes (Light / Dark / System) diff --git a/docs/quickstart.md b/docs/quickstart.md index 2306afeb7..1487ed6f9 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -75,8 +75,8 @@ Create your first page at `src/pages/page.tsx`: // src/pages/page.tsx const HomePage = () => { return ( -
-

Welcome to AppShell

+
+

Welcome to AppShell

); }; @@ -111,8 +111,8 @@ src/pages/ // src/pages/dashboard/page.tsx const DashboardPage = () => { return ( -
-

Dashboard

+
+

Dashboard

); }; @@ -127,8 +127,8 @@ import { useParams } from "@tailor-platform/app-shell"; const OrderDetailPage = () => { const { id } = useParams(); return ( -
-

Order #{id}

+
+

Order #{id}

); };