@tailor-platform/app-shell 1.16.0 → 1.18.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 (45) hide show
  1. package/dist/app-shell-core.css +1 -1
  2. package/dist/app-shell.d.ts +762 -51
  3. package/dist/app-shell.js +7642 -5636
  4. package/dist/{appshell-B0wh5Lq0.js → appshell-Y1AYCuVJ.js} +323 -304
  5. package/dist/semantic-contrast.test.ts +227 -0
  6. package/dist/style-entry.d.ts +61 -26
  7. package/dist/testing.d.ts +66 -29
  8. package/dist/testing.js +1 -1
  9. package/dist/theme-bridge.test.ts +124 -1
  10. package/dist/theme.bridge.css +178 -0
  11. package/dist/theme.css +8 -2
  12. package/dist/themes/_template.css +8 -3
  13. package/dist/themes/bloom.css +0 -8
  14. package/dist/themes/cream.css +0 -8
  15. package/dist/themes/default.css +458 -103
  16. package/dist/vite-plugin.d.ts +61 -26
  17. package/package.json +2 -2
  18. package/skills/app-shell-patterns/SKILL.md +48 -37
  19. package/skills/app-shell-patterns/references/api/define-i18n-labels.md +123 -91
  20. package/skills/app-shell-patterns/references/api/define-module.md +4 -6
  21. package/skills/app-shell-patterns/references/api/define-resource.md +2 -2
  22. package/skills/app-shell-patterns/references/api/use-app-shell-config.md +53 -47
  23. package/skills/app-shell-patterns/references/api/use-app-shell-data.md +30 -26
  24. package/skills/app-shell-patterns/references/api/use-app-shell.md +37 -30
  25. package/skills/app-shell-patterns/references/api/use-toast.md +28 -31
  26. package/skills/app-shell-patterns/references/components/app-shell.md +4 -4
  27. package/skills/app-shell-patterns/references/components/card.md +79 -7
  28. package/skills/app-shell-patterns/references/components/data-table.md +599 -77
  29. package/skills/app-shell-patterns/references/components/date-picker.md +32 -21
  30. package/skills/app-shell-patterns/references/components/filter-rail.md +165 -0
  31. package/skills/app-shell-patterns/references/components/sidebar-group.md +12 -6
  32. package/skills/app-shell-patterns/references/components/sidebar-section.md +114 -0
  33. package/skills/app-shell-patterns/references/components/text.md +198 -0
  34. package/skills/app-shell-patterns/references/concepts/custom-components.md +1 -1
  35. package/skills/app-shell-patterns/references/concepts/file-based-routing.md +7 -1
  36. package/skills/app-shell-patterns/references/concepts/graphql.md +20 -14
  37. package/skills/app-shell-patterns/references/concepts/routing-navigation.md +41 -3
  38. package/skills/app-shell-patterns/references/concepts/sidebar-navigation.md +32 -0
  39. package/skills/app-shell-patterns/references/concepts/styling-theming.md +278 -65
  40. package/skills/app-shell-patterns/references/migrations.md +43 -1
  41. package/skills/app-shell-patterns/references/pages/document-detail.md +6 -6
  42. package/skills/app-shell-patterns/references/patterns/form-modal.md +26 -20
  43. package/skills/app-shell-patterns/references/patterns/i18n-labels.md +139 -0
  44. package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +129 -118
  45. package/skills/app-shell-patterns/references/patterns/list-dense-scan.md +5 -4
@@ -7,24 +7,49 @@ description: Hook to access AppShell configuration data
7
7
 
8
8
  # useAppShellConfig
9
9
 
10
- React hook to access only the AppShell configuration (title, icon, basePath, modules, settingsResources). This is a lighter alternative to `useAppShell` when you only need configuration data.
10
+ React hook to access only the AppShell configuration (title, icon, favicon, appInfo, and the resolved `configurations` object with modules, settingsResources, basePath, locale, and timeZone). This is a lighter alternative to `useAppShell` when you only need configuration data.
11
11
 
12
12
  ## Signature
13
13
 
14
14
  ```typescript
15
- const useAppShellConfig: () => AppShellConfig;
15
+ const useAppShellConfig: () => {
16
+ title?: string;
17
+ icon?: React.ReactNode;
18
+ favicon?: string;
19
+ appInfo?: AppInfo;
20
+ configurations: {
21
+ modules: ReturnType<typeof defineModule>[];
22
+ settingsResources: ReturnType<typeof defineResource>[];
23
+ basePath?: string;
24
+ locale: string;
25
+ resolvedLocale?: string;
26
+ timeZone?: string;
27
+ errorBoundary: ErrorBoundaryComponent;
28
+ };
29
+ };
16
30
  ```
17
31
 
18
32
  ## Return Value
19
33
 
20
- | Property | Type | Description |
21
- | ------------------- | ------------------------------ | ------------------------------------------------ |
22
- | `title` | `string \| undefined` | App title |
23
- | `icon` | `React.ReactNode \| undefined` | App icon |
24
- | `basePath` | `string \| undefined` | Base path for all routes |
25
- | `modules` | `Module[]` | Registered modules |
26
- | `settingsResources` | `Resource[]` | Resources that appear only in Settings menu |
27
- | `appInfo` | `AppInfo \| undefined` | Configuration for the built-in `/__appinfo` page |
34
+ | Property | Type | Description |
35
+ | ---------------- | ------------------------------ | ------------------------------------------------ |
36
+ | `title` | `string \| undefined` | App title |
37
+ | `icon` | `React.ReactNode \| undefined` | App icon |
38
+ | `favicon` | `string \| undefined` | Favicon URL |
39
+ | `appInfo` | `AppInfo \| undefined` | Configuration for the built-in `/__appinfo` page |
40
+ | `configurations` | object | Resolved configuration, see below |
41
+
42
+ ### `configurations`
43
+
44
+ | Property | Type | Description |
45
+ | ------------------- | ------------------------------------- | ----------------------------------------------------- |
46
+ | `modules` | `ReturnType<typeof defineModule>[]` | Registered modules (`Module` itself is not exported) |
47
+ | `settingsResources` | `ReturnType<typeof defineResource>[]` | Resources that appear only in Settings menu |
48
+ | `basePath` | `string \| undefined` | Base path for all routes |
49
+ | `locale` | `string` | Language subtag used for built-in UI strings (`"en"`) |
50
+ | `resolvedLocale` | `string \| undefined` | Full BCP-47 tag used for Intl / date formatting |
51
+ | `timeZone` | `string \| undefined` | IANA timezone used by date/time components |
52
+ | `errorBoundary` | `ErrorBoundaryComponent` | Global error boundary element |
28
53
 
29
54
  ## Usage
30
55
 
@@ -34,12 +59,12 @@ const useAppShellConfig: () => AppShellConfig;
34
59
  import { useAppShellConfig } from "@tailor-platform/app-shell";
35
60
 
36
61
  function AppInfo() {
37
- const { title, basePath } = useAppShellConfig();
62
+ const { title, configurations } = useAppShellConfig();
38
63
 
39
64
  return (
40
65
  <div>
41
66
  <h2>{title}</h2>
42
- <p>Base: {basePath}</p>
67
+ <p>Base: {configurations.basePath}</p>
43
68
  </div>
44
69
  );
45
70
  }
@@ -47,51 +72,33 @@ function AppInfo() {
47
72
 
48
73
  ### Access Modules
49
74
 
75
+ Module titles are `LocalizedString` values (a string or a `(locale) => string` function), so the example lists `path` instead of rendering `meta.title` directly.
76
+
50
77
  ```typescript
51
78
  function ModuleList() {
52
- const { modules } = useAppShellConfig();
79
+ const { configurations } = useAppShellConfig();
53
80
 
54
81
  return (
55
82
  <ul>
56
- {modules.map((module) => (
57
- <li key={module.path}>{module.meta?.title}</li>
83
+ {configurations.modules.map((module) => (
84
+ <li key={module.path}>{module.path}</li>
58
85
  ))}
59
86
  </ul>
60
87
  );
61
88
  }
62
89
  ```
63
90
 
64
- ### Build Custom Navigation
65
-
66
- ```typescript
67
- function CustomNav() {
68
- const { modules } = useAppShellConfig();
69
-
70
- return (
71
- <nav>
72
- {modules.map((module) => (
73
- <a key={module.path} href={`/${module.path}`}>
74
- {module.meta?.title}
75
- </a>
76
- ))}
77
- </nav>
78
- );
79
- }
80
- ```
81
-
82
91
  ### Settings Resources
83
92
 
84
93
  ```typescript
85
94
  function SettingsMenu() {
86
- const { settingsResources } = useAppShellConfig();
95
+ const { configurations } = useAppShellConfig();
87
96
 
88
97
  return (
89
98
  <div>
90
99
  <h3>Settings</h3>
91
- {settingsResources.map((resource) => (
92
- <button key={resource.path}>
93
- {resource.meta?.title}
94
- </button>
100
+ {configurations.settingsResources.map((resource) => (
101
+ <button key={resource.path}>{resource.path}</button>
95
102
  ))}
96
103
  </div>
97
104
  );
@@ -103,21 +110,20 @@ function SettingsMenu() {
103
110
  Use `useAppShellConfig` when you only need configuration data:
104
111
 
105
112
  ```typescript
106
- // ✅ Good - only need config
107
- const { modules } = useAppShellConfig();
113
+ // ✅ Good - subscribes to configuration only
114
+ const { configurations } = useAppShellConfig();
108
115
 
109
- // ❌ Overkill - useAppShell returns more data
116
+ // ❌ Overkill - useAppShell also subscribes to contextData changes
110
117
  const { configurations } = useAppShell();
111
- const modules = configurations.modules;
112
118
  ```
113
119
 
114
120
  ## Comparison with Other Hooks
115
121
 
116
- | Hook | Returns | Use When |
117
- | ------------------- | --------------------- | ------------------- |
118
- | `useAppShellConfig` | Configuration only | Need config data |
119
- | `useAppShellData` | Context data only | Need custom context |
120
- | `useAppShell` | Both config + context | Need both |
122
+ | Hook | Returns | Use When |
123
+ | ------------------- | --------------------------------------------------- | ------------------- |
124
+ | `useAppShellConfig` | `{ title, icon, favicon, appInfo, configurations }` | Need config data |
125
+ | `useAppShellData` | `{ contextData }` | Need custom context |
126
+ | `useAppShell` | Both, merged into one object | Need both |
121
127
 
122
128
  ## Related
123
129
 
@@ -12,14 +12,16 @@ React hook to access only the custom context data. This is a lighter alternative
12
12
  ## Signature
13
13
 
14
14
  ```typescript
15
- const useAppShellData: () => ContextData;
15
+ const useAppShellData: () => {
16
+ contextData: ContextData;
17
+ };
16
18
  ```
17
19
 
18
20
  ## Return Value
19
21
 
20
- | Type | Description |
21
- | ------------- | ------------------------------------------------------------------------- |
22
- | `ContextData` | Custom context data passed to AppShell (type-safe via `AppShellRegister`) |
22
+ | Property | Type | Description |
23
+ | ------------- | ------------- | ------------------------------------------------------------------------- |
24
+ | `contextData` | `ContextData` | Custom context data passed to AppShell (type-safe via `AppShellRegister`) |
23
25
 
24
26
  ## Usage
25
27
 
@@ -29,7 +31,7 @@ const useAppShellData: () => ContextData;
29
31
  import { useAppShellData } from "@tailor-platform/app-shell";
30
32
 
31
33
  function UserProfile() {
32
- const contextData = useAppShellData();
34
+ const { contextData } = useAppShellData();
33
35
 
34
36
  return (
35
37
  <div>
@@ -44,16 +46,16 @@ function UserProfile() {
44
46
 
45
47
  ```typescript
46
48
  function UserInfo() {
47
- const data = useAppShellData();
49
+ const { contextData } = useAppShellData();
48
50
 
49
- if (!data.currentUser) {
51
+ if (!contextData.currentUser) {
50
52
  return <div>Not logged in</div>;
51
53
  }
52
54
 
53
55
  return (
54
56
  <div>
55
- <h2>{data.currentUser.name}</h2>
56
- <p>{data.currentUser.email}</p>
57
+ <h2>{contextData.currentUser.name}</h2>
58
+ <p>{contextData.currentUser.email}</p>
57
59
  </div>
58
60
  );
59
61
  }
@@ -63,8 +65,8 @@ function UserInfo() {
63
65
 
64
66
  ```typescript
65
67
  function AdminPanel() {
66
- const { permissions } = useAppShellData();
67
- const canManageUsers = permissions.includes("users:manage");
68
+ const { contextData } = useAppShellData();
69
+ const canManageUsers = contextData.permissions.includes("users:manage");
68
70
 
69
71
  if (!canManageUsers) {
70
72
  return <AccessDenied />;
@@ -78,9 +80,9 @@ function AdminPanel() {
78
80
 
79
81
  ```typescript
80
82
  function BetaFeature() {
81
- const { featureFlags } = useAppShellData();
83
+ const { contextData } = useAppShellData();
82
84
 
83
- if (!featureFlags?.newEditor) {
85
+ if (!contextData.featureFlags?.newEditor) {
84
86
  return <LegacyEditor />;
85
87
  }
86
88
 
@@ -94,6 +96,8 @@ Define your context type with module augmentation:
94
96
 
95
97
  ```typescript
96
98
  // types.d.ts
99
+ type User = { name: string; email: string; role: string };
100
+
97
101
  declare module "@tailor-platform/app-shell" {
98
102
  interface AppShellRegister {
99
103
  contextData: {
@@ -112,10 +116,10 @@ declare module "@tailor-platform/app-shell" {
112
116
  Now `useAppShellData()` returns fully typed data:
113
117
 
114
118
  ```typescript
115
- const data = useAppShellData();
116
- data.currentUser; // Type: User | null
117
- data.permissions; // Type: string[]
118
- data.featureFlags; // Type: { newEditor: boolean, betaDashboard: boolean }
119
+ const { contextData } = useAppShellData();
120
+ contextData.currentUser; // Type: User | null
121
+ contextData.permissions; // Type: string[]
122
+ contextData.featureFlags; // Type: { newEditor: boolean, betaDashboard: boolean }
119
123
  ```
120
124
 
121
125
  ## When to Use
@@ -123,20 +127,20 @@ data.featureFlags; // Type: { newEditor: boolean, betaDashboard: boolean }
123
127
  Use `useAppShellData` when you only need context data:
124
128
 
125
129
  ```typescript
126
- // ✅ Good - only need context
127
- const data = useAppShellData();
130
+ // ✅ Good - subscribes to context data only
131
+ const { contextData } = useAppShellData();
128
132
 
129
- // ❌ Overkill - useAppShell returns more than needed
130
- const { context } = useAppShell();
133
+ // ❌ Overkill - useAppShell also subscribes to configuration changes
134
+ const { contextData } = useAppShell();
131
135
  ```
132
136
 
133
137
  ## Comparison with Other Hooks
134
138
 
135
- | Hook | Returns | Use When |
136
- | ------------------- | --------------------- | ------------------- |
137
- | `useAppShellData` | Context data only | Need custom context |
138
- | `useAppShellConfig` | Configuration only | Need config data |
139
- | `useAppShell` | Both config + context | Need both |
139
+ | Hook | Returns | Use When |
140
+ | ------------------- | --------------------------------------------------- | ------------------- |
141
+ | `useAppShellData` | `{ contextData }` | Need custom context |
142
+ | `useAppShellConfig` | `{ title, icon, favicon, appInfo, configurations }` | Need config data |
143
+ | `useAppShell` | Both, merged into one object | Need both |
140
144
 
141
145
  ## Related
142
146
 
@@ -13,23 +13,40 @@ React hook to access AppShell context data and configuration within your compone
13
13
 
14
14
  ```typescript
15
15
  const useAppShell: () => {
16
- context: ContextData;
17
16
  contextData: ContextData;
18
- configurations: AppShellConfig;
17
+ configurations: {
18
+ modules: ReturnType<typeof defineModule>[];
19
+ settingsResources: ReturnType<typeof defineResource>[];
20
+ basePath?: string;
21
+ locale: string;
22
+ resolvedLocale?: string;
23
+ timeZone?: string;
24
+ errorBoundary: ErrorBoundaryComponent;
25
+ };
26
+ title?: string;
27
+ icon?: React.ReactNode;
28
+ favicon?: string;
29
+ appInfo?: AppInfo;
19
30
  };
20
31
  ```
21
32
 
33
+ The hook merges the results of [`useAppShellConfig`](./use-app-shell-config.md) and [`useAppShellData`](./use-app-shell-data.md). Prefer one of those when you only need configuration or only need context data, so the component does not re-render for changes it does not use.
34
+
22
35
  ## Return Value
23
36
 
24
- ### `context` / `contextData`
37
+ ### `contextData`
25
38
 
26
39
  - **Type:** `ContextData`
27
40
  - **Description:** Custom context data passed to AppShell. Same as the `contextData` prop.
28
41
 
29
42
  ### `configurations`
30
43
 
31
- - **Type:** `AppShellConfig`
32
- - **Description:** AppShell configuration (locale, basePath, etc.)
44
+ - **Type:** object
45
+ - **Description:** Resolved AppShell configuration: `modules`, `settingsResources`, `basePath`, `locale`, `resolvedLocale`, `timeZone`, and `errorBoundary`.
46
+
47
+ ### `title`, `icon`, `favicon`, `appInfo`
48
+
49
+ - **Description:** The matching AppShell props, as passed.
33
50
 
34
51
  ## Usage
35
52
 
@@ -39,9 +56,9 @@ const useAppShell: () => {
39
56
  import { useAppShell } from "@tailor-platform/app-shell";
40
57
 
41
58
  function MyComponent() {
42
- const { context } = useAppShell();
59
+ const { contextData } = useAppShell();
43
60
 
44
- return <div>Welcome, {context.currentUser?.name}</div>;
61
+ return <div>Welcome, {contextData.currentUser?.name}</div>;
45
62
  }
46
63
  ```
47
64
 
@@ -61,6 +78,8 @@ Define your context type with module augmentation:
61
78
 
62
79
  ```typescript
63
80
  // types.d.ts
81
+ type User = { name: string; email: string; role: string };
82
+
64
83
  declare module "@tailor-platform/app-shell" {
65
84
  interface AppShellRegister {
66
85
  contextData: {
@@ -82,9 +101,9 @@ declare module "@tailor-platform/app-shell" {
82
101
  />
83
102
 
84
103
  // MyComponent.tsx
85
- const { context } = useAppShell();
86
- context.currentUser // Fully typed!
87
- context.permissions // Fully typed!
104
+ const { contextData } = useAppShell();
105
+ contextData.currentUser // Fully typed!
106
+ contextData.permissions // Fully typed!
88
107
  ```
89
108
 
90
109
  ## Examples
@@ -93,16 +112,16 @@ context.permissions // Fully typed!
93
112
 
94
113
  ```typescript
95
114
  function UserInfo() {
96
- const { context } = useAppShell();
115
+ const { contextData } = useAppShell();
97
116
 
98
- if (!context.currentUser) {
117
+ if (!contextData.currentUser) {
99
118
  return <div>Not logged in</div>;
100
119
  }
101
120
 
102
121
  return (
103
122
  <div>
104
- <h2>{context.currentUser.name}</h2>
105
- <p>{context.currentUser.email}</p>
123
+ <h2>{contextData.currentUser.name}</h2>
124
+ <p>{contextData.currentUser.email}</p>
106
125
  </div>
107
126
  );
108
127
  }
@@ -112,8 +131,8 @@ function UserInfo() {
112
131
 
113
132
  ```typescript
114
133
  function DeleteButton() {
115
- const { context } = useAppShell();
116
- const canDelete = context.permissions.includes("users:delete");
134
+ const { contextData } = useAppShell();
135
+ const canDelete = contextData.permissions.includes("users:delete");
117
136
 
118
137
  if (!canDelete) {
119
138
  return null;
@@ -123,21 +142,9 @@ function DeleteButton() {
123
142
  }
124
143
  ```
125
144
 
126
- ### Feature Flags
127
-
128
- ```typescript
129
- function BetaFeature() {
130
- const { context } = useAppShell();
131
-
132
- if (!context.featureFlags?.newDashboard) {
133
- return <OldDashboard />;
134
- }
135
-
136
- return <NewDashboard />;
137
- }
138
- ```
139
-
140
145
  ## Related
141
146
 
142
147
  - [AppShell Component](../components/app-shell.md) - Root component
148
+ - [useAppShellData](./use-app-shell-data.md) - Context data only
149
+ - [useAppShellConfig](./use-app-shell-config.md) - Configuration only
143
150
  - [Guards Overview](./guards/overview.md) - Access control using context
@@ -7,26 +7,36 @@ description: Hook for displaying toast notifications
7
7
 
8
8
  # useToast
9
9
 
10
- React hook to display toast notifications for user feedback.
10
+ React hook to display toast notifications for user feedback. It returns the `toast` function exported by [sonner](https://sonner.emilkowal.ski/toast). The built-in layouts (`SidebarLayout`, `GlobalHeaderLayout`) already render the toaster, so no provider setup is needed.
11
11
 
12
12
  ## Signature
13
13
 
14
14
  ```typescript
15
- const useToast: () => {
16
- toast: (props: ToastProps) => void;
17
- };
15
+ const useToast: () => typeof toast; // `toast` is sonner's export; use `ReturnType<typeof useToast>` to name it
16
+
17
+ // The returned function (abbreviated; see sonner for the full signature)
18
+ toast(message: React.ReactNode, options?: ExternalToast): string | number;
19
+ toast.success(message, options?);
20
+ toast.error(message, options?);
21
+ toast.info(message, options?);
22
+ toast.warning(message, options?);
23
+ toast.loading(message, options?);
24
+ toast.promise(promise, { loading, success, error });
25
+ toast.dismiss(id?);
18
26
  ```
19
27
 
20
28
  ## Parameters
21
29
 
22
- ### ToastProps
30
+ ### ExternalToast
31
+
32
+ The options type of sonner's `toast`. Inside an app that depends only on `@tailor-platform/app-shell`, name it as `Parameters<ReturnType<typeof useToast>>[1]` (importing it from `sonner` requires sonner as a direct dependency). The most common fields:
23
33
 
24
34
  ```typescript
25
35
  {
26
- title?: string;
27
- description?: string;
28
- variant?: "default" | "success" | "error" | "warning";
36
+ description?: React.ReactNode;
29
37
  duration?: number; // milliseconds
38
+ action?: { label: React.ReactNode; onClick: (event: React.MouseEvent) => void };
39
+ id?: string | number;
30
40
  }
31
41
  ```
32
42
 
@@ -38,14 +48,11 @@ const useToast: () => {
38
48
  import { useToast } from "@tailor-platform/app-shell";
39
49
 
40
50
  function SaveButton() {
41
- const { toast } = useToast();
51
+ const toast = useToast();
42
52
 
43
53
  const handleSave = () => {
44
54
  // Save logic...
45
- toast({
46
- title: "Saved!",
47
- description: "Your changes have been saved.",
48
- });
55
+ toast("Saved", { description: "Your changes have been saved." });
49
56
  };
50
57
 
51
58
  return <button onClick={handleSave}>Save</button>;
@@ -56,15 +63,11 @@ function SaveButton() {
56
63
 
57
64
  ```typescript
58
65
  function CreateButton() {
59
- const { toast } = useToast();
66
+ const toast = useToast();
60
67
 
61
68
  const handleCreate = async () => {
62
- await createItem();
63
- toast({
64
- title: "Created",
65
- description: "Item created successfully.",
66
- variant: "success",
67
- });
69
+ const item = await createItem();
70
+ toast.success(`Item ${item.id} created`);
68
71
  };
69
72
 
70
73
  return <button onClick={handleCreate}>Create</button>;
@@ -75,17 +78,13 @@ function CreateButton() {
75
78
 
76
79
  ```typescript
77
80
  function DeleteButton() {
78
- const { toast } = useToast();
81
+ const toast = useToast();
79
82
 
80
83
  const handleDelete = async () => {
81
84
  try {
82
85
  await deleteItem();
83
86
  } catch (error) {
84
- toast({
85
- title: "Error",
86
- description: error.message,
87
- variant: "error",
88
- });
87
+ toast.error(`Failed to delete: ${(error as Error).message}`);
89
88
  }
90
89
  };
91
90
 
@@ -96,12 +95,10 @@ function DeleteButton() {
96
95
  ### Custom Duration
97
96
 
98
97
  ```typescript
99
- toast({
100
- title: "Processing...",
101
- duration: 5000, // 5 seconds
102
- });
98
+ toast("Processing...", { duration: 5000 }); // 5 seconds
103
99
  ```
104
100
 
105
101
  ## Related
106
102
 
107
- - [SidebarLayout](../components/sidebar-layout.md) - Toast provider included
103
+ - [Toast pattern](../patterns/interaction-toast.md) - When to use a toast and how to word it
104
+ - [SidebarLayout](../components/sidebar-layout.md) - Renders the toaster
@@ -259,7 +259,7 @@ const GlobalErrorBoundary = () => {
259
259
  First, define your context data type using module augmentation:
260
260
 
261
261
  ```typescript
262
- // types.d.ts
262
+ // types.d.ts — `User` and `ApiClient` are your application's own types
263
263
  declare module "@tailor-platform/app-shell" {
264
264
  interface AppShellRegister {
265
265
  contextData: {
@@ -293,11 +293,11 @@ Access the context data in your components:
293
293
  import { useAppShell } from "@tailor-platform/app-shell";
294
294
 
295
295
  function MyComponent() {
296
- const { context } = useAppShell();
296
+ const { contextData } = useAppShell();
297
297
 
298
298
  // Fully typed!
299
- const user = context.currentUser;
300
- const client = context.apiClient;
299
+ const user = contextData.currentUser;
300
+ const client = contextData.apiClient;
301
301
 
302
302
  return <div>Welcome, {user?.name}</div>;
303
303
  }
@@ -38,7 +38,7 @@ function BasicUsage() {
38
38
  | -------------- | ---------------------------------------------------------------- |
39
39
  | `Card.Root` | Outer container. Renders a `<div>` with surface styling applied. |
40
40
  | `Card.Header` | Top section with an optional title and description. |
41
- | `Card.Content` | Body section with consistent horizontal and bottom padding. |
41
+ | `Card.Content` | Body section with consistent padding, or edge-to-edge. |
42
42
 
43
43
  ## Card.Root Props
44
44
 
@@ -62,10 +62,82 @@ function BasicUsage() {
62
62
 
63
63
  `Card.Content` accepts all standard HTML `<div>` props.
64
64
 
65
- | Prop | Type | Default | Description |
66
- | ----------- | ----------------- | ------- | ---------------------- |
67
- | `className` | `string` | - | Additional CSS classes |
68
- | `children` | `React.ReactNode` | - | Card body content |
65
+ | Prop | Type | Default | Description |
66
+ | ----------- | ----------------------- | ----------- | ---------------------------------------------------------------------------------------------- |
67
+ | `padding` | `"default"` \| `"none"` | `"default"` | `"none"` renders the content edge-to-edge — see [Edge-to-edge content](#edge-to-edge-content). |
68
+ | `className` | `string` | - | Additional CSS classes |
69
+ | `children` | `React.ReactNode` | - | Card body content |
70
+
71
+ ## Edge-to-edge content
72
+
73
+ Use `padding="none"` when a `Table`, `DataTable` or divided list should meet the card's border instead of floating inside it:
74
+
75
+ <example-preview name="edge-to-edge-table"></example-preview>
76
+
77
+ ```tsx
78
+ function EdgeToEdgeTable() {
79
+ return (
80
+ <Card.Root>
81
+ <Card.Header title="Line items" />
82
+ <Card.Content padding="none">
83
+ <Table.Root>
84
+ <Table.Header>
85
+ <Table.Row>
86
+ <Table.Head>Item</Table.Head>
87
+ <Table.Head align="right">Qty</Table.Head>
88
+ </Table.Row>
89
+ </Table.Header>
90
+ <Table.Body>
91
+ <Table.Row>
92
+ <Table.Cell>Steel bracket</Table.Cell>
93
+ <Table.Cell align="right">12</Table.Cell>
94
+ </Table.Row>
95
+ <Table.Row>
96
+ <Table.Cell>Hex bolt M8</Table.Cell>
97
+ <Table.Cell align="right">40</Table.Cell>
98
+ </Table.Row>
99
+ </Table.Body>
100
+ </Table.Root>
101
+ </Card.Content>
102
+ </Card.Root>
103
+ );
104
+ }
105
+ ```
106
+
107
+ Don't add padding back on the table. `Table.Head` and `Table.Cell` already inset their first and last cells by 24px, which lines the first column up with the card title.
108
+
109
+ Edge-to-edge content also:
110
+
111
+ - **Keeps row backgrounds inside the card's rounded corners.** A direct `Table` or `DataTable` child clips its row hover and pinned-cell backgrounds to the card's corners.
112
+ - **Removes a nested `DataTable`'s own border**, so it doesn't draw a box inside the card. Its toolbar and footer stay as they are:
113
+
114
+ ```tsx
115
+ <Card.Root>
116
+ <Card.Header title="Line items" />
117
+ <Card.Content padding="none">
118
+ <DataTable.Root value={table}>
119
+ <DataTable.Table />
120
+ <DataTable.Footer>
121
+ <DataTable.Pagination />
122
+ </DataTable.Footer>
123
+ </DataTable.Root>
124
+ </Card.Content>
125
+ </Card.Root>
126
+ ```
127
+
128
+ For a scrollable list, put the overflow on `Card.Content`. Rows supply their own `px-6`, and dividers run to the card edges:
129
+
130
+ ```tsx
131
+ <Card.Content padding="none" className="max-h-60 overflow-y-auto">
132
+ <ul className="divide-y divide-border border-t border-border">
133
+ {items.map((item) => (
134
+ <li key={item.id} className="px-6 py-3">
135
+ {item.name}
136
+ </li>
137
+ ))}
138
+ </ul>
139
+ </Card.Content>
140
+ ```
69
141
 
70
142
  ## Examples
71
143
 
@@ -96,8 +168,8 @@ function BasicUsage() {
96
168
  View all
97
169
  </Button>
98
170
  </Card.Header>
99
- <Card.Content>
100
- <Table>{/* ... */}</Table>
171
+ <Card.Content padding="none">
172
+ <Table.Root>{/* ... */}</Table.Root>
101
173
  </Card.Content>
102
174
  </Card.Root>
103
175
  ```