@modern-admin/react 0.1.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/action-guard.d.ts +13 -0
- package/dist/action-guard.d.ts.map +1 -0
- package/dist/action-guard.js +15 -0
- package/dist/action-guard.js.map +1 -0
- package/dist/action-menu.d.ts +17 -0
- package/dist/action-menu.d.ts.map +1 -0
- package/dist/action-menu.jsx +80 -0
- package/dist/action-menu.jsx.map +1 -0
- package/dist/admin-app.d.ts +23 -0
- package/dist/admin-app.d.ts.map +1 -0
- package/dist/admin-app.jsx +407 -0
- package/dist/admin-app.jsx.map +1 -0
- package/dist/admin-router.d.ts +29 -0
- package/dist/admin-router.d.ts.map +1 -0
- package/dist/admin-router.jsx +215 -0
- package/dist/admin-router.jsx.map +1 -0
- package/dist/breadcrumbs.d.ts +17 -0
- package/dist/breadcrumbs.d.ts.map +1 -0
- package/dist/breadcrumbs.jsx +40 -0
- package/dist/breadcrumbs.jsx.map +1 -0
- package/dist/client.d.ts +526 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +582 -0
- package/dist/client.js.map +1 -0
- package/dist/component-loader.d.ts +10 -0
- package/dist/component-loader.d.ts.map +1 -0
- package/dist/component-loader.js +23 -0
- package/dist/component-loader.js.map +1 -0
- package/dist/components/ai-assistant-widget.d.ts +3 -0
- package/dist/components/ai-assistant-widget.d.ts.map +1 -0
- package/dist/components/ai-assistant-widget.jsx +390 -0
- package/dist/components/ai-assistant-widget.jsx.map +1 -0
- package/dist/components/ai-fill-dialog.d.ts +9 -0
- package/dist/components/ai-fill-dialog.d.ts.map +1 -0
- package/dist/components/ai-fill-dialog.jsx +105 -0
- package/dist/components/ai-fill-dialog.jsx.map +1 -0
- package/dist/components/chart-builder-dialog.d.ts +10 -0
- package/dist/components/chart-builder-dialog.d.ts.map +1 -0
- package/dist/components/chart-builder-dialog.jsx +433 -0
- package/dist/components/chart-builder-dialog.jsx.map +1 -0
- package/dist/components/chart-widget.d.ts +12 -0
- package/dist/components/chart-widget.d.ts.map +1 -0
- package/dist/components/chart-widget.jsx +365 -0
- package/dist/components/chart-widget.jsx.map +1 -0
- package/dist/components/global-search-dialog.d.ts +7 -0
- package/dist/components/global-search-dialog.d.ts.map +1 -0
- package/dist/components/global-search-dialog.jsx +187 -0
- package/dist/components/global-search-dialog.jsx.map +1 -0
- package/dist/components/group-settings-dialog.d.ts +13 -0
- package/dist/components/group-settings-dialog.d.ts.map +1 -0
- package/dist/components/group-settings-dialog.jsx +53 -0
- package/dist/components/group-settings-dialog.jsx.map +1 -0
- package/dist/components/move-chart-dialog.d.ts +18 -0
- package/dist/components/move-chart-dialog.d.ts.map +1 -0
- package/dist/components/move-chart-dialog.jsx +68 -0
- package/dist/components/move-chart-dialog.jsx.map +1 -0
- package/dist/components/reference-multi-table-dialog.d.ts +12 -0
- package/dist/components/reference-multi-table-dialog.d.ts.map +1 -0
- package/dist/components/reference-multi-table-dialog.jsx +126 -0
- package/dist/components/reference-multi-table-dialog.jsx.map +1 -0
- package/dist/components/related-records-tabs.d.ts +8 -0
- package/dist/components/related-records-tabs.d.ts.map +1 -0
- package/dist/components/related-records-tabs.jsx +75 -0
- package/dist/components/related-records-tabs.jsx.map +1 -0
- package/dist/components/revisions-button.d.ts +7 -0
- package/dist/components/revisions-button.d.ts.map +1 -0
- package/dist/components/revisions-button.jsx +152 -0
- package/dist/components/revisions-button.jsx.map +1 -0
- package/dist/components/wizard-form.d.ts +43 -0
- package/dist/components/wizard-form.d.ts.map +1 -0
- package/dist/components/wizard-form.jsx +136 -0
- package/dist/components/wizard-form.jsx.map +1 -0
- package/dist/dashboard/time-series.d.ts +20 -0
- package/dist/dashboard/time-series.d.ts.map +1 -0
- package/dist/dashboard/time-series.js +108 -0
- package/dist/dashboard/time-series.js.map +1 -0
- package/dist/dialogs.d.ts +35 -0
- package/dist/dialogs.d.ts.map +1 -0
- package/dist/dialogs.jsx +152 -0
- package/dist/dialogs.jsx.map +1 -0
- package/dist/export.d.ts +39 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +114 -0
- package/dist/export.js.map +1 -0
- package/dist/extension-registry.d.ts +122 -0
- package/dist/extension-registry.d.ts.map +1 -0
- package/dist/extension-registry.js +93 -0
- package/dist/extension-registry.js.map +1 -0
- package/dist/header-controls.d.ts +4 -0
- package/dist/header-controls.d.ts.map +1 -0
- package/dist/header-controls.jsx +70 -0
- package/dist/header-controls.jsx.map +1 -0
- package/dist/hooks.d.ts +104 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +374 -0
- package/dist/hooks.js.map +1 -0
- package/dist/hotkey-help.d.ts +3 -0
- package/dist/hotkey-help.d.ts.map +1 -0
- package/dist/hotkey-help.jsx +32 -0
- package/dist/hotkey-help.jsx.map +1 -0
- package/dist/hotkey-registry.d.ts +18 -0
- package/dist/hotkey-registry.d.ts.map +1 -0
- package/dist/hotkey-registry.jsx +34 -0
- package/dist/hotkey-registry.jsx.map +1 -0
- package/dist/i18n.d.ts +74 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.jsx +127 -0
- package/dist/i18n.jsx.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/notify.d.ts +41 -0
- package/dist/notify.d.ts.map +1 -0
- package/dist/notify.jsx +58 -0
- package/dist/notify.jsx.map +1 -0
- package/dist/pages/ai-assistant-settings-section.d.ts +3 -0
- package/dist/pages/ai-assistant-settings-section.d.ts.map +1 -0
- package/dist/pages/ai-assistant-settings-section.jsx +126 -0
- package/dist/pages/ai-assistant-settings-section.jsx.map +1 -0
- package/dist/pages/audit-log-page.d.ts +3 -0
- package/dist/pages/audit-log-page.d.ts.map +1 -0
- package/dist/pages/audit-log-page.jsx +354 -0
- package/dist/pages/audit-log-page.jsx.map +1 -0
- package/dist/pages/edit-page.d.ts +7 -0
- package/dist/pages/edit-page.d.ts.map +1 -0
- package/dist/pages/edit-page.jsx +614 -0
- package/dist/pages/edit-page.jsx.map +1 -0
- package/dist/pages/export-dialog.d.ts +11 -0
- package/dist/pages/export-dialog.d.ts.map +1 -0
- package/dist/pages/export-dialog.jsx +102 -0
- package/dist/pages/export-dialog.jsx.map +1 -0
- package/dist/pages/home-page.d.ts +3 -0
- package/dist/pages/home-page.d.ts.map +1 -0
- package/dist/pages/home-page.jsx +211 -0
- package/dist/pages/home-page.jsx.map +1 -0
- package/dist/pages/list-page.d.ts +42 -0
- package/dist/pages/list-page.d.ts.map +1 -0
- package/dist/pages/list-page.jsx +1596 -0
- package/dist/pages/list-page.jsx.map +1 -0
- package/dist/pages/login-page.d.ts +11 -0
- package/dist/pages/login-page.d.ts.map +1 -0
- package/dist/pages/login-page.jsx +157 -0
- package/dist/pages/login-page.jsx.map +1 -0
- package/dist/pages/settings-page.d.ts +5 -0
- package/dist/pages/settings-page.d.ts.map +1 -0
- package/dist/pages/settings-page.jsx +787 -0
- package/dist/pages/settings-page.jsx.map +1 -0
- package/dist/pages/settings-shared.d.ts +51 -0
- package/dist/pages/settings-shared.d.ts.map +1 -0
- package/dist/pages/settings-shared.jsx +66 -0
- package/dist/pages/settings-shared.jsx.map +1 -0
- package/dist/pages/show-page.d.ts +7 -0
- package/dist/pages/show-page.d.ts.map +1 -0
- package/dist/pages/show-page.jsx +147 -0
- package/dist/pages/show-page.jsx.map +1 -0
- package/dist/pages/wizard-create-page.d.ts +14 -0
- package/dist/pages/wizard-create-page.d.ts.map +1 -0
- package/dist/pages/wizard-create-page.jsx +106 -0
- package/dist/pages/wizard-create-page.jsx.map +1 -0
- package/dist/property-renderer.d.ts +8 -0
- package/dist/property-renderer.d.ts.map +1 -0
- package/dist/property-renderer.jsx +690 -0
- package/dist/property-renderer.jsx.map +1 -0
- package/dist/provider.d.ts +20 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.jsx +32 -0
- package/dist/provider.jsx.map +1 -0
- package/dist/realtime.d.ts +22 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +38 -0
- package/dist/realtime.js.map +1 -0
- package/dist/reference.d.ts +52 -0
- package/dist/reference.d.ts.map +1 -0
- package/dist/reference.jsx +224 -0
- package/dist/reference.jsx.map +1 -0
- package/dist/relations.d.ts +11 -0
- package/dist/relations.d.ts.map +1 -0
- package/dist/relations.js +36 -0
- package/dist/relations.js.map +1 -0
- package/dist/router.d.ts +82 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.jsx +187 -0
- package/dist/router.jsx.map +1 -0
- package/dist/show-when.d.ts +7 -0
- package/dist/show-when.d.ts.map +1 -0
- package/dist/show-when.js +77 -0
- package/dist/show-when.js.map +1 -0
- package/dist/types.d.ts +194 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/dist/use-dashboard-charts.d.ts +93 -0
- package/dist/use-dashboard-charts.d.ts.map +1 -0
- package/dist/use-dashboard-charts.js +263 -0
- package/dist/use-dashboard-charts.js.map +1 -0
- package/dist/use-hotkey.d.ts +17 -0
- package/dist/use-hotkey.d.ts.map +1 -0
- package/dist/use-hotkey.js +103 -0
- package/dist/use-hotkey.js.map +1 -0
- package/dist/user-directory.d.ts +18 -0
- package/dist/user-directory.d.ts.map +1 -0
- package/dist/user-directory.js +51 -0
- package/dist/user-directory.js.map +1 -0
- package/dist/validation.d.ts +22 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +338 -0
- package/dist/validation.js.map +1 -0
- package/package.json +59 -0
- package/src/action-guard.ts +20 -0
- package/src/action-menu.tsx +161 -0
- package/src/admin-app.tsx +630 -0
- package/src/admin-router.tsx +273 -0
- package/src/breadcrumbs.tsx +75 -0
- package/src/client.ts +1093 -0
- package/src/component-loader.ts +33 -0
- package/src/components/ai-assistant-widget.tsx +565 -0
- package/src/components/ai-fill-dialog.tsx +143 -0
- package/src/components/chart-builder-dialog.tsx +618 -0
- package/src/components/chart-widget.tsx +654 -0
- package/src/components/global-search-dialog.tsx +272 -0
- package/src/components/group-settings-dialog.tsx +93 -0
- package/src/components/move-chart-dialog.tsx +130 -0
- package/src/components/reference-multi-table-dialog.tsx +196 -0
- package/src/components/related-records-tabs.tsx +130 -0
- package/src/components/revisions-button.tsx +237 -0
- package/src/components/wizard-form.tsx +302 -0
- package/src/dashboard/time-series.ts +125 -0
- package/src/dialogs.tsx +265 -0
- package/src/export.ts +140 -0
- package/src/extension-registry.ts +195 -0
- package/src/header-controls.tsx +125 -0
- package/src/hooks.ts +509 -0
- package/src/hotkey-help.tsx +56 -0
- package/src/hotkey-registry.tsx +60 -0
- package/src/i18n.tsx +267 -0
- package/src/index.ts +192 -0
- package/src/notify.tsx +94 -0
- package/src/pages/ai-assistant-settings-section.tsx +167 -0
- package/src/pages/audit-log-page.tsx +580 -0
- package/src/pages/edit-page.tsx +743 -0
- package/src/pages/export-dialog.tsx +154 -0
- package/src/pages/home-page.tsx +318 -0
- package/src/pages/list-page.tsx +2645 -0
- package/src/pages/login-page.tsx +242 -0
- package/src/pages/settings-page.tsx +1143 -0
- package/src/pages/settings-shared.tsx +134 -0
- package/src/pages/show-page.tsx +223 -0
- package/src/pages/wizard-create-page.tsx +164 -0
- package/src/property-renderer.tsx +1143 -0
- package/src/provider.tsx +70 -0
- package/src/realtime.ts +55 -0
- package/src/reference.tsx +386 -0
- package/src/relations.ts +55 -0
- package/src/router.tsx +211 -0
- package/src/show-when.ts +76 -0
- package/src/types.ts +198 -0
- package/src/use-dashboard-charts.ts +362 -0
- package/src/use-hotkey.ts +128 -0
- package/src/user-directory.ts +56 -0
- package/src/validation.ts +361 -0
package/src/i18n.tsx
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// React bindings for @modern-admin/i18n. Wraps the I18n registry in a
|
|
2
|
+
// provider, persists the active locale in localStorage, and exposes a
|
|
3
|
+
// `useI18n()` hook that re-renders subscribers on locale changes.
|
|
4
|
+
|
|
5
|
+
import * as React from 'react'
|
|
6
|
+
import { I18n, builtinLocales, type LocaleBundle } from '@modern-admin/i18n'
|
|
7
|
+
import type { KeyValueFieldSpec, PropertyJSON, RelatedResource, ResourceJSON } from './types.js'
|
|
8
|
+
|
|
9
|
+
const STORAGE_KEY = 'modern-admin:locale'
|
|
10
|
+
|
|
11
|
+
export interface MetadataKeyValueFieldTranslations {
|
|
12
|
+
label?: string
|
|
13
|
+
description?: string
|
|
14
|
+
placeholder?: string
|
|
15
|
+
availableValues?: Record<string, string>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface MetadataPropertyTranslations {
|
|
19
|
+
label?: string
|
|
20
|
+
description?: string
|
|
21
|
+
availableValues?: Record<string, string>
|
|
22
|
+
keyValueFields?: Record<string, MetadataKeyValueFieldTranslations>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface MetadataActionTranslations {
|
|
26
|
+
/** Display label shown in action menus, buttons, and tooltips. */
|
|
27
|
+
label?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface MetadataResourceTranslations {
|
|
31
|
+
label?: string
|
|
32
|
+
name?: string
|
|
33
|
+
navigation?: {
|
|
34
|
+
name?: string
|
|
35
|
+
group?: string
|
|
36
|
+
}
|
|
37
|
+
properties?: Record<string, MetadataPropertyTranslations>
|
|
38
|
+
/** Per-action overrides keyed by action name (e.g. `publish`, `archive`). */
|
|
39
|
+
actions?: Record<string, MetadataActionTranslations>
|
|
40
|
+
/**
|
|
41
|
+
* Tab label overrides for related-resource tabs on the show page.
|
|
42
|
+
* Key is the related resource's `resourceId`.
|
|
43
|
+
*/
|
|
44
|
+
relatedResources?: Record<string, string>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface MetadataLocaleTranslations {
|
|
48
|
+
navigation?: {
|
|
49
|
+
groups?: Record<string, string>
|
|
50
|
+
}
|
|
51
|
+
resources?: Record<string, MetadataResourceTranslations>
|
|
52
|
+
properties?: Record<string, MetadataPropertyTranslations>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type MetadataTranslations = Record<string, MetadataLocaleTranslations>
|
|
56
|
+
|
|
57
|
+
const isDefined = <T,>(value: T | undefined): value is T => value !== undefined
|
|
58
|
+
|
|
59
|
+
const firstDefined = <T,>(...values: Array<T | undefined>): T | undefined =>
|
|
60
|
+
values.find(isDefined)
|
|
61
|
+
|
|
62
|
+
const localizeAvailableValues = (
|
|
63
|
+
availableValues: Array<{ value: string; label: string }> | null,
|
|
64
|
+
...maps: Array<Record<string, string> | undefined>
|
|
65
|
+
): Array<{ value: string; label: string }> | null => {
|
|
66
|
+
if (!availableValues) return availableValues
|
|
67
|
+
return availableValues.map((option) => ({
|
|
68
|
+
...option,
|
|
69
|
+
label: firstDefined(...maps.map((map) => map?.[option.value]), option.label) ?? option.label,
|
|
70
|
+
}))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const localizeKeyValueField = (
|
|
74
|
+
field: KeyValueFieldSpec,
|
|
75
|
+
...translations: Array<MetadataKeyValueFieldTranslations | undefined>
|
|
76
|
+
): KeyValueFieldSpec => ({
|
|
77
|
+
...field,
|
|
78
|
+
label: firstDefined(...translations.map((translation) => translation?.label), field.label),
|
|
79
|
+
description: firstDefined(...translations.map((translation) => translation?.description), field.description),
|
|
80
|
+
placeholder: firstDefined(...translations.map((translation) => translation?.placeholder), field.placeholder),
|
|
81
|
+
availableValues: field.availableValues?.map((option) => {
|
|
82
|
+
if (typeof option === 'string') {
|
|
83
|
+
const label =
|
|
84
|
+
firstDefined(...translations.map((translation) => translation?.availableValues?.[option]), option) ??
|
|
85
|
+
option
|
|
86
|
+
return { value: option, label }
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
...option,
|
|
90
|
+
label:
|
|
91
|
+
firstDefined(...translations.map((translation) => translation?.availableValues?.[option.value]), option.label) ??
|
|
92
|
+
option.label,
|
|
93
|
+
}
|
|
94
|
+
}),
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Applies translated tab labels to `relatedResources`. Each translation map
|
|
99
|
+
* is keyed by `resourceId`; the first map that has a matching entry wins.
|
|
100
|
+
* Exported for unit testing.
|
|
101
|
+
*/
|
|
102
|
+
export const localizeRelatedResources = (
|
|
103
|
+
relatedResources: RelatedResource[] | undefined,
|
|
104
|
+
...translations: Array<Record<string, string> | undefined>
|
|
105
|
+
): RelatedResource[] | undefined => {
|
|
106
|
+
if (!relatedResources) return relatedResources
|
|
107
|
+
return relatedResources.map((r) => {
|
|
108
|
+
const translatedLabel = firstDefined(...translations.map((map) => map?.[r.resourceId]))
|
|
109
|
+
return translatedLabel !== undefined ? { ...r, label: translatedLabel } : r
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const localizeProperty = (
|
|
114
|
+
property: PropertyJSON,
|
|
115
|
+
...translations: Array<MetadataPropertyTranslations | undefined>
|
|
116
|
+
): PropertyJSON => ({
|
|
117
|
+
...property,
|
|
118
|
+
label: firstDefined(...translations.map((translation) => translation?.label), property.label) ?? property.label,
|
|
119
|
+
description: firstDefined(...translations.map((translation) => translation?.description), property.description),
|
|
120
|
+
availableValues: localizeAvailableValues(
|
|
121
|
+
property.availableValues,
|
|
122
|
+
...translations.map((translation) => translation?.availableValues),
|
|
123
|
+
),
|
|
124
|
+
keyValueFields: property.keyValueFields?.map((field) =>
|
|
125
|
+
localizeKeyValueField(
|
|
126
|
+
field,
|
|
127
|
+
...translations.map((translation) => translation?.keyValueFields?.[field.key]),
|
|
128
|
+
),
|
|
129
|
+
),
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
interface I18nContextValue {
|
|
133
|
+
locale: string
|
|
134
|
+
setLocale(code: string): void
|
|
135
|
+
t(key: string, params?: Record<string, unknown>): string
|
|
136
|
+
availableLocales(): Array<{ code: string; name: string }>
|
|
137
|
+
localizeResource(resource: ResourceJSON): ResourceJSON
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const I18nContext = React.createContext<I18nContextValue | null>(null)
|
|
141
|
+
|
|
142
|
+
export interface I18nProviderProps {
|
|
143
|
+
children: React.ReactNode
|
|
144
|
+
/** Override or extend the bundled locales. Defaults to all 9 built-ins. */
|
|
145
|
+
locales?: LocaleBundle[]
|
|
146
|
+
defaultLocale?: string
|
|
147
|
+
fallbackLocale?: string
|
|
148
|
+
metadataTranslations?: MetadataTranslations
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function I18nProvider({
|
|
152
|
+
children,
|
|
153
|
+
locales = builtinLocales,
|
|
154
|
+
defaultLocale,
|
|
155
|
+
fallbackLocale = 'en',
|
|
156
|
+
metadataTranslations,
|
|
157
|
+
}: I18nProviderProps): React.ReactElement {
|
|
158
|
+
const i18n = React.useMemo(() => {
|
|
159
|
+
const initial =
|
|
160
|
+
(typeof localStorage !== 'undefined' && localStorage.getItem(STORAGE_KEY)) ||
|
|
161
|
+
defaultLocale ||
|
|
162
|
+
locales[0]?.code
|
|
163
|
+
return new I18n({ locales, defaultLocale: initial ?? undefined, fallbackLocale })
|
|
164
|
+
}, [locales, defaultLocale, fallbackLocale])
|
|
165
|
+
|
|
166
|
+
const [locale, setLocaleState] = React.useState(() => i18n.locale)
|
|
167
|
+
|
|
168
|
+
const localizeResource = React.useCallback(
|
|
169
|
+
(resource: ResourceJSON): ResourceJSON => {
|
|
170
|
+
const localeMeta = metadataTranslations?.[locale]
|
|
171
|
+
const fallbackMeta = metadataTranslations?.[fallbackLocale]
|
|
172
|
+
const resourceLocale = localeMeta?.resources?.[resource.id]
|
|
173
|
+
const resourceFallback = fallbackMeta?.resources?.[resource.id]
|
|
174
|
+
const localizedName =
|
|
175
|
+
firstDefined(resourceLocale?.label, resourceLocale?.name, resourceFallback?.label, resourceFallback?.name, resource.name) ??
|
|
176
|
+
resource.name
|
|
177
|
+
const group = resource.navigation?.group
|
|
178
|
+
return {
|
|
179
|
+
...resource,
|
|
180
|
+
name: localizedName,
|
|
181
|
+
navigation:
|
|
182
|
+
resource.navigation === null
|
|
183
|
+
? null
|
|
184
|
+
: resource.navigation
|
|
185
|
+
? {
|
|
186
|
+
...resource.navigation,
|
|
187
|
+
name: firstDefined(
|
|
188
|
+
resourceLocale?.navigation?.name,
|
|
189
|
+
resourceFallback?.navigation?.name,
|
|
190
|
+
resource.navigation.name,
|
|
191
|
+
),
|
|
192
|
+
group: firstDefined(
|
|
193
|
+
resourceLocale?.navigation?.group,
|
|
194
|
+
resourceFallback?.navigation?.group,
|
|
195
|
+
group ? localeMeta?.navigation?.groups?.[group] : undefined,
|
|
196
|
+
group ? fallbackMeta?.navigation?.groups?.[group] : undefined,
|
|
197
|
+
resource.navigation.group,
|
|
198
|
+
),
|
|
199
|
+
}
|
|
200
|
+
: resource.navigation,
|
|
201
|
+
properties: resource.properties.map((property) =>
|
|
202
|
+
localizeProperty(
|
|
203
|
+
property,
|
|
204
|
+
resourceLocale?.properties?.[property.path],
|
|
205
|
+
localeMeta?.properties?.[property.path],
|
|
206
|
+
resourceFallback?.properties?.[property.path],
|
|
207
|
+
fallbackMeta?.properties?.[property.path],
|
|
208
|
+
),
|
|
209
|
+
),
|
|
210
|
+
actions: resource.actions.map((action) => {
|
|
211
|
+
const localizedLabel = firstDefined(
|
|
212
|
+
resourceLocale?.actions?.[action.name]?.label,
|
|
213
|
+
resourceFallback?.actions?.[action.name]?.label,
|
|
214
|
+
)
|
|
215
|
+
if (localizedLabel === undefined) return action
|
|
216
|
+
return {
|
|
217
|
+
...action,
|
|
218
|
+
custom: { ...(action.custom ?? {}), label: localizedLabel },
|
|
219
|
+
}
|
|
220
|
+
}),
|
|
221
|
+
relatedResources: localizeRelatedResources(
|
|
222
|
+
resource.relatedResources,
|
|
223
|
+
resourceLocale?.relatedResources,
|
|
224
|
+
resourceFallback?.relatedResources,
|
|
225
|
+
),
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
[fallbackLocale, locale, metadataTranslations],
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
const setLocale = React.useCallback(
|
|
232
|
+
(code: string) => {
|
|
233
|
+
i18n.setLocale(code)
|
|
234
|
+
if (typeof localStorage !== 'undefined') localStorage.setItem(STORAGE_KEY, code)
|
|
235
|
+
setLocaleState(i18n.locale)
|
|
236
|
+
},
|
|
237
|
+
[i18n],
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
const value = React.useMemo<I18nContextValue>(
|
|
241
|
+
() => ({
|
|
242
|
+
locale,
|
|
243
|
+
setLocale,
|
|
244
|
+
t: (key, params) => i18n.t(key, params),
|
|
245
|
+
availableLocales: () => i18n.availableLocales(),
|
|
246
|
+
localizeResource,
|
|
247
|
+
}),
|
|
248
|
+
[locale, setLocale, i18n, localizeResource],
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Subscribe to the active locale + translations. Falls back to a no-op
|
|
255
|
+
* implementation when no provider is mounted, so consumers can render
|
|
256
|
+
* without forcing apps to install i18n. */
|
|
257
|
+
export function useI18n(): I18nContextValue {
|
|
258
|
+
const ctx = React.useContext(I18nContext)
|
|
259
|
+
if (ctx) return ctx
|
|
260
|
+
return {
|
|
261
|
+
locale: 'en',
|
|
262
|
+
setLocale: () => {},
|
|
263
|
+
t: (k) => k,
|
|
264
|
+
availableLocales: () => [{ code: 'en', name: 'English' }],
|
|
265
|
+
localizeResource: (resource) => resource,
|
|
266
|
+
}
|
|
267
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// @modern-admin/react — React-side runtime: provider, hooks, property-type
|
|
2
|
+
// renderers, ComponentLoader, and a default <AdminApp> shell.
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
AdminClient,
|
|
6
|
+
AdminApiError,
|
|
7
|
+
type AdminClientOptions,
|
|
8
|
+
type AuthUiProps,
|
|
9
|
+
type AiAssistantChatEnqueueResponse,
|
|
10
|
+
type AiAssistantChatMessage,
|
|
11
|
+
type AiAssistantChatResponse,
|
|
12
|
+
type AiAssistantCitation,
|
|
13
|
+
type AiAssistantSettings,
|
|
14
|
+
type AiAssistantTask,
|
|
15
|
+
type ApiKeyRecord,
|
|
16
|
+
type UploadedFileInfo,
|
|
17
|
+
type UploadProgress,
|
|
18
|
+
type UploadFileOptions,
|
|
19
|
+
type UploadFilesOptions,
|
|
20
|
+
} from './client.js'
|
|
21
|
+
export { ComponentLoader, type ComponentEntry } from './component-loader.js'
|
|
22
|
+
export {
|
|
23
|
+
registerSidebarItem,
|
|
24
|
+
registerSettingsSection,
|
|
25
|
+
registerPropertyExtension,
|
|
26
|
+
registerExtensionRoute,
|
|
27
|
+
getSidebarExtensions,
|
|
28
|
+
getSettingsSectionExtensions,
|
|
29
|
+
getPropertyExtension,
|
|
30
|
+
getRouteExtension,
|
|
31
|
+
_resetExtensionRegistry,
|
|
32
|
+
type SidebarExtension,
|
|
33
|
+
type SettingsExtension,
|
|
34
|
+
type PropertyExtension,
|
|
35
|
+
type RouteExtension,
|
|
36
|
+
} from './extension-registry.js'
|
|
37
|
+
export {
|
|
38
|
+
ModernAdminProvider,
|
|
39
|
+
useAdminClient,
|
|
40
|
+
useAdminContext,
|
|
41
|
+
type ModernAdminProviderProps,
|
|
42
|
+
} from './provider.js'
|
|
43
|
+
export {
|
|
44
|
+
useAdminConfig,
|
|
45
|
+
useFeatures,
|
|
46
|
+
useResource,
|
|
47
|
+
useResources,
|
|
48
|
+
useRecords,
|
|
49
|
+
useRecord,
|
|
50
|
+
useCreateRecord,
|
|
51
|
+
useUpdateRecord,
|
|
52
|
+
useDeleteRecord,
|
|
53
|
+
useBulkDeleteRecords,
|
|
54
|
+
useInvokeResourceAction,
|
|
55
|
+
useSearchRecords,
|
|
56
|
+
useCurrentUser,
|
|
57
|
+
useLogin,
|
|
58
|
+
useLogout,
|
|
59
|
+
useAuthUiProps,
|
|
60
|
+
useSocialLogin,
|
|
61
|
+
type CurrentUserResult,
|
|
62
|
+
} from './hooks.js'
|
|
63
|
+
export {
|
|
64
|
+
PropertyDisplay,
|
|
65
|
+
PropertyEditor,
|
|
66
|
+
type PropertyDisplayProps,
|
|
67
|
+
type PropertyEditorProps,
|
|
68
|
+
} from './property-renderer.js'
|
|
69
|
+
export {
|
|
70
|
+
ReferenceLink,
|
|
71
|
+
ReferenceLinkList,
|
|
72
|
+
ReferenceCombobox,
|
|
73
|
+
ReferenceMultiCombobox,
|
|
74
|
+
} from './reference.js'
|
|
75
|
+
export {
|
|
76
|
+
I18nProvider,
|
|
77
|
+
useI18n,
|
|
78
|
+
type I18nProviderProps,
|
|
79
|
+
type MetadataKeyValueFieldTranslations,
|
|
80
|
+
type MetadataPropertyTranslations,
|
|
81
|
+
type MetadataActionTranslations,
|
|
82
|
+
type MetadataResourceTranslations,
|
|
83
|
+
type MetadataLocaleTranslations,
|
|
84
|
+
type MetadataTranslations,
|
|
85
|
+
} from './i18n.js'
|
|
86
|
+
export { useHotkey, type HotkeyOptions } from './use-hotkey.js'
|
|
87
|
+
export {
|
|
88
|
+
HotkeyRegistryProvider,
|
|
89
|
+
useRegisteredHotkeys,
|
|
90
|
+
type HotkeyDescriptor,
|
|
91
|
+
} from './hotkey-registry.js'
|
|
92
|
+
export { HotkeyHelpButton } from './hotkey-help.js'
|
|
93
|
+
export { ThemeToggle, LanguageSwitcher } from './header-controls.js'
|
|
94
|
+
export { NotifyToaster, useNotify, type NotifyMessage } from './notify.js'
|
|
95
|
+
export {
|
|
96
|
+
DialogsProvider,
|
|
97
|
+
useDialogs,
|
|
98
|
+
type DialogsApi,
|
|
99
|
+
type ConfirmOptions,
|
|
100
|
+
type AlertOptions,
|
|
101
|
+
type OpenOptions,
|
|
102
|
+
type DialogsProviderProps,
|
|
103
|
+
} from './dialogs.js'
|
|
104
|
+
export { confirmGuard } from './action-guard.js'
|
|
105
|
+
export {
|
|
106
|
+
buildValidationSchema,
|
|
107
|
+
buildPropertySchema,
|
|
108
|
+
defaultValueFor,
|
|
109
|
+
type FormValuesGetter,
|
|
110
|
+
type Translator,
|
|
111
|
+
} from './validation.js'
|
|
112
|
+
export { evaluateShowWhen } from './show-when.js'
|
|
113
|
+
export {
|
|
114
|
+
Link,
|
|
115
|
+
useRoute,
|
|
116
|
+
useNavigate,
|
|
117
|
+
useOpenInNewTab,
|
|
118
|
+
buildHref,
|
|
119
|
+
parseLocation,
|
|
120
|
+
type Route,
|
|
121
|
+
type ListQueryState,
|
|
122
|
+
type LinkProps,
|
|
123
|
+
} from './router.js'
|
|
124
|
+
export { AdminApp, type AdminAppProps } from './admin-app.js'
|
|
125
|
+
export { LoginPage, type LoginPageProps } from './pages/login-page.js'
|
|
126
|
+
export {
|
|
127
|
+
useRealtimeInvalidation,
|
|
128
|
+
applyDeletionLocally,
|
|
129
|
+
type RealtimeSubscriber,
|
|
130
|
+
type RealtimeWireEvent,
|
|
131
|
+
} from './realtime.js'
|
|
132
|
+
export { ResourceListPage } from './pages/list-page.js'
|
|
133
|
+
export { ResourceShowPage } from './pages/show-page.js'
|
|
134
|
+
export { ResourceEditPage } from './pages/edit-page.js'
|
|
135
|
+
export {
|
|
136
|
+
ResourceWizardCreatePage,
|
|
137
|
+
type ResourceWizardCreatePageProps,
|
|
138
|
+
} from './pages/wizard-create-page.js'
|
|
139
|
+
export {
|
|
140
|
+
WizardForm,
|
|
141
|
+
type WizardStep,
|
|
142
|
+
type WizardFormLabels,
|
|
143
|
+
type WizardFormProps,
|
|
144
|
+
} from './components/wizard-form.js'
|
|
145
|
+
export { HomePage } from './pages/home-page.js'
|
|
146
|
+
export { ExportDialog, type ExportDialogProps } from './pages/export-dialog.js'
|
|
147
|
+
export {
|
|
148
|
+
PageBreadcrumbs,
|
|
149
|
+
homeCrumb,
|
|
150
|
+
type BreadcrumbItemSpec,
|
|
151
|
+
type PageBreadcrumbsProps,
|
|
152
|
+
} from './breadcrumbs.js'
|
|
153
|
+
export {
|
|
154
|
+
fetchAllRecords,
|
|
155
|
+
recordsToCsv,
|
|
156
|
+
recordsToJson,
|
|
157
|
+
csvEscape,
|
|
158
|
+
downloadText,
|
|
159
|
+
exportFilename,
|
|
160
|
+
type ExportFormat,
|
|
161
|
+
type FetchAllOptions,
|
|
162
|
+
type SerializeOptions,
|
|
163
|
+
} from './export.js'
|
|
164
|
+
export type {
|
|
165
|
+
AdminConfig,
|
|
166
|
+
AdminFeatures,
|
|
167
|
+
ActionDescriptor,
|
|
168
|
+
CurrentUser,
|
|
169
|
+
ListQuery,
|
|
170
|
+
ListResponse,
|
|
171
|
+
PropertyJSON,
|
|
172
|
+
RecordJSON,
|
|
173
|
+
RecordResponse,
|
|
174
|
+
RelatedResource,
|
|
175
|
+
ResourceJSON,
|
|
176
|
+
ShowWhenSpec,
|
|
177
|
+
View,
|
|
178
|
+
} from './types.js'
|
|
179
|
+
export { RelatedRecordsTabs } from './components/related-records-tabs.js'
|
|
180
|
+
export {
|
|
181
|
+
ReferenceMultiTableDialog,
|
|
182
|
+
type ReferenceMultiTableDialogProps,
|
|
183
|
+
} from './components/reference-multi-table-dialog.js'
|
|
184
|
+
export {
|
|
185
|
+
LocalStorageDashboardStore,
|
|
186
|
+
ServerDashboardStore,
|
|
187
|
+
useDashboardCharts,
|
|
188
|
+
resolveRange,
|
|
189
|
+
emitDashboardReload,
|
|
190
|
+
type UseDashboardChartsOptions,
|
|
191
|
+
type UseDashboardChartsResult,
|
|
192
|
+
} from './use-dashboard-charts.js'
|
package/src/notify.tsx
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Global, i18n-aware toast notifications.
|
|
2
|
+
//
|
|
3
|
+
// `useNotify()` returns a typed surface that translates message keys before
|
|
4
|
+
// handing them to sonner. Mount `<NotifyToaster />` once at the app root —
|
|
5
|
+
// it wraps the shadcn-flavoured `<Toaster />` with sensible defaults.
|
|
6
|
+
// Helpers also accept raw strings (already-translated) for cases where the
|
|
7
|
+
// caller has the literal text on hand (server error messages, etc.).
|
|
8
|
+
|
|
9
|
+
import * as React from 'react'
|
|
10
|
+
import { Toaster, toast, type ToasterProps } from '@modern-admin/ui'
|
|
11
|
+
import { useI18n } from './i18n.js'
|
|
12
|
+
|
|
13
|
+
export interface NotifyMessage {
|
|
14
|
+
/** i18n key, e.g. 'toast:saved' */
|
|
15
|
+
key?: string
|
|
16
|
+
/** Already-translated literal text (used as-is when present). */
|
|
17
|
+
message?: string
|
|
18
|
+
/** Interpolation params for the `key` lookup. */
|
|
19
|
+
params?: Record<string, unknown>
|
|
20
|
+
/** Optional secondary line. */
|
|
21
|
+
description?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type Input = string | NotifyMessage
|
|
25
|
+
|
|
26
|
+
interface NotifyApi {
|
|
27
|
+
success(input: Input, opts?: { description?: string }): void
|
|
28
|
+
error(input: Input, opts?: { description?: string }): void
|
|
29
|
+
info(input: Input, opts?: { description?: string }): void
|
|
30
|
+
warning(input: Input, opts?: { description?: string }): void
|
|
31
|
+
/** Show loading -> success/error around a promise. Strings are i18n keys. */
|
|
32
|
+
promise<T>(
|
|
33
|
+
p: Promise<T>,
|
|
34
|
+
messages: { loading: Input; success: Input | ((value: T) => Input); error: Input | ((err: unknown) => Input) },
|
|
35
|
+
): Promise<T>
|
|
36
|
+
/** Escape hatch for callers that want raw sonner access. */
|
|
37
|
+
raw: typeof toast
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The global toaster. Mount once near the root of the admin shell. */
|
|
41
|
+
export function NotifyToaster(props: ToasterProps): React.ReactElement {
|
|
42
|
+
return <Toaster richColors closeButton position="top-right" {...props} />
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Hook returning a translation-aware notification surface. */
|
|
46
|
+
export function useNotify(): NotifyApi {
|
|
47
|
+
const { t } = useI18n()
|
|
48
|
+
|
|
49
|
+
const resolve = React.useCallback(
|
|
50
|
+
(input: Input): { title: string; description?: string } => {
|
|
51
|
+
if (typeof input === 'string') return { title: input }
|
|
52
|
+
const title = input.key ? t(input.key, input.params) : (input.message ?? '')
|
|
53
|
+
return input.description ? { title, description: input.description } : { title }
|
|
54
|
+
},
|
|
55
|
+
[t],
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return React.useMemo<NotifyApi>(
|
|
59
|
+
() => ({
|
|
60
|
+
success: (input, opts) => {
|
|
61
|
+
const r = resolve(input)
|
|
62
|
+
toast.success(r.title, { description: opts?.description ?? r.description })
|
|
63
|
+
},
|
|
64
|
+
error: (input, opts) => {
|
|
65
|
+
const r = resolve(input)
|
|
66
|
+
toast.error(r.title, { description: opts?.description ?? r.description })
|
|
67
|
+
},
|
|
68
|
+
info: (input, opts) => {
|
|
69
|
+
const r = resolve(input)
|
|
70
|
+
toast.info(r.title, { description: opts?.description ?? r.description })
|
|
71
|
+
},
|
|
72
|
+
warning: (input, opts) => {
|
|
73
|
+
const r = resolve(input)
|
|
74
|
+
toast.warning(r.title, { description: opts?.description ?? r.description })
|
|
75
|
+
},
|
|
76
|
+
promise: (p, messages) => {
|
|
77
|
+
toast.promise(p, {
|
|
78
|
+
loading: resolve(messages.loading).title,
|
|
79
|
+
success: (value) => {
|
|
80
|
+
const m = typeof messages.success === 'function' ? messages.success(value) : messages.success
|
|
81
|
+
return resolve(m).title
|
|
82
|
+
},
|
|
83
|
+
error: (err) => {
|
|
84
|
+
const m = typeof messages.error === 'function' ? messages.error(err) : messages.error
|
|
85
|
+
return resolve(m).title
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
return p
|
|
89
|
+
},
|
|
90
|
+
raw: toast,
|
|
91
|
+
}),
|
|
92
|
+
[resolve],
|
|
93
|
+
)
|
|
94
|
+
}
|