Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions docs/api/use-page-meta.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
9 changes: 1 addition & 8 deletions docs/components/action-panel.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
18 changes: 9 additions & 9 deletions docs/components/ai-chat.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function Assistant() {

return (
// `overflow-hidden` keeps the header rule inside the card's rounded corners.
<Card.Root className="astw:flex astw:h-full astw:flex-col astw:overflow-hidden">
<Card.Root className="flex h-full flex-col overflow-hidden">
<AIChat status={status}>
<AIChat.Header title="Assistant" />
<AIChat.Conversation>
Expand Down Expand Up @@ -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

Expand All @@ -134,7 +134,7 @@ Inside a `<Layout fill>` column (the column is `flex flex-col`, so the card take
<Layout fill>
<Layout.Header title="Assistant" />
<Layout.Column>
<Card.Root className="astw:flex astw:min-h-0 astw:flex-1 astw:flex-col astw:overflow-hidden">
<Card.Root className="flex min-h-0 flex-1 flex-col overflow-hidden">
<AIChat status={status}>
<AIChat.Header title="Assistant" />
<AIChat.Conversation>{/* … */}</AIChat.Conversation>
Expand All @@ -145,7 +145,7 @@ Inside a `<Layout fill>` column (the column is `flex flex-col`, so the card take
</Layout>
```

`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

Expand All @@ -157,10 +157,10 @@ A 48px strip above the transcript: leading graphic, title, and an open action sl
actions={
<>
<AIChat.Action label="Conversation history" onClick={openHistory}>
<History className="astw:size-3.5" aria-hidden />
<History className="size-3.5" aria-hidden />
</AIChat.Action>
<AIChat.Action label="Clear conversation" onClick={clear}>
<Eraser className="astw:size-3.5" aria-hidden />
<Eraser className="size-3.5" aria-hidden />
</AIChat.Action>
</>
}
Expand All @@ -174,7 +174,7 @@ For a docked right panel, put the collapse control in `icon` so it takes the lea
title="Assistant"
icon={
<AIChat.Action label="Collapse panel" onClick={onClose}>
<ChevronsRight className="astw:size-3.5" aria-hidden />
<ChevronsRight className="size-3.5" aria-hidden />
</AIChat.Action>
}
/>
Expand Down Expand Up @@ -206,7 +206,7 @@ Renders the markdown subset a streamed LLM response actually emits — bold, inl

```tsx
<AIChat.EmptyState
icon={<Sparkles className="astw:size-6 astw:text-primary" aria-hidden />}
icon={<Sparkles className="size-6 text-primary" aria-hidden />}
title="Ask the assistant"
description="Grounded in your help articles."
>
Expand All @@ -225,7 +225,7 @@ Icon-button row under a finished assistant turn — copy, retry, feedback. Rende
```tsx
<AIChat.Actions>
<AIChat.Action label="Copy" onClick={() => navigator.clipboard.writeText(message.content)}>
<Copy className="astw:size-3.5" aria-hidden />
<Copy className="size-3.5" aria-hidden />
</AIChat.Action>
</AIChat.Actions>
```
Expand Down
5 changes: 2 additions & 3 deletions docs/components/alert.md
Original file line number Diff line number Diff line change
Expand Up @@ -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";

<div className={cn(alertVariants({ variant: "info" }), "astw:my-4")}>Custom alert container</div>;
<div className={`${alertVariants({ variant: "info" })} my-4`}>Custom alert container</div>;
```

## Related Components
Expand Down
10 changes: 5 additions & 5 deletions docs/components/app-shell.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,9 @@ import { useRouteError } from "@tailor-platform/app-shell";
const GlobalErrorBoundary = () => {
const error = useRouteError() as Error;
return (
<div className="astw:p-8">
<h1 className="astw:text-xl astw:font-bold astw:mb-4">Something went wrong</h1>
<p className="astw:text-red-600">{error.message}</p>
<div className="p-8">
<h1 className="mb-4 text-xl font-bold">Something went wrong</h1>
<p className="text-red-600">{error.message}</p>
</div>
);
};
Expand Down Expand Up @@ -449,8 +449,8 @@ declare module "@tailor-platform/app-shell" {
const ErrorBoundary = () => {
const error = useRouteError() as Error;
return (
<div className="astw:p-8">
<h1 className="astw:text-xl astw:font-bold">Error</h1>
<div className="p-8">
<h1 className="text-xl font-bold">Error</h1>
<p>{error.message}</p>
</div>
);
Expand Down
17 changes: 8 additions & 9 deletions docs/components/badge.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ const priorities = [
{ label: "Critical", variant = "error" },
];

<div className="astw:flex astw:gap-2">
<div className="flex gap-2">
{priorities.map((p) => (
<Badge key={p.label} variant={p.variant}>
{p.label}
Expand All @@ -148,17 +148,17 @@ const priorities = [
```tsx
import { CheckCircle, AlertCircle, XCircle } from "lucide-react";

<div className="astw:flex astw:gap-2">
<div className="flex gap-2">
<Badge variant="success">
<CheckCircle className="astw:w-3 astw:h-3 astw:mr-1" />
<CheckCircle className="mr-1 h-3 w-3" />
Verified
</Badge>
<Badge variant="warning">
<AlertCircle className="astw:w-3 astw:h-3 astw:mr-1" />
<AlertCircle className="mr-1 h-3 w-3" />
Review
</Badge>
<Badge variant="error">
<XCircle className="astw:w-3 astw:h-3 astw:mr-1" />
<XCircle className="mr-1 h-3 w-3" />
Blocked
</Badge>
</div>;
Expand Down Expand Up @@ -188,7 +188,7 @@ function ProductBadge({ product }: { product: Product }) {
### Custom Styling

```tsx
<Badge variant="success" className="astw:text-xs astw:px-3 astw:py-1 astw:uppercase">
<Badge variant="success" className="px-3 py-1 text-xs uppercase">
Premium
</Badge>
```
Expand Down Expand Up @@ -279,15 +279,14 @@ 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
3. **Creating custom variants** using the `badgeVariants` utility

```tsx
import { badgeVariants } from "@tailor-platform/app-shell";
import { cn } from "@/lib/utils";

<div className={cn(badgeVariants({ variant: "success" }), "astw:text-lg")}>Custom Badge</div>;
<div className={`${badgeVariants({ variant: "success" })} text-lg`}>Custom Badge</div>;
```
2 changes: 1 addition & 1 deletion docs/components/button.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ This is the Base UI render pattern — the button's class names and event handle
### Form Actions

```tsx
<div className="astw:flex astw:gap-2 astw:justify-end">
<div className="flex justify-end gap-2">
<Button variant="outline">Cancel</Button>
<Button>Save Changes</Button>
</div>
Expand Down
48 changes: 11 additions & 37 deletions docs/components/command-palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 (
<Dialog open={open} onOpenChange={handleOpenChange}>
<Input
value={search}
onChange={(e) => setSearch(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Search pages..."
/>
{filteredRoutes.map((route) => (
<button key={route.path} onClick={() => handleSelect(route)}>
{route.icon}
{route.title}
<span>{route.breadcrumb.join(" > ")}</span>
</button>
))}
</Dialog>
);
}
```
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

Expand Down
Loading