@tailor-platform/app-shell 1.15.0 → 1.17.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 (109) hide show
  1. package/dist/app-shell-core.css +1 -1
  2. package/dist/app-shell.d.ts +811 -131
  3. package/dist/app-shell.js +7155 -5797
  4. package/dist/{appshell-DLVBFo5J.js → appshell-CahhZy7v.js} +656 -524
  5. package/dist/semantic-contrast.test.ts +154 -0
  6. package/dist/style-entry.d.ts +147 -92
  7. package/dist/testing.d.ts +152 -95
  8. package/dist/testing.js +1 -1
  9. package/dist/theme-bridge.test.ts +101 -1
  10. package/dist/theme.bridge.css +91 -0
  11. package/dist/theme.css +6 -1
  12. package/dist/themes/_template.css +7 -1
  13. package/dist/themes/bloom.css +0 -6
  14. package/dist/themes/cream.css +0 -6
  15. package/dist/themes/default.css +389 -69
  16. package/dist/vite-plugin.d.ts +147 -92
  17. package/package.json +8 -8
  18. package/skills/app-shell-patterns/SKILL.md +108 -19
  19. package/skills/app-shell-patterns/references/api/create-ai-gateway-client.md +115 -0
  20. package/skills/app-shell-patterns/references/api/create-typed-paths.md +117 -0
  21. package/skills/app-shell-patterns/references/api/define-i18n-labels.md +143 -0
  22. package/skills/app-shell-patterns/references/api/define-module.md +247 -0
  23. package/skills/app-shell-patterns/references/api/define-resource.md +307 -0
  24. package/skills/app-shell-patterns/references/api/guards/hidden.md +202 -0
  25. package/skills/app-shell-patterns/references/api/guards/overview.md +354 -0
  26. package/skills/app-shell-patterns/references/api/guards/pass.md +129 -0
  27. package/skills/app-shell-patterns/references/api/guards/redirect-to.md +307 -0
  28. package/skills/app-shell-patterns/references/api/router/use-location.md +240 -0
  29. package/skills/app-shell-patterns/references/api/router/use-navigate.md +138 -0
  30. package/skills/app-shell-patterns/references/api/router/use-params.md +218 -0
  31. package/skills/app-shell-patterns/references/api/use-ai-chat.md +110 -0
  32. package/skills/app-shell-patterns/references/api/use-app-shell-config.md +132 -0
  33. package/skills/app-shell-patterns/references/api/use-app-shell-data.md +150 -0
  34. package/skills/app-shell-patterns/references/api/use-app-shell.md +150 -0
  35. package/skills/app-shell-patterns/references/api/use-auth.md +145 -0
  36. package/skills/app-shell-patterns/references/api/use-override-breadcrumb.md +74 -0
  37. package/skills/app-shell-patterns/references/api/use-page-meta.md +96 -0
  38. package/skills/app-shell-patterns/references/api/use-register-command-palette-actions.md +90 -0
  39. package/skills/app-shell-patterns/references/api/use-resolved-locale.md +67 -0
  40. package/skills/app-shell-patterns/references/api/use-route-error.md +214 -0
  41. package/skills/app-shell-patterns/references/api/use-theme.md +108 -0
  42. package/skills/app-shell-patterns/references/api/use-time-zone.md +80 -0
  43. package/skills/app-shell-patterns/references/api/use-toast.md +104 -0
  44. package/skills/app-shell-patterns/references/components/action-panel.md +168 -0
  45. package/skills/app-shell-patterns/references/components/activity-card.md +142 -0
  46. package/skills/app-shell-patterns/references/components/ai-chat.md +315 -0
  47. package/skills/app-shell-patterns/references/components/alert.md +202 -0
  48. package/skills/app-shell-patterns/references/components/app-shell.md +530 -0
  49. package/skills/app-shell-patterns/references/components/appearance-switcher.md +87 -0
  50. package/skills/app-shell-patterns/references/components/attachment.md +138 -0
  51. package/skills/app-shell-patterns/references/components/autocomplete.md +278 -0
  52. package/skills/app-shell-patterns/references/components/avatar.md +80 -0
  53. package/skills/app-shell-patterns/references/components/badge.md +309 -0
  54. package/skills/app-shell-patterns/references/components/button.md +164 -0
  55. package/skills/app-shell-patterns/references/components/card.md +181 -0
  56. package/skills/app-shell-patterns/references/components/checkbox.md +169 -0
  57. package/skills/app-shell-patterns/references/components/combobox.md +318 -0
  58. package/skills/app-shell-patterns/references/components/command-palette.md +413 -0
  59. package/skills/app-shell-patterns/references/components/csv-importer.md +214 -0
  60. package/skills/app-shell-patterns/references/components/data-table.md +1168 -0
  61. package/skills/app-shell-patterns/references/components/date-picker.md +259 -0
  62. package/skills/app-shell-patterns/references/components/default-header.md +75 -0
  63. package/skills/app-shell-patterns/references/components/default-sidebar.md +136 -0
  64. package/skills/app-shell-patterns/references/components/description-card.md +569 -0
  65. package/skills/app-shell-patterns/references/components/dialog.md +164 -0
  66. package/skills/app-shell-patterns/references/components/document-progress-card.md +154 -0
  67. package/skills/app-shell-patterns/references/components/filter-rail.md +165 -0
  68. package/skills/app-shell-patterns/references/components/form.md +378 -0
  69. package/skills/app-shell-patterns/references/components/global-header-layout.md +132 -0
  70. package/skills/app-shell-patterns/references/components/grid.md +184 -0
  71. package/skills/app-shell-patterns/references/components/input.md +83 -0
  72. package/skills/app-shell-patterns/references/components/layout.md +539 -0
  73. package/skills/app-shell-patterns/references/components/menu.md +218 -0
  74. package/skills/app-shell-patterns/references/components/metric-card.md +94 -0
  75. package/skills/app-shell-patterns/references/components/select.md +283 -0
  76. package/skills/app-shell-patterns/references/components/sheet.md +237 -0
  77. package/skills/app-shell-patterns/references/components/sidebar-group.md +472 -0
  78. package/skills/app-shell-patterns/references/components/sidebar-item.md +458 -0
  79. package/skills/app-shell-patterns/references/components/sidebar-layout.md +258 -0
  80. package/skills/app-shell-patterns/references/components/sidebar-section.md +114 -0
  81. package/skills/app-shell-patterns/references/components/spinner.md +81 -0
  82. package/skills/app-shell-patterns/references/components/table.md +188 -0
  83. package/skills/app-shell-patterns/references/components/tabs.md +158 -0
  84. package/skills/app-shell-patterns/references/components/textarea.md +122 -0
  85. package/skills/app-shell-patterns/references/components/timeline.md +317 -0
  86. package/skills/app-shell-patterns/references/components/toolbar.md +138 -0
  87. package/skills/app-shell-patterns/references/components/tooltip.md +144 -0
  88. package/skills/app-shell-patterns/references/components/with-guard.md +445 -0
  89. package/skills/app-shell-patterns/references/concepts/authentication.md +301 -0
  90. package/skills/app-shell-patterns/references/concepts/custom-components.md +59 -0
  91. package/skills/app-shell-patterns/references/concepts/file-based-routing.md +356 -0
  92. package/skills/app-shell-patterns/references/{fundamental → concepts}/graphql.md +31 -18
  93. package/skills/app-shell-patterns/references/concepts/modules-and-resources.md +229 -0
  94. package/skills/app-shell-patterns/references/concepts/routing-navigation.md +425 -0
  95. package/skills/app-shell-patterns/references/concepts/sidebar-navigation.md +420 -0
  96. package/skills/app-shell-patterns/references/concepts/styling-theming.md +785 -0
  97. package/skills/app-shell-patterns/references/migrations.md +55 -0
  98. package/skills/app-shell-patterns/references/pages/{detail.md → document-detail.md} +10 -33
  99. package/skills/app-shell-patterns/references/patterns/form-composer.md +43 -45
  100. package/skills/app-shell-patterns/references/patterns/form-modal.md +37 -92
  101. package/skills/app-shell-patterns/references/patterns/form-sectioned.md +12 -38
  102. package/skills/app-shell-patterns/references/patterns/form-single-page.md +11 -32
  103. package/skills/app-shell-patterns/references/patterns/form-wizard.md +12 -53
  104. package/skills/app-shell-patterns/references/patterns/interaction-confirm.md +11 -27
  105. package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +136 -139
  106. package/skills/app-shell-patterns/references/patterns/interaction-toast.md +10 -34
  107. package/skills/app-shell-patterns/references/patterns/list-dense-scan.md +13 -88
  108. package/skills/app-shell-patterns/references/fundamental/components.md +0 -1009
  109. package/skills/app-shell-patterns/references/fundamental/design-system.md +0 -440
@@ -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