@tailor-platform/app-shell 1.16.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.
- package/dist/app-shell-core.css +1 -1
- package/dist/app-shell.d.ts +692 -106
- package/dist/app-shell.js +6294 -4939
- package/dist/{appshell-B0wh5Lq0.js → appshell-CahhZy7v.js} +316 -297
- package/dist/semantic-contrast.test.ts +154 -0
- package/dist/style-entry.d.ts +117 -82
- package/dist/testing.d.ts +122 -85
- package/dist/testing.js +1 -1
- package/dist/theme-bridge.test.ts +101 -1
- package/dist/theme.bridge.css +91 -0
- package/dist/theme.css +6 -1
- package/dist/themes/_template.css +7 -1
- package/dist/themes/bloom.css +0 -6
- package/dist/themes/cream.css +0 -6
- package/dist/themes/default.css +389 -69
- package/dist/vite-plugin.d.ts +117 -82
- package/package.json +1 -1
- package/skills/app-shell-patterns/SKILL.md +7 -5
- package/skills/app-shell-patterns/references/api/define-module.md +2 -2
- package/skills/app-shell-patterns/references/api/define-resource.md +2 -2
- package/skills/app-shell-patterns/references/api/use-app-shell-config.md +53 -47
- package/skills/app-shell-patterns/references/api/use-app-shell-data.md +30 -26
- package/skills/app-shell-patterns/references/api/use-app-shell.md +37 -30
- package/skills/app-shell-patterns/references/api/use-toast.md +28 -31
- package/skills/app-shell-patterns/references/components/app-shell.md +4 -4
- package/skills/app-shell-patterns/references/components/card.md +79 -7
- package/skills/app-shell-patterns/references/components/data-table.md +267 -52
- package/skills/app-shell-patterns/references/components/date-picker.md +32 -21
- package/skills/app-shell-patterns/references/components/filter-rail.md +165 -0
- package/skills/app-shell-patterns/references/components/sidebar-group.md +6 -0
- package/skills/app-shell-patterns/references/components/sidebar-section.md +114 -0
- package/skills/app-shell-patterns/references/concepts/custom-components.md +1 -1
- package/skills/app-shell-patterns/references/concepts/file-based-routing.md +7 -1
- package/skills/app-shell-patterns/references/concepts/graphql.md +20 -14
- package/skills/app-shell-patterns/references/concepts/routing-navigation.md +41 -3
- package/skills/app-shell-patterns/references/concepts/sidebar-navigation.md +32 -0
- package/skills/app-shell-patterns/references/concepts/styling-theming.md +255 -58
- package/skills/app-shell-patterns/references/migrations.md +21 -0
- package/skills/app-shell-patterns/references/pages/document-detail.md +6 -6
- package/skills/app-shell-patterns/references/patterns/form-modal.md +26 -20
- package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +129 -118
- package/skills/app-shell-patterns/references/patterns/list-dense-scan.md +2 -2
|
@@ -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,
|
|
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: () =>
|
|
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
|
|
21
|
-
|
|
|
22
|
-
| `title`
|
|
23
|
-
| `icon`
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
|
|
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,
|
|
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 {
|
|
79
|
+
const { configurations } = useAppShellConfig();
|
|
53
80
|
|
|
54
81
|
return (
|
|
55
82
|
<ul>
|
|
56
|
-
{modules.map((module) => (
|
|
57
|
-
<li key={module.path}>{module.
|
|
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 {
|
|
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 -
|
|
107
|
-
const {
|
|
113
|
+
// ✅ Good - subscribes to configuration only
|
|
114
|
+
const { configurations } = useAppShellConfig();
|
|
108
115
|
|
|
109
|
-
// ❌ Overkill - useAppShell
|
|
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
|
|
117
|
-
| ------------------- |
|
|
118
|
-
| `useAppShellConfig` |
|
|
119
|
-
| `useAppShellData` |
|
|
120
|
-
| `useAppShell` | 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: () =>
|
|
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
|
|
49
|
+
const { contextData } = useAppShellData();
|
|
48
50
|
|
|
49
|
-
if (!
|
|
51
|
+
if (!contextData.currentUser) {
|
|
50
52
|
return <div>Not logged in</div>;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
return (
|
|
54
56
|
<div>
|
|
55
|
-
<h2>{
|
|
56
|
-
<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 {
|
|
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 {
|
|
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
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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 -
|
|
127
|
-
const
|
|
130
|
+
// ✅ Good - subscribes to context data only
|
|
131
|
+
const { contextData } = useAppShellData();
|
|
128
132
|
|
|
129
|
-
// ❌ Overkill - useAppShell
|
|
130
|
-
const {
|
|
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
|
|
136
|
-
| ------------------- |
|
|
137
|
-
| `useAppShellData` |
|
|
138
|
-
| `useAppShellConfig` |
|
|
139
|
-
| `useAppShell` | 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:
|
|
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
|
-
### `
|
|
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:**
|
|
32
|
-
- **Description:** AppShell configuration
|
|
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 {
|
|
59
|
+
const { contextData } = useAppShell();
|
|
43
60
|
|
|
44
|
-
return <div>Welcome, {
|
|
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 {
|
|
86
|
-
|
|
87
|
-
|
|
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 {
|
|
115
|
+
const { contextData } = useAppShell();
|
|
97
116
|
|
|
98
|
-
if (!
|
|
117
|
+
if (!contextData.currentUser) {
|
|
99
118
|
return <div>Not logged in</div>;
|
|
100
119
|
}
|
|
101
120
|
|
|
102
121
|
return (
|
|
103
122
|
<div>
|
|
104
|
-
<h2>{
|
|
105
|
-
<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 {
|
|
116
|
-
const canDelete =
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
- [
|
|
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 {
|
|
296
|
+
const { contextData } = useAppShell();
|
|
297
297
|
|
|
298
298
|
// Fully typed!
|
|
299
|
-
const user =
|
|
300
|
-
const client =
|
|
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
|
|
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
|
|
66
|
-
| ----------- |
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
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
|
```
|