@tailor-platform/app-shell 1.14.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/LICENSE.md +21 -0
  2. package/dist/app-shell-core.css +1 -1
  3. package/dist/app-shell.d.ts +1203 -316
  4. package/dist/app-shell.js +4562 -3307
  5. package/dist/{appshell-CtS8Y3LW.js → appshell-B0wh5Lq0.js} +850 -549
  6. package/dist/style-entry.d.ts +118 -98
  7. package/dist/testing.d.ts +118 -98
  8. package/dist/testing.js +1 -1
  9. package/dist/themes/_template.css +1 -1
  10. package/dist/vite-plugin.d.ts +118 -98
  11. package/package.json +13 -13
  12. package/skills/app-shell-patterns/SKILL.md +108 -24
  13. package/skills/app-shell-patterns/references/api/create-ai-gateway-client.md +115 -0
  14. package/skills/app-shell-patterns/references/api/create-typed-paths.md +117 -0
  15. package/skills/app-shell-patterns/references/api/define-i18n-labels.md +143 -0
  16. package/skills/app-shell-patterns/references/api/define-module.md +247 -0
  17. package/skills/app-shell-patterns/references/api/define-resource.md +307 -0
  18. package/skills/app-shell-patterns/references/api/guards/hidden.md +202 -0
  19. package/skills/app-shell-patterns/references/api/guards/overview.md +354 -0
  20. package/skills/app-shell-patterns/references/api/guards/pass.md +129 -0
  21. package/skills/app-shell-patterns/references/api/guards/redirect-to.md +307 -0
  22. package/skills/app-shell-patterns/references/api/router/use-location.md +240 -0
  23. package/skills/app-shell-patterns/references/api/router/use-navigate.md +138 -0
  24. package/skills/app-shell-patterns/references/api/router/use-params.md +218 -0
  25. package/skills/app-shell-patterns/references/api/use-ai-chat.md +110 -0
  26. package/skills/app-shell-patterns/references/api/use-app-shell-config.md +126 -0
  27. package/skills/app-shell-patterns/references/api/use-app-shell-data.md +146 -0
  28. package/skills/app-shell-patterns/references/api/use-app-shell.md +143 -0
  29. package/skills/app-shell-patterns/references/api/use-auth.md +145 -0
  30. package/skills/app-shell-patterns/references/api/use-override-breadcrumb.md +74 -0
  31. package/skills/app-shell-patterns/references/api/use-page-meta.md +96 -0
  32. package/skills/app-shell-patterns/references/api/use-register-command-palette-actions.md +90 -0
  33. package/skills/app-shell-patterns/references/api/use-resolved-locale.md +67 -0
  34. package/skills/app-shell-patterns/references/api/use-route-error.md +214 -0
  35. package/skills/app-shell-patterns/references/api/use-theme.md +108 -0
  36. package/skills/app-shell-patterns/references/api/use-time-zone.md +80 -0
  37. package/skills/app-shell-patterns/references/api/use-toast.md +107 -0
  38. package/skills/app-shell-patterns/references/components/action-panel.md +168 -0
  39. package/skills/app-shell-patterns/references/components/activity-card.md +142 -0
  40. package/skills/app-shell-patterns/references/components/ai-chat.md +315 -0
  41. package/skills/app-shell-patterns/references/components/alert.md +202 -0
  42. package/skills/app-shell-patterns/references/components/app-shell.md +530 -0
  43. package/skills/app-shell-patterns/references/components/appearance-switcher.md +87 -0
  44. package/skills/app-shell-patterns/references/components/attachment.md +138 -0
  45. package/skills/app-shell-patterns/references/components/autocomplete.md +278 -0
  46. package/skills/app-shell-patterns/references/components/avatar.md +80 -0
  47. package/skills/app-shell-patterns/references/components/badge.md +309 -0
  48. package/skills/app-shell-patterns/references/components/button.md +164 -0
  49. package/skills/app-shell-patterns/references/components/card.md +109 -0
  50. package/skills/app-shell-patterns/references/components/checkbox.md +169 -0
  51. package/skills/app-shell-patterns/references/components/combobox.md +318 -0
  52. package/skills/app-shell-patterns/references/components/command-palette.md +413 -0
  53. package/skills/app-shell-patterns/references/components/csv-importer.md +214 -0
  54. package/skills/app-shell-patterns/references/components/data-table.md +953 -0
  55. package/skills/app-shell-patterns/references/components/date-picker.md +248 -0
  56. package/skills/app-shell-patterns/references/components/default-header.md +75 -0
  57. package/skills/app-shell-patterns/references/components/default-sidebar.md +136 -0
  58. package/skills/app-shell-patterns/references/components/description-card.md +569 -0
  59. package/skills/app-shell-patterns/references/components/dialog.md +164 -0
  60. package/skills/app-shell-patterns/references/components/document-progress-card.md +154 -0
  61. package/skills/app-shell-patterns/references/components/form.md +378 -0
  62. package/skills/app-shell-patterns/references/components/global-header-layout.md +132 -0
  63. package/skills/app-shell-patterns/references/components/grid.md +184 -0
  64. package/skills/app-shell-patterns/references/components/input.md +83 -0
  65. package/skills/app-shell-patterns/references/components/layout.md +539 -0
  66. package/skills/app-shell-patterns/references/components/menu.md +218 -0
  67. package/skills/app-shell-patterns/references/components/metric-card.md +94 -0
  68. package/skills/app-shell-patterns/references/components/select.md +283 -0
  69. package/skills/app-shell-patterns/references/components/sheet.md +237 -0
  70. package/skills/app-shell-patterns/references/components/sidebar-group.md +466 -0
  71. package/skills/app-shell-patterns/references/components/sidebar-item.md +458 -0
  72. package/skills/app-shell-patterns/references/components/sidebar-layout.md +258 -0
  73. package/skills/app-shell-patterns/references/components/spinner.md +81 -0
  74. package/skills/app-shell-patterns/references/components/table.md +188 -0
  75. package/skills/app-shell-patterns/references/components/tabs.md +158 -0
  76. package/skills/app-shell-patterns/references/components/textarea.md +122 -0
  77. package/skills/app-shell-patterns/references/components/timeline.md +317 -0
  78. package/skills/app-shell-patterns/references/components/toolbar.md +138 -0
  79. package/skills/app-shell-patterns/references/components/tooltip.md +144 -0
  80. package/skills/app-shell-patterns/references/components/with-guard.md +445 -0
  81. package/skills/app-shell-patterns/references/concepts/authentication.md +301 -0
  82. package/skills/app-shell-patterns/references/concepts/custom-components.md +59 -0
  83. package/skills/app-shell-patterns/references/concepts/file-based-routing.md +350 -0
  84. package/skills/app-shell-patterns/references/{fundamental → concepts}/graphql.md +11 -4
  85. package/skills/app-shell-patterns/references/concepts/modules-and-resources.md +229 -0
  86. package/skills/app-shell-patterns/references/concepts/routing-navigation.md +387 -0
  87. package/skills/app-shell-patterns/references/concepts/sidebar-navigation.md +388 -0
  88. package/skills/app-shell-patterns/references/{fundamental/design-system.md → concepts/styling-theming.md} +260 -105
  89. package/skills/app-shell-patterns/references/migrations.md +34 -0
  90. package/skills/app-shell-patterns/references/pages/document-detail.md +1125 -0
  91. package/skills/app-shell-patterns/references/patterns/form-composer.md +44 -46
  92. package/skills/app-shell-patterns/references/patterns/form-modal.md +39 -100
  93. package/skills/app-shell-patterns/references/patterns/form-sectioned.md +12 -38
  94. package/skills/app-shell-patterns/references/patterns/form-single-page.md +11 -32
  95. package/skills/app-shell-patterns/references/patterns/form-wizard.md +12 -53
  96. package/skills/app-shell-patterns/references/patterns/interaction-confirm.md +11 -27
  97. package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +14 -28
  98. package/skills/app-shell-patterns/references/patterns/interaction-toast.md +10 -34
  99. package/skills/app-shell-patterns/references/patterns/list-dense-scan.md +11 -86
  100. package/skills/app-shell-patterns/references/fundamental/components.md +0 -981
  101. package/skills/app-shell-patterns/references/patterns/detail-hero-with-actions.md +0 -201
@@ -0,0 +1,144 @@
1
+ ---
2
+ title: Tooltip
3
+ description: Hover/focus tooltip with configurable placement and shared delay via Tooltip.Provider
4
+ ---
5
+
6
+ <!-- GENERATED by docs-kit from docs-src/components/tooltip.docs.outline.md — do not edit by hand. -->
7
+
8
+ # Tooltip
9
+
10
+ The `Tooltip` component displays contextual information on hover or focus. It is backed by Base UI's Tooltip primitive.
11
+
12
+ ## Import
13
+
14
+ ```tsx
15
+ import { Tooltip } from "@tailor-platform/app-shell";
16
+ ```
17
+
18
+ ## Basic Usage
19
+
20
+ <example-preview name="basic-usage"></example-preview>
21
+
22
+ ```tsx
23
+ function BasicUsage() {
24
+ return (
25
+ <Tooltip.Root>
26
+ <Tooltip.Trigger render={<Button variant="outline" />}>Hover me</Tooltip.Trigger>
27
+ <Tooltip.Content>Helpful information</Tooltip.Content>
28
+ </Tooltip.Root>
29
+ );
30
+ }
31
+ ```
32
+
33
+ ## Sub-components
34
+
35
+ | Sub-component | Description |
36
+ | ------------------ | ----------------------------------------------------------- |
37
+ | `Tooltip.Provider` | Provides shared delay configuration for all nested tooltips |
38
+ | `Tooltip.Root` | Manages tooltip open/close state |
39
+ | `Tooltip.Trigger` | Element that shows the tooltip on hover or focus |
40
+ | `Tooltip.Content` | The tooltip popup containing the help text |
41
+
42
+ ## Props
43
+
44
+ ### Tooltip.Provider Props
45
+
46
+ Wrap a section of your UI (e.g. a toolbar) with `Tooltip.Provider` to share delay settings across multiple tooltips.
47
+
48
+ | Prop | Type | Default | Description |
49
+ | ------------ | ----------------- | ------- | --------------------------------------------------- |
50
+ | `delay` | `number` | `0` | Delay in ms before the tooltip opens (milliseconds) |
51
+ | `closeDelay` | `number` | - | Delay in ms before the tooltip closes |
52
+ | `timeout` | `number` | - | Timeout in ms after which the tooltip auto-closes |
53
+ | `children` | `React.ReactNode` | - | Nested tooltip components |
54
+
55
+ ### Tooltip.Root Props
56
+
57
+ | Prop | Type | Default | Description |
58
+ | -------------- | ------------------------- | ------- | --------------------------------- |
59
+ | `open` | `boolean` | - | Controlled open state |
60
+ | `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
61
+ | `onOpenChange` | `(open: boolean) => void` | - | Callback when open state changes |
62
+ | `children` | `React.ReactNode` | - | Tooltip sub-components |
63
+
64
+ ### Tooltip.Content Props
65
+
66
+ | Prop | Type | Default | Description |
67
+ | ------------ | ---------------------------------------- | ---------- | -------------------------------------------------------- |
68
+ | `side` | `"top" \| "right" \| "bottom" \| "left"` | `"top"` | Preferred placement of the tooltip |
69
+ | `align` | `"start" \| "center" \| "end"` | `"center"` | Alignment along the placement axis |
70
+ | `sideOffset` | `number` | `5` | Distance in px from the trigger |
71
+ | `noArrow` | `boolean` | `false` | When `true`, suppresses the arrow indicator on the popup |
72
+ | `className` | `string` | - | Additional CSS classes |
73
+ | `children` | `React.ReactNode` | - | Tooltip text or content |
74
+
75
+ ## Placement
76
+
77
+ ```tsx
78
+ <Tooltip.Root>
79
+ <Tooltip.Trigger render={<Button />}>Trigger</Tooltip.Trigger>
80
+ <Tooltip.Content side="right" align="start">
81
+ Appears on the right
82
+ </Tooltip.Content>
83
+ </Tooltip.Root>
84
+ ```
85
+
86
+ ## Shared Delay with Provider
87
+
88
+ Use `Tooltip.Provider` to avoid tooltips opening immediately in dense UIs like toolbars:
89
+
90
+ ```tsx
91
+ <Tooltip.Provider delay={300}>
92
+ <Toolbar>
93
+ <Tooltip.Root>
94
+ <Tooltip.Trigger render={<Button size="icon" />}>
95
+ <BoldIcon />
96
+ </Tooltip.Trigger>
97
+ <Tooltip.Content>Bold</Tooltip.Content>
98
+ </Tooltip.Root>
99
+ <Tooltip.Root>
100
+ <Tooltip.Trigger render={<Button size="icon" />}>
101
+ <ItalicIcon />
102
+ </Tooltip.Trigger>
103
+ <Tooltip.Content>Italic</Tooltip.Content>
104
+ </Tooltip.Root>
105
+ </Toolbar>
106
+ </Tooltip.Provider>
107
+ ```
108
+
109
+ ## Examples
110
+
111
+ ### Icon Button with Tooltip
112
+
113
+ ```tsx
114
+ import { PencilIcon } from "lucide-react";
115
+
116
+ <Tooltip.Root>
117
+ <Tooltip.Trigger render={<Button size="icon" variant="ghost" />}>
118
+ <PencilIcon />
119
+ <span className="sr-only">Edit</span>
120
+ </Tooltip.Trigger>
121
+ <Tooltip.Content>Edit order</Tooltip.Content>
122
+ </Tooltip.Root>;
123
+ ```
124
+
125
+ ### Controlled Tooltip
126
+
127
+ ```tsx
128
+ const [open, setOpen] = useState(false);
129
+
130
+ <Tooltip.Root open={open} onOpenChange={setOpen}>
131
+ <Tooltip.Trigger render={<Button />}>Info</Tooltip.Trigger>
132
+ <Tooltip.Content>Additional details here</Tooltip.Content>
133
+ </Tooltip.Root>;
134
+ ```
135
+
136
+ ## Accessibility
137
+
138
+ - Tooltip content is rendered in a portal
139
+ - Trigger receives focus and keyboard activation support
140
+ - `sr-only` text on icon-only triggers is recommended for screen readers
141
+
142
+ ## Related Components
143
+
144
+ - [Button](./button.md) - Common trigger element for tooltips
@@ -0,0 +1,445 @@
1
+ ---
2
+ title: WithGuard
3
+ description: Conditionally render UI elements based on guard evaluation with support for async permissions checks
4
+ ---
5
+
6
+ <!-- GENERATED by docs-kit from docs-src/components/with-guard.docs.outline.md — do not edit by hand. -->
7
+
8
+ # WithGuard
9
+
10
+ `WithGuard` is a component for conditionally rendering UI elements based on guard evaluation. It uses the same guard system as route-level guards, making permission logic reusable throughout your application.
11
+
12
+ ## Import
13
+
14
+ ```tsx
15
+ import { WithGuard, pass, hidden } from "@tailor-platform/app-shell";
16
+ ```
17
+
18
+ ## Basic Usage
19
+
20
+ ```tsx
21
+ import { WithGuard, pass, hidden } from "@tailor-platform/app-shell";
22
+
23
+ const isAdmin = ({ context }) => (context.currentUser?.role === "admin" ? pass() : hidden());
24
+
25
+ function AdminPanel() {
26
+ return (
27
+ <div>
28
+ <h1>Dashboard</h1>
29
+
30
+ <WithGuard guards={[isAdmin]}>
31
+ <Button>Delete All Users</Button>
32
+ </WithGuard>
33
+ </div>
34
+ );
35
+ }
36
+ ```
37
+
38
+ If the user is not an admin, the button won't render.
39
+
40
+ ## Props
41
+
42
+ _Generated from the type surface — first-party props only._
43
+
44
+ ### WithGuardProps
45
+
46
+ | Prop | Type | Default | Description |
47
+ | ----------- | ----------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
48
+ | `children` | `ReactNode` | - | Content to render when all guards pass. |
49
+ | `fallback?` | `ReactNode` | `null (renders nothing)` | Content to render when any guard returns hidden. |
50
+ | `guards` | `Guard[]` | - | Guards to evaluate. All guards must pass for children to render. Uses the same Guard type as route guards for full reusability. |
51
+ | `loading?` | `ReactNode` | `null (renders nothing)` | Content to render while guards are being evaluated (for async guards). |
52
+
53
+ ## Exports
54
+
55
+ - `WithGuard` — const
56
+ - `WithGuardProps` — type
57
+
58
+ ## Guard Functions
59
+
60
+ WithGuard uses the same `Guard` type as route guards:
61
+
62
+ ```typescript
63
+ type Guard = (ctx: GuardContext) => GuardResult | Promise<GuardResult>;
64
+
65
+ type GuardResult =
66
+ | { type: "pass" } // Allow rendering
67
+ | { type: "hidden" }; // Hide content (render fallback)
68
+ ```
69
+
70
+ **Note:** Unlike route guards, `redirectTo()` is **not supported** in WithGuard. Use `hidden()` with a fallback that handles navigation if needed.
71
+
72
+ ### Creating Guards
73
+
74
+ ```tsx
75
+ import { type Guard, pass, hidden } from "@tailor-platform/app-shell";
76
+
77
+ // Simple guard
78
+ const requireAuth: Guard = ({ context }) => {
79
+ return context.currentUser ? pass() : hidden();
80
+ };
81
+
82
+ // Async guard with API call
83
+ const hasPermission: Guard = async ({ context }) => {
84
+ const canAccess = await fetch("/api/permissions/admin")
85
+ .then((r) => r.json())
86
+ .then((data) => data.allowed);
87
+
88
+ return canAccess ? pass() : hidden();
89
+ };
90
+
91
+ // Parameterized guard (curried function)
92
+ const isOwner =
93
+ (resourceId: string): Guard =>
94
+ ({ context }) => {
95
+ return context.currentUser?.id === resourceId ? pass() : hidden();
96
+ };
97
+ ```
98
+
99
+ ## Examples
100
+
101
+ ### Simple Role Check
102
+
103
+ ```tsx
104
+ const isAdmin = ({ context }) => (context.currentUser?.role === "admin" ? pass() : hidden());
105
+
106
+ <WithGuard guards={[isAdmin]}>
107
+ <Button>Admin Settings</Button>
108
+ </WithGuard>;
109
+ ```
110
+
111
+ ### With Fallback
112
+
113
+ Show alternative content when guard fails:
114
+
115
+ ```tsx
116
+ <WithGuard guards={[isAdmin]} fallback={<p>You need admin access to view this.</p>}>
117
+ <AdminPanel />
118
+ </WithGuard>
119
+ ```
120
+
121
+ ### With Loading State
122
+
123
+ Show loading spinner while checking permissions:
124
+
125
+ ```tsx
126
+ const checkPermission: Guard = async ({ context }) => {
127
+ const allowed = await api.checkPermission("admin");
128
+ return allowed ? pass() : hidden();
129
+ };
130
+
131
+ <WithGuard guards={[checkPermission]} loading={<Spinner />} fallback={<UpgradePrompt />}>
132
+ <PremiumFeature />
133
+ </WithGuard>;
134
+ ```
135
+
136
+ ### Multiple Guards
137
+
138
+ All guards must pass for content to render:
139
+
140
+ ```tsx
141
+ const isAdmin = ({ context }) => (context.currentUser?.role === "admin" ? pass() : hidden());
142
+
143
+ const hasFeatureFlag = ({ context }) => (context.featureFlags?.betaFeatures ? pass() : hidden());
144
+
145
+ <WithGuard guards={[isAdmin, hasFeatureFlag]}>
146
+ <BetaAdminFeature />
147
+ </WithGuard>;
148
+ ```
149
+
150
+ ### Parameterized Guards
151
+
152
+ Use curried functions to create reusable parameterized guards:
153
+
154
+ ```tsx
155
+ const isOwner =
156
+ (resourceId: string): Guard =>
157
+ ({ context }) => {
158
+ return context.currentUser?.id === resourceId ? pass() : hidden();
159
+ };
160
+
161
+ function ResourceActions({ resourceId }) {
162
+ return (
163
+ <WithGuard guards={[isOwner(resourceId)]}>
164
+ <Button>Edit</Button>
165
+ <Button>Delete</Button>
166
+ </WithGuard>
167
+ );
168
+ }
169
+ ```
170
+
171
+ ### In Sidebar Navigation
172
+
173
+ Conditionally show navigation items:
174
+
175
+ ```tsx
176
+ import { DefaultSidebar, SidebarItem, WithGuard } from "@tailor-platform/app-shell";
177
+
178
+ const isAdmin = ({ context }) => (context.currentUser?.role === "admin" ? pass() : hidden());
179
+
180
+ <DefaultSidebar>
181
+ <SidebarItem to="/dashboard" />
182
+ <SidebarItem to="/products" />
183
+
184
+ <WithGuard guards={[isAdmin]}>
185
+ <SidebarItem to="/admin" />
186
+ <SidebarItem to="/settings" />
187
+ </WithGuard>
188
+ </DefaultSidebar>;
189
+ ```
190
+
191
+ ### Feature Flags
192
+
193
+ Toggle features based on flags:
194
+
195
+ ```tsx
196
+ const hasBetaFeature = ({ context }) => (context.featureFlags?.newDashboard ? pass() : hidden());
197
+
198
+ <WithGuard guards={[hasBetaFeature]} fallback={<OldDashboard />}>
199
+ <NewDashboard />
200
+ </WithGuard>;
201
+ ```
202
+
203
+ ### Subscription Tiers
204
+
205
+ Show features based on user's subscription:
206
+
207
+ ```tsx
208
+ const isPremium = ({ context }) =>
209
+ context.currentUser?.subscription === "premium" ? pass() : hidden();
210
+
211
+ <WithGuard
212
+ guards={[isPremium]}
213
+ fallback={
214
+ <Card>
215
+ <h3>Premium Feature</h3>
216
+ <p>Upgrade to access advanced analytics.</p>
217
+ <Button>Upgrade Now</Button>
218
+ </Card>
219
+ }
220
+ >
221
+ <AdvancedAnalytics />
222
+ </WithGuard>;
223
+ ```
224
+
225
+ ### Dynamic Permissions
226
+
227
+ Check permissions from API:
228
+
229
+ ```tsx
230
+ const canDeleteOrder =
231
+ (orderId: string): Guard =>
232
+ async ({ context }) => {
233
+ const response = await fetch(`/api/orders/${orderId}/permissions`);
234
+ const { canDelete } = await response.json();
235
+ return canDelete ? pass() : hidden();
236
+ };
237
+
238
+ function OrderActions({ orderId }) {
239
+ return (
240
+ <div>
241
+ <Button>View</Button>
242
+ <Button>Edit</Button>
243
+ <WithGuard guards={[canDeleteOrder(orderId)]} loading={<Spinner size="sm" />}>
244
+ <Button variant="destructive">Delete</Button>
245
+ </WithGuard>
246
+ </div>
247
+ );
248
+ }
249
+ ```
250
+
251
+ ### Combining with Route Guards
252
+
253
+ Reuse the same guards for routes and UI:
254
+
255
+ ```tsx
256
+ // guards.ts
257
+ export const requireAuth: Guard = ({ context }) => {
258
+ return context.currentUser ? pass() : hidden();
259
+ };
260
+
261
+ export const isAdmin: Guard = ({ context }) => {
262
+ return context.currentUser?.role === "admin" ? pass() : hidden();
263
+ };
264
+
265
+ // routes.tsx
266
+ const adminModule = defineModule({
267
+ path: "admin",
268
+ component: AdminPage,
269
+ guards: [requireAuth, isAdmin], // Route-level
270
+ });
271
+
272
+ // component.tsx
273
+ <WithGuard guards={[requireAuth, isAdmin]}>
274
+ {" "}
275
+ {/* UI-level */}
276
+ <AdminSettings />
277
+ </WithGuard>;
278
+ ```
279
+
280
+ ## Guard Evaluation
281
+
282
+ Guards are evaluated in order and stop at the first non-pass result:
283
+
284
+ ```tsx
285
+ const guards = [guardA, guardB, guardC];
286
+
287
+ // If guardA returns hidden(), guardB and guardC are NOT evaluated
288
+ <WithGuard guards={guards}>
289
+ <Content />
290
+ </WithGuard>;
291
+ ```
292
+
293
+ ## Performance
294
+
295
+ ### Caching
296
+
297
+ WithGuard caches guard results based on `contextData` reference:
298
+
299
+ - Guards re-evaluate when `contextData` changes
300
+ - Guards do NOT re-evaluate when props change (except `guards` array)
301
+
302
+ ### Suspense Integration
303
+
304
+ Async guards use React Suspense:
305
+
306
+ - Shows `loading` prop while evaluating
307
+ - Renders `children` or `fallback` when complete
308
+ - Automatically suspends during evaluation
309
+
310
+ ## Context Data
311
+
312
+ Guards receive the same context as route guards:
313
+
314
+ ```typescript
315
+ // Define context type
316
+ declare module "@tailor-platform/app-shell" {
317
+ interface AppShellRegister {
318
+ contextData: {
319
+ currentUser: User | null;
320
+ featureFlags: FeatureFlags;
321
+ permissions: string[];
322
+ };
323
+ }
324
+ }
325
+
326
+ // Use in guards
327
+ const hasPermission =
328
+ (perm: string): Guard =>
329
+ ({ context }) => {
330
+ return context.permissions.includes(perm) ? pass() : hidden();
331
+ };
332
+ ```
333
+
334
+ ## Comparison: WithGuard vs Route Guards
335
+
336
+ | Feature | WithGuard | Route Guards |
337
+ | --------------------------- | ------------------------ | --------------------------- |
338
+ | **Purpose** | Conditional UI rendering | Route access control |
339
+ | **Location** | Inside components | Module/resource definitions |
340
+ | **Supports `pass()`** | ✅ Yes | ✅ Yes |
341
+ | **Supports `hidden()`** | ✅ Yes | ✅ Yes |
342
+ | **Supports `redirectTo()`** | ❌ No | ✅ Yes |
343
+ | **Async guards** | ✅ Yes | ✅ Yes |
344
+ | **Reusable** | ✅ Yes | ✅ Yes |
345
+
346
+ ## Best Practices
347
+
348
+ ### Do:
349
+
350
+ - ✅ Reuse guards between routes and UI
351
+ - ✅ Use descriptive guard names (`isAdmin`, `canEdit`)
352
+ - ✅ Provide fallback for better UX
353
+ - ✅ Show loading state for async guards
354
+ - ✅ Cache expensive permission checks
355
+
356
+ ### Don't:
357
+
358
+ - ❌ Use `redirectTo()` in WithGuard (not supported)
359
+ - ❌ Make guards too complex (extract to functions)
360
+ - ❌ Forget to handle loading states
361
+ - ❌ Skip fallback when hiding important features
362
+ - ❌ Duplicate permission logic (reuse guards)
363
+
364
+ ## Accessibility
365
+
366
+ - Content is fully removed from DOM when hidden (not just visually)
367
+ - Screen readers won't announce hidden content
368
+ - Loading states should be announced to screen readers
369
+ - Fallback content should be accessible
370
+
371
+ ## TypeScript
372
+
373
+ Full type safety with TypeScript:
374
+
375
+ ```typescript
376
+ import { type Guard, type WithGuardProps } from "@tailor-platform/app-shell";
377
+
378
+ // Type-safe guard
379
+ const isAdmin: Guard = ({ context }) => {
380
+ // context is fully typed
381
+ return context.currentUser?.role === "admin" ? pass() : hidden();
382
+ };
383
+
384
+ // Type-safe props
385
+ const guardProps: WithGuardProps = {
386
+ guards: [isAdmin],
387
+ children: <AdminPanel />,
388
+ fallback: <AccessDenied />,
389
+ };
390
+ ```
391
+
392
+ ## Troubleshooting
393
+
394
+ ### Guard not re-evaluating
395
+
396
+ **Problem:** Guard doesn't update when data changes
397
+
398
+ **Solution:** Ensure `contextData` reference changes:
399
+
400
+ ```tsx
401
+ // ❌ Bad - mutating object
402
+ context.currentUser.role = "admin";
403
+
404
+ // ✅ Good - new reference
405
+ setContextData({
406
+ ...contextData,
407
+ currentUser: { ...currentUser, role: "admin" },
408
+ });
409
+ ```
410
+
411
+ ### Infinite loading
412
+
413
+ **Problem:** Loading state never resolves
414
+
415
+ **Solution:** Ensure async guards return a result:
416
+
417
+ ```tsx
418
+ // ❌ Bad - no return
419
+ const checkPermission: Guard = async ({ context }) => {
420
+ await fetch("/api/check");
421
+ // Missing return!
422
+ };
423
+
424
+ // ✅ Good - returns result
425
+ const checkPermission: Guard = async ({ context }) => {
426
+ const allowed = await fetch("/api/check").then((r) => r.json());
427
+ return allowed ? pass() : hidden();
428
+ };
429
+ ```
430
+
431
+ ## Related Components
432
+
433
+ - [AppShell](./app-shell.md) - Context provider for guards
434
+ - [SidebarItem](./sidebar-item.md) - Use WithGuard in navigation
435
+
436
+ ## Related Concepts
437
+
438
+ - [Guards and Permissions](../api/guards/overview.md) - Comprehensive guard guide
439
+ - [Modules and Resources](../concepts/modules-and-resources.md) - Route-level guards
440
+
441
+ ## API Reference
442
+
443
+ - [pass()](../api/guards/pass.md) - Allow access guard
444
+ - [hidden()](../api/guards/hidden.md) - Hide content guard
445
+ - [Guards Overview](../api/guards/overview.md) - Complete guard reference