@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.
- package/dist/app-shell-core.css +1 -1
- package/dist/app-shell.d.ts +762 -51
- package/dist/app-shell.js +7642 -5636
- package/dist/{appshell-B0wh5Lq0.js → appshell-Y1AYCuVJ.js} +323 -304
- package/dist/semantic-contrast.test.ts +227 -0
- package/dist/style-entry.d.ts +61 -26
- package/dist/testing.d.ts +66 -29
- package/dist/testing.js +1 -1
- package/dist/theme-bridge.test.ts +124 -1
- package/dist/theme.bridge.css +178 -0
- package/dist/theme.css +8 -2
- package/dist/themes/_template.css +8 -3
- package/dist/themes/bloom.css +0 -8
- package/dist/themes/cream.css +0 -8
- package/dist/themes/default.css +458 -103
- package/dist/vite-plugin.d.ts +61 -26
- package/package.json +2 -2
- package/skills/app-shell-patterns/SKILL.md +48 -37
- package/skills/app-shell-patterns/references/api/define-i18n-labels.md +123 -91
- package/skills/app-shell-patterns/references/api/define-module.md +4 -6
- 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 +599 -77
- 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 +12 -6
- package/skills/app-shell-patterns/references/components/sidebar-section.md +114 -0
- package/skills/app-shell-patterns/references/components/text.md +198 -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 +278 -65
- package/skills/app-shell-patterns/references/migrations.md +43 -1
- 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/i18n-labels.md +139 -0
- package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +129 -118
- package/skills/app-shell-patterns/references/patterns/list-dense-scan.md +5 -4
package/dist/vite-plugin.d.ts
CHANGED
|
@@ -41,12 +41,12 @@ declare namespace Root {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
declare namespace
|
|
44
|
+
declare namespace Image {
|
|
45
45
|
var displayName: string;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
declare namespace
|
|
49
|
+
declare namespace Fallback {
|
|
50
50
|
var displayName: string;
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -56,17 +56,17 @@ declare namespace Root {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
declare namespace
|
|
59
|
+
declare namespace Title {
|
|
60
60
|
var displayName: string;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
declare namespace
|
|
64
|
+
declare namespace Description {
|
|
65
65
|
var displayName: string;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
declare namespace
|
|
69
|
+
declare namespace Provider {
|
|
70
70
|
var displayName: string;
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -76,12 +76,27 @@ declare namespace Root {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
declare namespace
|
|
79
|
+
declare namespace Header {
|
|
80
80
|
var displayName: string;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
declare namespace
|
|
84
|
+
declare namespace ClearAll {
|
|
85
|
+
var displayName: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
declare namespace Sections {
|
|
90
|
+
var displayName: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
declare namespace Trigger {
|
|
95
|
+
var displayName: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
declare namespace Settings {
|
|
85
100
|
var displayName: string;
|
|
86
101
|
}
|
|
87
102
|
|
|
@@ -96,47 +111,47 @@ declare namespace Header {
|
|
|
96
111
|
}
|
|
97
112
|
|
|
98
113
|
|
|
99
|
-
declare namespace
|
|
114
|
+
declare namespace Content {
|
|
100
115
|
var displayName: string;
|
|
101
116
|
}
|
|
102
117
|
|
|
103
118
|
|
|
104
|
-
declare namespace
|
|
119
|
+
declare namespace Root {
|
|
105
120
|
var displayName: string;
|
|
106
121
|
}
|
|
107
122
|
|
|
108
123
|
|
|
109
|
-
declare namespace
|
|
124
|
+
declare namespace Header {
|
|
110
125
|
var displayName: string;
|
|
111
126
|
}
|
|
112
127
|
|
|
113
128
|
|
|
114
|
-
declare namespace
|
|
129
|
+
declare namespace Body {
|
|
115
130
|
var displayName: string;
|
|
116
131
|
}
|
|
117
132
|
|
|
118
133
|
|
|
119
|
-
declare namespace
|
|
134
|
+
declare namespace Footer {
|
|
120
135
|
var displayName: string;
|
|
121
136
|
}
|
|
122
137
|
|
|
123
138
|
|
|
124
|
-
declare namespace
|
|
139
|
+
declare namespace Row {
|
|
125
140
|
var displayName: string;
|
|
126
141
|
}
|
|
127
142
|
|
|
128
143
|
|
|
129
|
-
declare namespace
|
|
144
|
+
declare namespace Head {
|
|
130
145
|
var displayName: string;
|
|
131
146
|
}
|
|
132
147
|
|
|
133
148
|
|
|
134
|
-
declare namespace
|
|
149
|
+
declare namespace Cell {
|
|
135
150
|
var displayName: string;
|
|
136
151
|
}
|
|
137
152
|
|
|
138
153
|
|
|
139
|
-
declare namespace
|
|
154
|
+
declare namespace Caption {
|
|
140
155
|
var displayName: string;
|
|
141
156
|
}
|
|
142
157
|
|
|
@@ -341,6 +356,26 @@ declare namespace Root {
|
|
|
341
356
|
}
|
|
342
357
|
|
|
343
358
|
|
|
359
|
+
declare namespace List {
|
|
360
|
+
var displayName: string;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
declare namespace Tab {
|
|
365
|
+
var displayName: string;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
declare namespace Panel {
|
|
370
|
+
var displayName: string;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
declare namespace Root {
|
|
375
|
+
var displayName: string;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
|
|
344
379
|
declare namespace Row {
|
|
345
380
|
var displayName: string;
|
|
346
381
|
}
|
|
@@ -501,16 +536,6 @@ declare namespace ComboboxCollection {
|
|
|
501
536
|
}
|
|
502
537
|
|
|
503
538
|
|
|
504
|
-
declare namespace AutocompleteStandalone {
|
|
505
|
-
var displayName: string;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
declare namespace AutocompleteAsyncStandalone {
|
|
510
|
-
var displayName: string;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
|
|
514
539
|
declare namespace AutocompleteRoot {
|
|
515
540
|
var displayName: string;
|
|
516
541
|
}
|
|
@@ -581,6 +606,16 @@ declare namespace AutocompleteStatus {
|
|
|
581
606
|
}
|
|
582
607
|
|
|
583
608
|
|
|
609
|
+
declare namespace AutocompleteStandalone {
|
|
610
|
+
var displayName: string;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
declare namespace AutocompleteAsyncStandalone {
|
|
615
|
+
var displayName: string;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
|
|
584
619
|
declare namespace DataTableRoot {
|
|
585
620
|
var displayName: string;
|
|
586
621
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tailor-platform/app-shell",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"description": "An opinionated React application framework for building ERP applications on Tailor Platform",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"app-shell",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"@vitejs/plugin-react": "^6.1.0",
|
|
78
78
|
"@vitest/coverage-v8": "4.1.11",
|
|
79
79
|
"happy-dom": "20.14.5",
|
|
80
|
-
"postcss": "8.5.
|
|
80
|
+
"postcss": "8.5.30",
|
|
81
81
|
"react": "^19.2.7",
|
|
82
82
|
"react-dom": "^19.2.7",
|
|
83
83
|
"react-hook-form": "7.71.2",
|
|
@@ -59,6 +59,7 @@ Every AppShell component, its API surface, and usage guidance. Read the entry fo
|
|
|
59
59
|
| [DescriptionCard](references/components/description-card.md) | Display structured key-value information in a responsive grid layout with automatic field rendering |
|
|
60
60
|
| [Dialog](references/components/dialog.md) | Modal dialog with a compound component API |
|
|
61
61
|
| [DocumentProgressCard](references/components/document-progress-card.md) | Generic card visualising a document's lifecycle state — optional percentage, stacked progress bar, and status legend |
|
|
62
|
+
| [FilterRail](references/components/filter-rail.md) | An always-visible faceted filter rail with user layout settings, driven by the same CollectionControl as DataTable |
|
|
62
63
|
| [Form, Field, and Fieldset](references/components/form.md) | Components for building validated forms with automatic accessibility wiring, validation state management, and optional React Hook Form integration |
|
|
63
64
|
| [GlobalHeaderLayout](references/components/global-header-layout.md) | Opinionated layout with an app-wide header above the whole shell and a sidebar that collapses to a persistent icon rail |
|
|
64
65
|
| [Grid](references/components/grid.md) | Generic CSS-Grid layout primitive for equal or custom-width columns with responsive reflow, auto-fit, and spanning |
|
|
@@ -71,9 +72,11 @@ Every AppShell component, its API surface, and usage guidance. Read the entry fo
|
|
|
71
72
|
| [SidebarGroup](references/components/sidebar-group.md) | Collapsible group component for organizing sidebar navigation items with icons and nested structure |
|
|
72
73
|
| [SidebarItem](references/components/sidebar-item.md) | Individual navigation item for customizing sidebar with icons, active states, and external links |
|
|
73
74
|
| [SidebarLayout](references/components/sidebar-layout.md) | Default AppShell layout with a built-in sidebar, content header, optional app-wide top bar, and an escape-hatch body slot |
|
|
75
|
+
| [SidebarSection](references/components/sidebar-section.md) | Static, non-collapsible heading for grouping flat sidebar navigation items |
|
|
74
76
|
| [Spinner](references/components/spinner.md) | Lightweight loading indicator component with accessible defaults and preset sizes |
|
|
75
77
|
| [Table](references/components/table.md) | Semantic HTML table with pre-styled sub-components |
|
|
76
78
|
| [Tabs](references/components/tabs.md) | Tab navigation with a compound component API |
|
|
79
|
+
| [Text](references/components/text.md) | Text with a typography role, rendered as an element you choose |
|
|
77
80
|
| [Textarea](references/components/textarea.md) | Styled multi-line text input sized by rows, with automatic Field / React Hook Form integration |
|
|
78
81
|
| [Timeline](references/components/timeline.md) | Composable primitives for building time-based layouts with axis guides, row backgrounds, intervals, and dependency links |
|
|
79
82
|
| [Toolbar](references/components/toolbar.md) | Full-width action bars with composable rows, grouped controls, and keyboard navigation |
|
|
@@ -84,33 +87,33 @@ Every AppShell component, its API surface, and usage guidance. Read the entry fo
|
|
|
84
87
|
|
|
85
88
|
Hooks, factory functions, guards, and router utilities exported by AppShell.
|
|
86
89
|
|
|
87
|
-
| Name | Description
|
|
88
|
-
| ------------------------------------------------------------------------------------------ |
|
|
89
|
-
| [createAIGatewayClient](references/api/create-ai-gateway-client.md) | Create a low-level AI Gateway client that reuses AppShell authentication
|
|
90
|
-
| [createTypedPaths](references/api/create-typed-paths.md) | Generate type-safe route paths from file-based routing
|
|
91
|
-
| [defineI18nLabels](references/api/define-i18n-labels.md) |
|
|
92
|
-
| [defineModule](references/api/define-module.md) | Define a top-level module that appears in the main navigation
|
|
93
|
-
| [defineResource](references/api/define-resource.md) | Define a nested resource (page) within a module
|
|
94
|
-
| [hidden](references/api/guards/hidden.md) | Guard function that denies access and returns 404 Not Found
|
|
95
|
-
| [Guards Overview](references/api/guards/overview.md) | Access control system for routes and components using guard functions
|
|
96
|
-
| [pass](references/api/guards/pass.md) | Guard function that allows access and continues to the next guard
|
|
97
|
-
| [redirectTo](references/api/guards/redirect-to.md) | Guard function that redirects to another page
|
|
98
|
-
| [useLocation](references/api/router/use-location.md) | Hook to access current location object with pathname, search, and state
|
|
99
|
-
| [useNavigate](references/api/router/use-navigate.md) | Hook for programmatic navigation between routes
|
|
100
|
-
| [useParams](references/api/router/use-params.md) | Hook to access dynamic route parameters
|
|
101
|
-
| [useAIChat](references/api/use-ai-chat.md) | Simple text-only chat hook for AI Gateway
|
|
102
|
-
| [useAppShell](references/api/use-app-shell.md) | Hook to access AppShell context data and configuration
|
|
103
|
-
| [useAppShellConfig](references/api/use-app-shell-config.md) | Hook to access AppShell configuration data
|
|
104
|
-
| [useAppShellData](references/api/use-app-shell-data.md) | Hook to access custom context data passed to AppShell
|
|
105
|
-
| [useAuth](references/api/use-auth.md) | Hook for accessing authentication state and methods
|
|
106
|
-
| [useOverrideBreadcrumb](references/api/use-override-breadcrumb.md) | Hook for dynamically overriding breadcrumb titles from within page components
|
|
107
|
-
| [usePageMeta](references/api/use-page-meta.md) | Hook to resolve page metadata from route path
|
|
108
|
-
| [useRegisterCommandPaletteActions](references/api/use-register-command-palette-actions.md) | Hook for registering contextual page actions into the CommandPalette
|
|
109
|
-
| [useResolvedLocale](references/api/use-resolved-locale.md) | Hook to access the full BCP-47 locale and language code from AppShell context
|
|
110
|
-
| [useRouteError](references/api/use-route-error.md) | Hook to access error details in custom error boundaries
|
|
111
|
-
| [useTheme](references/api/use-theme.md) | Hook for accessing and controlling theme (light/dark mode)
|
|
112
|
-
| [useTimeZone](references/api/use-time-zone.md) | Hook to access the configured IANA timezone from AppShell context
|
|
113
|
-
| [useToast](references/api/use-toast.md) | Hook for displaying toast notifications
|
|
90
|
+
| Name | Description |
|
|
91
|
+
| ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
|
|
92
|
+
| [createAIGatewayClient](references/api/create-ai-gateway-client.md) | Create a low-level AI Gateway client that reuses AppShell authentication |
|
|
93
|
+
| [createTypedPaths](references/api/create-typed-paths.md) | Generate type-safe route paths from file-based routing |
|
|
94
|
+
| [defineI18nLabels](references/api/define-i18n-labels.md) | Type-safe locale-first labels for component text and localized navigation metadata |
|
|
95
|
+
| [defineModule](references/api/define-module.md) | Define a top-level module that appears in the main navigation |
|
|
96
|
+
| [defineResource](references/api/define-resource.md) | Define a nested resource (page) within a module |
|
|
97
|
+
| [hidden](references/api/guards/hidden.md) | Guard function that denies access and returns 404 Not Found |
|
|
98
|
+
| [Guards Overview](references/api/guards/overview.md) | Access control system for routes and components using guard functions |
|
|
99
|
+
| [pass](references/api/guards/pass.md) | Guard function that allows access and continues to the next guard |
|
|
100
|
+
| [redirectTo](references/api/guards/redirect-to.md) | Guard function that redirects to another page |
|
|
101
|
+
| [useLocation](references/api/router/use-location.md) | Hook to access current location object with pathname, search, and state |
|
|
102
|
+
| [useNavigate](references/api/router/use-navigate.md) | Hook for programmatic navigation between routes |
|
|
103
|
+
| [useParams](references/api/router/use-params.md) | Hook to access dynamic route parameters |
|
|
104
|
+
| [useAIChat](references/api/use-ai-chat.md) | Simple text-only chat hook for AI Gateway |
|
|
105
|
+
| [useAppShell](references/api/use-app-shell.md) | Hook to access AppShell context data and configuration |
|
|
106
|
+
| [useAppShellConfig](references/api/use-app-shell-config.md) | Hook to access AppShell configuration data |
|
|
107
|
+
| [useAppShellData](references/api/use-app-shell-data.md) | Hook to access custom context data passed to AppShell |
|
|
108
|
+
| [useAuth](references/api/use-auth.md) | Hook for accessing authentication state and methods |
|
|
109
|
+
| [useOverrideBreadcrumb](references/api/use-override-breadcrumb.md) | Hook for dynamically overriding breadcrumb titles from within page components |
|
|
110
|
+
| [usePageMeta](references/api/use-page-meta.md) | Hook to resolve page metadata from route path |
|
|
111
|
+
| [useRegisterCommandPaletteActions](references/api/use-register-command-palette-actions.md) | Hook for registering contextual page actions into the CommandPalette |
|
|
112
|
+
| [useResolvedLocale](references/api/use-resolved-locale.md) | Hook to access the full BCP-47 locale and language code from AppShell context |
|
|
113
|
+
| [useRouteError](references/api/use-route-error.md) | Hook to access error details in custom error boundaries |
|
|
114
|
+
| [useTheme](references/api/use-theme.md) | Hook for accessing and controlling theme (light/dark mode) |
|
|
115
|
+
| [useTimeZone](references/api/use-time-zone.md) | Hook to access the configured IANA timezone from AppShell context |
|
|
116
|
+
| [useToast](references/api/use-toast.md) | Hook for displaying toast notifications |
|
|
114
117
|
|
|
115
118
|
## Available Pages
|
|
116
119
|
|
|
@@ -132,13 +135,19 @@ A page is the shape of a whole screen — the outer choice, made before picking
|
|
|
132
135
|
| [`form/single-page`](references/patterns/form-single-page.md) | Single Page Form | Routed full-page form for moderate field count (6-15) without natural sectioning |
|
|
133
136
|
| [`form/wizard`](references/patterns/form-wizard.md) | Wizard Form | Multi-stage create flow with 3-7 steps and per-step validation gates |
|
|
134
137
|
|
|
138
|
+
### i18n
|
|
139
|
+
|
|
140
|
+
| Slug | Name | Description |
|
|
141
|
+
| --------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------ |
|
|
142
|
+
| [`i18n/labels`](references/patterns/i18n-labels.md) | i18n Labels | Decide UI language before implementation and keep display labels separate from stable data |
|
|
143
|
+
|
|
135
144
|
### interaction
|
|
136
145
|
|
|
137
|
-
| Slug | Name | Description
|
|
138
|
-
| ----------------------------------------------------------------------------- | ------------ |
|
|
139
|
-
| [`interaction/confirm`](references/patterns/interaction-confirm.md) | Confirm | Confirmation dialog before destructive or irreversible actions
|
|
140
|
-
| [`interaction/multi-select`](references/patterns/interaction-multi-select.md) | Multi Select |
|
|
141
|
-
| [`interaction/toast`](references/patterns/interaction-toast.md) | Toast | Lightweight feedback after mutations — success or error notifications
|
|
146
|
+
| Slug | Name | Description |
|
|
147
|
+
| ----------------------------------------------------------------------------- | ------------ | --------------------------------------------------------------------------------------- |
|
|
148
|
+
| [`interaction/confirm`](references/patterns/interaction-confirm.md) | Confirm | Confirmation dialog before destructive or irreversible actions |
|
|
149
|
+
| [`interaction/multi-select`](references/patterns/interaction-multi-select.md) | Multi Select | Bulk operations on selected DataTable rows, from a bar that takes over the table footer |
|
|
150
|
+
| [`interaction/toast`](references/patterns/interaction-toast.md) | Toast | Lightweight feedback after mutations — success or error notifications |
|
|
142
151
|
|
|
143
152
|
### list
|
|
144
153
|
|
|
@@ -149,11 +158,12 @@ A page is the shape of a whole screen — the outer choice, made before picking
|
|
|
149
158
|
## How to Use
|
|
150
159
|
|
|
151
160
|
1. Identify the user's intent (list, detail, form, interaction, screen composition, recipe)
|
|
152
|
-
2.
|
|
153
|
-
3.
|
|
154
|
-
4.
|
|
155
|
-
5. Read the
|
|
156
|
-
6.
|
|
161
|
+
2. Before implementing screen text, read [`i18n/labels`](references/patterns/i18n-labels.md) and establish the app's UI language independently of the specification's language. Use feature label catalogs by default, unless the project explicitly opts out for a single-language app. The API reference is [`defineI18nLabels`](references/api/define-i18n-labels.md).
|
|
162
|
+
3. Building a whole screen? Match it to a **page** entry first and read that. A page fixes the screen's shape — its layout, what goes where, and in what order — before any of the parts inside it are chosen.
|
|
163
|
+
4. Match each part you are building to a **pattern** entry slug from the tables above. A one-off recipe rather than a screen skips straight to this step.
|
|
164
|
+
5. Read the entry's detailed spec: `references/<category>/<slug>.md` (relative to this file)
|
|
165
|
+
6. Read the concept references for styling, design tokens, GraphQL, and custom components: `references/concepts/`; component and hook APIs live in `references/components/` and `references/api/`
|
|
166
|
+
7. Implement using ONLY the imports listed in the selected entries' `requiredImports` — for a page, use those of its patterns; for localization, include the `i18n/labels` entry
|
|
157
167
|
|
|
158
168
|
## Rules
|
|
159
169
|
|
|
@@ -175,3 +185,4 @@ Full rationale in [`styling-theming`](references/concepts/styling-theming.md)
|
|
|
175
185
|
- **Forms default to `form/modal`** — only build a routed full-page form when the design explicitly calls for one.
|
|
176
186
|
- **Forms use AppShell `Form` + `Field`**, submitting via `onFormSubmit` — never a bare `<form onSubmit>` with `FormData`. `onFormSubmit` reads registered `Field.Root`s, so every control (dropdowns included) just needs a wrapping `Field.Root name="…"` — no `name` on the control, no `useState`. React Hook Form is optional, consumer-installed, and only warranted for cross-field validation, field arrays, or a Zod resolver. Details in [`Form`](references/components/form.md) and the `form/*` patterns.
|
|
177
187
|
- **Handle every state:** loading (skeleton), empty (labelled empty state), and error (inline + retry) — never ship only the happy path.
|
|
188
|
+
- **Keep UI language consistent:** apply the chosen language to navigation, component text, accessibility labels, and feedback. Use the catalog's `useT()` for rendered strings and `labels.t()` for `LocalizedString` metadata; keep server codes and comparison values untranslated. See [`i18n/labels`](references/patterns/i18n-labels.md).
|
|
@@ -1,143 +1,175 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: defineI18nLabels
|
|
3
|
-
description:
|
|
3
|
+
description: Type-safe locale-first labels for component text and localized navigation metadata
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
<!-- GENERATED by docs-kit from docs-src/hooks/define-i18n-labels.docs.outline.md — do not edit by hand. -->
|
|
7
7
|
|
|
8
8
|
# defineI18nLabels
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Defines a label catalog and returns two resolvers: a React Hook for rendered text and a `LocalizedString` factory for AppShell configuration. No additional i18n package is required.
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Define a Catalog
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
Create the catalog once at module scope, not inside a component. The object is **locale-first**: `en` is the required base locale; each locale contains a flat map of label keys to strings or functions that return strings.
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
// i18n-labels.ts
|
|
18
|
+
import { defineI18nLabels } from "@tailor-platform/app-shell";
|
|
19
|
+
|
|
20
|
+
export const labels = defineI18nLabels({
|
|
21
|
+
en: {
|
|
22
|
+
title: "Requests",
|
|
23
|
+
save: "Save",
|
|
24
|
+
greeting: ({ name }: { name: string }) => `Hello, ${name}!`,
|
|
25
|
+
itemCount: ({ count }: { count: number }) => (count === 1 ? "1 item" : `${count} items`),
|
|
26
|
+
"status.PENDING": "Pending",
|
|
27
|
+
unknownStatus: "Unknown status",
|
|
28
|
+
},
|
|
29
|
+
ja: {
|
|
30
|
+
title: "申請一覧",
|
|
31
|
+
save: "保存",
|
|
32
|
+
greeting: ({ name }: { name: string }) => `こんにちは、${name}さん!`,
|
|
33
|
+
itemCount: ({ count }: { count: number }) => `${count}件`,
|
|
34
|
+
"status.PENDING": "承認待ち",
|
|
35
|
+
unknownStatus: "不明な状態",
|
|
36
|
+
},
|
|
37
|
+
});
|
|
19
38
|
|
|
20
|
-
|
|
39
|
+
export const useT = labels.useT;
|
|
40
|
+
```
|
|
21
41
|
|
|
22
|
-
|
|
42
|
+
`useT` above is exported by **your catalog module**, not directly by `@tailor-platform/app-shell`. The component examples below use this shared setup; import `labels` and `useT` from `./i18n-labels` in application files.
|
|
23
43
|
|
|
24
|
-
|
|
25
|
-
- **Required:** Yes
|
|
26
|
-
- **Description:** Object mapping label keys to locale-specific strings
|
|
44
|
+
Label keys are flat strings, not nested objects. A dotted key such as `"status.PENDING"` is an ordinary flat string key.
|
|
27
45
|
|
|
28
46
|
## Return Value
|
|
29
47
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
48
|
+
| Member | Returns | Use for |
|
|
49
|
+
| -------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
50
|
+
| `labels.useT()` | A resolver `t` that returns a `string` | Text rendered by a React component, string-valued props, toasts, and validation messages |
|
|
51
|
+
| `t(key, props?)` | `string` | A known label key, with required props for function-valued labels |
|
|
52
|
+
| `t.dynamic(key, fallback)` | `string` | A runtime string key for a **static** label |
|
|
53
|
+
| `labels.t(key, props?)` | `LocalizedString` | `meta.title`, `meta.breadcrumbTitle`, and props explicitly accepting `LocalizedString` |
|
|
54
|
+
|
|
55
|
+
`LocalizedString` is `string | ((locale: string) => string)`. `labels.t()` creates a locale callback, **not rendered text**. Do not put its result directly into JSX children or pass it to a prop that accepts only `string`.
|
|
56
|
+
|
|
57
|
+
## In Components: `useT()`
|
|
58
|
+
|
|
59
|
+
Call the Hook at the top level of a component under `AppShell`. The returned resolver reads the current AppShell locale.
|
|
60
|
+
|
|
61
|
+
<example-preview name="in-components"></example-preview>
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
function InComponents() {
|
|
65
|
+
const t = useT();
|
|
66
|
+
|
|
67
|
+
return <Button>{t("save")}</Button>;
|
|
33
68
|
}
|
|
34
69
|
```
|
|
35
70
|
|
|
36
|
-
|
|
71
|
+
Use the same resolver for string props such as `placeholder`, `aria-label`, or `Layout.Header`'s `title`, and inside event handlers for localized notifications. Do not call a Hook in an event handler; capture `t` during render.
|
|
37
72
|
|
|
38
|
-
|
|
73
|
+
## Function-valued Labels: Typed Props
|
|
39
74
|
|
|
40
|
-
|
|
41
|
-
import { defineI18nLabels } from "@tailor-platform/app-shell";
|
|
75
|
+
Use a function for a whole sentence containing runtime values. Its props are inferred from the English definition and checked for the other declared locales. Keep pluralization and word order inside each locale's function rather than concatenating translated fragments.
|
|
42
76
|
|
|
43
|
-
|
|
44
|
-
dashboard: {
|
|
45
|
-
en: "Dashboard",
|
|
46
|
-
ja: "ダッシュボード",
|
|
47
|
-
},
|
|
48
|
-
products: {
|
|
49
|
-
en: "Products",
|
|
50
|
-
ja: "商品",
|
|
51
|
-
},
|
|
52
|
-
settings: {
|
|
53
|
-
en: "Settings",
|
|
54
|
-
ja: "設定",
|
|
55
|
-
},
|
|
56
|
-
});
|
|
77
|
+
<example-preview name="dynamic-labels"></example-preview>
|
|
57
78
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
})
|
|
79
|
+
```tsx
|
|
80
|
+
function DynamicLabels() {
|
|
81
|
+
const t = useT();
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<Layout>
|
|
85
|
+
<Layout.Header title={t("greeting", { name: "Alex" })} />
|
|
86
|
+
<Layout.Column>{t("itemCount", { count: 3 })}</Layout.Column>
|
|
87
|
+
</Layout>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
66
90
|
```
|
|
67
91
|
|
|
68
|
-
|
|
92
|
+
Both `t("greeting", { name })` and `labels.t("greeting", { name })` require the correct props. When using `labels.t()` for metadata, props are bound **when `labels.t()` is called**, not fetched at render time. For a breadcrumb depending on a URL segment, see [Routing & Navigation](../concepts/routing-navigation.md#dynamic-breadcrumb-titles).
|
|
69
93
|
|
|
70
|
-
|
|
71
|
-
const labels = defineI18nLabels({
|
|
72
|
-
welcome: {
|
|
73
|
-
en: "Welcome",
|
|
74
|
-
ja: "ようこそ",
|
|
75
|
-
es: "Bienvenido",
|
|
76
|
-
fr: "Bienvenue",
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
```
|
|
94
|
+
## Runtime Keys: `t.dynamic()`
|
|
80
95
|
|
|
81
|
-
|
|
96
|
+
`t.dynamic(key: string, fallback: string)` accepts a key constructed at runtime, for example from a server enum. Keep the enum stable and translate only its display label.
|
|
82
97
|
|
|
83
|
-
|
|
84
|
-
function WelcomeMessage() {
|
|
85
|
-
const labels = defineI18nLabels({
|
|
86
|
-
greeting: {
|
|
87
|
-
en: "Hello",
|
|
88
|
-
ja: "こんにちは",
|
|
89
|
-
},
|
|
90
|
-
});
|
|
98
|
+
<example-preview name="runtime-keys"></example-preview>
|
|
91
99
|
|
|
92
|
-
|
|
100
|
+
```tsx
|
|
101
|
+
function RuntimeKeys() {
|
|
102
|
+
const t = useT();
|
|
103
|
+
const status: string = "PENDING";
|
|
104
|
+
|
|
105
|
+
return <Badge variant="warning">{t.dynamic(`status.${status}`, t("unknownStatus"))}</Badge>;
|
|
93
106
|
}
|
|
94
107
|
```
|
|
95
108
|
|
|
96
|
-
|
|
109
|
+
Resolution order:
|
|
110
|
+
|
|
111
|
+
1. Look up the key in the current locale.
|
|
112
|
+
2. If absent, look it up in `en`.
|
|
113
|
+
3. If the resolved value is missing **or is a function**, return the supplied fallback unchanged.
|
|
97
114
|
|
|
98
|
-
|
|
99
|
-
import { SidebarGroup } from "@tailor-platform/app-shell";
|
|
115
|
+
The fallback is not translated automatically. Prefer another translated label, as above, instead of an English literal. `t.dynamic()` does not accept label props; use typed `t(key, props)` for function-valued labels. Prefer typed `t(key)` whenever the key is known at compile time.
|
|
100
116
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
117
|
+
## Navigation Metadata: `labels.t()`
|
|
118
|
+
|
|
119
|
+
Module and resource definitions are usually outside React components, so they cannot call Hooks. Pass the locale callback to AppShell, which resolves it using the active locale:
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import { defineModule } from "@tailor-platform/app-shell";
|
|
123
|
+
import { labels } from "./i18n-labels";
|
|
124
|
+
|
|
125
|
+
const requestModule = defineModule({
|
|
126
|
+
path: "requests",
|
|
127
|
+
meta: {
|
|
128
|
+
title: labels.t("title"),
|
|
129
|
+
breadcrumbTitle: labels.t("title"),
|
|
105
130
|
},
|
|
131
|
+
component: InComponents,
|
|
132
|
+
resources: [],
|
|
106
133
|
});
|
|
107
|
-
|
|
108
|
-
<SidebarGroup title={labels.t("admin")}>
|
|
109
|
-
<SidebarItem to="/admin/users" />
|
|
110
|
-
</SidebarGroup>
|
|
111
134
|
```
|
|
112
135
|
|
|
113
|
-
|
|
136
|
+
The same approach works for `defineResource().meta` and `SidebarGroup`, `SidebarSection`, and `SidebarItem` title overrides. For ordinary string-valued component props, use the Hook resolver instead.
|
|
114
137
|
|
|
115
|
-
|
|
138
|
+
## Setting Locale and Fallbacks
|
|
116
139
|
|
|
117
|
-
|
|
118
|
-
|
|
140
|
+
Choose the app's UI language explicitly when it is fixed:
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
<AppShell locale="ja-JP" modules={[requestModule]}>
|
|
119
144
|
<SidebarLayout />
|
|
120
145
|
</AppShell>
|
|
121
146
|
```
|
|
122
147
|
|
|
123
|
-
|
|
148
|
+
- AppShell normalizes a BCP-47 tag such as `ja-JP` to `ja` for label lookup. Use language keys such as `ja` in the catalog, not `ja-JP`.
|
|
149
|
+
- If `locale` is omitted, AppShell uses the browser's preferred language. If browser locale information is unavailable or invalid, it uses `en`.
|
|
150
|
+
- If a catalog has no definition for the selected language, labels fall back to `en`. A missing localized key also falls back to its English value at runtime.
|
|
151
|
+
- Custom catalogs can define other languages such as `fr`; this does **not** translate AppShell's built-in UI. Its shared built-in catalog currently supplies English and Japanese, with English fallback for other languages.
|
|
152
|
+
- For regional date and number formatting, use the full tag from [useResolvedLocale](./use-resolved-locale.md); label translation does not format dates, numbers, or time zones automatically.
|
|
124
153
|
|
|
125
|
-
|
|
154
|
+
## TypeScript Contract
|
|
126
155
|
|
|
127
|
-
|
|
156
|
+
With an ordinary inferred definition such as the catalog above:
|
|
128
157
|
|
|
129
|
-
|
|
158
|
+
- `en` is required, even for an app whose selected UI language is Japanese.
|
|
159
|
+
- Other explicitly declared locales must supply all base keys and compatible function props. Translation omissions become compile errors.
|
|
160
|
+
- `t()` and `labels.t()` reject unknown keys, missing or wrongly typed function props, and props passed to static labels.
|
|
161
|
+
- `t.dynamic()` deliberately accepts any string key; unknown keys are handled by its required fallback instead of compile-time validation.
|
|
130
162
|
|
|
131
|
-
|
|
132
|
-
const labels = defineI18nLabels({
|
|
133
|
-
dashboard: { en: "Dashboard", ja: "ダッシュボード" },
|
|
134
|
-
products: { en: "Products", ja: "商品" },
|
|
135
|
-
});
|
|
163
|
+
These checks validate structure and call sites, not translation quality or language consistency.
|
|
136
164
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
165
|
+
## Exports
|
|
166
|
+
|
|
167
|
+
- `I18nLabels` — type
|
|
168
|
+
- `defineI18nLabels` — const
|
|
140
169
|
|
|
141
170
|
## Related
|
|
142
171
|
|
|
143
|
-
- [
|
|
172
|
+
- [i18n Labels Pattern](../patterns/i18n-labels.md) — UI language decisions, key naming, file splitting, and server-originated text
|
|
173
|
+
- [defineModule](./define-module.md) — localized navigation metadata
|
|
174
|
+
- [defineResource](./define-resource.md) — localized resource metadata
|
|
175
|
+
- [useResolvedLocale](./use-resolved-locale.md) — full locale for formatting
|
|
@@ -70,8 +70,8 @@ defineModule({
|
|
|
70
70
|
|
|
71
71
|
**meta.breadcrumbTitle**
|
|
72
72
|
|
|
73
|
-
- **Type:** `string | ((segment: string) =>
|
|
74
|
-
- **Description:** Custom breadcrumb segment title override
|
|
73
|
+
- **Type:** `string | ((segment: string) => LocalizedString)`
|
|
74
|
+
- **Description:** Custom breadcrumb segment title override. Pass `labels.t(key)` directly for localization: `breadcrumbTitle: labels.t("dashboard")`. Other callbacks receive the URL segment and may return a `LocalizedString`, resolved using the current locale. The existing `() => labels.t(key)` form remains supported; handwritten locale functions must be returned from an outer callback. When omitted, the breadcrumb uses the localized `meta.title`.
|
|
75
75
|
|
|
76
76
|
### `guards`
|
|
77
77
|
|
|
@@ -209,10 +209,8 @@ const productsModule = defineModule({
|
|
|
209
209
|
import { defineI18nLabels } from "@tailor-platform/app-shell";
|
|
210
210
|
|
|
211
211
|
const labels = defineI18nLabels({
|
|
212
|
-
dashboard:
|
|
213
|
-
|
|
214
|
-
ja: "ダッシュボード",
|
|
215
|
-
},
|
|
212
|
+
en: { dashboard: "Dashboard" },
|
|
213
|
+
ja: { dashboard: "ダッシュボード" },
|
|
216
214
|
});
|
|
217
215
|
|
|
218
216
|
const dashboardModule = defineModule({
|
|
@@ -65,8 +65,8 @@ defineResource({
|
|
|
65
65
|
|
|
66
66
|
**meta.breadcrumbTitle**
|
|
67
67
|
|
|
68
|
-
- **Type:** `string | ((segment: string) =>
|
|
69
|
-
- **Description:** Custom breadcrumb segment title
|
|
68
|
+
- **Type:** `string | ((segment: string) => LocalizedString)`
|
|
69
|
+
- **Description:** Custom breadcrumb segment title. Pass `labels.t(key)` directly for localization: `breadcrumbTitle: labels.t("orders")`. Other callbacks receive the URL segment and may return a `LocalizedString`, for example `breadcrumbTitle: (segment) => labels.t("order", { id: segment })` for a dynamic label. AppShell resolves it using the current locale. The existing `() => labels.t(key)` form remains supported; handwritten locale functions must be returned from an outer callback. When omitted, the breadcrumb uses the localized `meta.title`.
|
|
70
70
|
|
|
71
71
|
### `subResources`
|
|
72
72
|
|