@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.
Files changed (261) hide show
  1. package/dist/action-guard.d.ts +13 -0
  2. package/dist/action-guard.d.ts.map +1 -0
  3. package/dist/action-guard.js +15 -0
  4. package/dist/action-guard.js.map +1 -0
  5. package/dist/action-menu.d.ts +17 -0
  6. package/dist/action-menu.d.ts.map +1 -0
  7. package/dist/action-menu.jsx +80 -0
  8. package/dist/action-menu.jsx.map +1 -0
  9. package/dist/admin-app.d.ts +23 -0
  10. package/dist/admin-app.d.ts.map +1 -0
  11. package/dist/admin-app.jsx +407 -0
  12. package/dist/admin-app.jsx.map +1 -0
  13. package/dist/admin-router.d.ts +29 -0
  14. package/dist/admin-router.d.ts.map +1 -0
  15. package/dist/admin-router.jsx +215 -0
  16. package/dist/admin-router.jsx.map +1 -0
  17. package/dist/breadcrumbs.d.ts +17 -0
  18. package/dist/breadcrumbs.d.ts.map +1 -0
  19. package/dist/breadcrumbs.jsx +40 -0
  20. package/dist/breadcrumbs.jsx.map +1 -0
  21. package/dist/client.d.ts +526 -0
  22. package/dist/client.d.ts.map +1 -0
  23. package/dist/client.js +582 -0
  24. package/dist/client.js.map +1 -0
  25. package/dist/component-loader.d.ts +10 -0
  26. package/dist/component-loader.d.ts.map +1 -0
  27. package/dist/component-loader.js +23 -0
  28. package/dist/component-loader.js.map +1 -0
  29. package/dist/components/ai-assistant-widget.d.ts +3 -0
  30. package/dist/components/ai-assistant-widget.d.ts.map +1 -0
  31. package/dist/components/ai-assistant-widget.jsx +390 -0
  32. package/dist/components/ai-assistant-widget.jsx.map +1 -0
  33. package/dist/components/ai-fill-dialog.d.ts +9 -0
  34. package/dist/components/ai-fill-dialog.d.ts.map +1 -0
  35. package/dist/components/ai-fill-dialog.jsx +105 -0
  36. package/dist/components/ai-fill-dialog.jsx.map +1 -0
  37. package/dist/components/chart-builder-dialog.d.ts +10 -0
  38. package/dist/components/chart-builder-dialog.d.ts.map +1 -0
  39. package/dist/components/chart-builder-dialog.jsx +433 -0
  40. package/dist/components/chart-builder-dialog.jsx.map +1 -0
  41. package/dist/components/chart-widget.d.ts +12 -0
  42. package/dist/components/chart-widget.d.ts.map +1 -0
  43. package/dist/components/chart-widget.jsx +365 -0
  44. package/dist/components/chart-widget.jsx.map +1 -0
  45. package/dist/components/global-search-dialog.d.ts +7 -0
  46. package/dist/components/global-search-dialog.d.ts.map +1 -0
  47. package/dist/components/global-search-dialog.jsx +187 -0
  48. package/dist/components/global-search-dialog.jsx.map +1 -0
  49. package/dist/components/group-settings-dialog.d.ts +13 -0
  50. package/dist/components/group-settings-dialog.d.ts.map +1 -0
  51. package/dist/components/group-settings-dialog.jsx +53 -0
  52. package/dist/components/group-settings-dialog.jsx.map +1 -0
  53. package/dist/components/move-chart-dialog.d.ts +18 -0
  54. package/dist/components/move-chart-dialog.d.ts.map +1 -0
  55. package/dist/components/move-chart-dialog.jsx +68 -0
  56. package/dist/components/move-chart-dialog.jsx.map +1 -0
  57. package/dist/components/reference-multi-table-dialog.d.ts +12 -0
  58. package/dist/components/reference-multi-table-dialog.d.ts.map +1 -0
  59. package/dist/components/reference-multi-table-dialog.jsx +126 -0
  60. package/dist/components/reference-multi-table-dialog.jsx.map +1 -0
  61. package/dist/components/related-records-tabs.d.ts +8 -0
  62. package/dist/components/related-records-tabs.d.ts.map +1 -0
  63. package/dist/components/related-records-tabs.jsx +75 -0
  64. package/dist/components/related-records-tabs.jsx.map +1 -0
  65. package/dist/components/revisions-button.d.ts +7 -0
  66. package/dist/components/revisions-button.d.ts.map +1 -0
  67. package/dist/components/revisions-button.jsx +152 -0
  68. package/dist/components/revisions-button.jsx.map +1 -0
  69. package/dist/components/wizard-form.d.ts +43 -0
  70. package/dist/components/wizard-form.d.ts.map +1 -0
  71. package/dist/components/wizard-form.jsx +136 -0
  72. package/dist/components/wizard-form.jsx.map +1 -0
  73. package/dist/dashboard/time-series.d.ts +20 -0
  74. package/dist/dashboard/time-series.d.ts.map +1 -0
  75. package/dist/dashboard/time-series.js +108 -0
  76. package/dist/dashboard/time-series.js.map +1 -0
  77. package/dist/dialogs.d.ts +35 -0
  78. package/dist/dialogs.d.ts.map +1 -0
  79. package/dist/dialogs.jsx +152 -0
  80. package/dist/dialogs.jsx.map +1 -0
  81. package/dist/export.d.ts +39 -0
  82. package/dist/export.d.ts.map +1 -0
  83. package/dist/export.js +114 -0
  84. package/dist/export.js.map +1 -0
  85. package/dist/extension-registry.d.ts +122 -0
  86. package/dist/extension-registry.d.ts.map +1 -0
  87. package/dist/extension-registry.js +93 -0
  88. package/dist/extension-registry.js.map +1 -0
  89. package/dist/header-controls.d.ts +4 -0
  90. package/dist/header-controls.d.ts.map +1 -0
  91. package/dist/header-controls.jsx +70 -0
  92. package/dist/header-controls.jsx.map +1 -0
  93. package/dist/hooks.d.ts +104 -0
  94. package/dist/hooks.d.ts.map +1 -0
  95. package/dist/hooks.js +374 -0
  96. package/dist/hooks.js.map +1 -0
  97. package/dist/hotkey-help.d.ts +3 -0
  98. package/dist/hotkey-help.d.ts.map +1 -0
  99. package/dist/hotkey-help.jsx +32 -0
  100. package/dist/hotkey-help.jsx.map +1 -0
  101. package/dist/hotkey-registry.d.ts +18 -0
  102. package/dist/hotkey-registry.d.ts.map +1 -0
  103. package/dist/hotkey-registry.jsx +34 -0
  104. package/dist/hotkey-registry.jsx.map +1 -0
  105. package/dist/i18n.d.ts +74 -0
  106. package/dist/i18n.d.ts.map +1 -0
  107. package/dist/i18n.jsx +127 -0
  108. package/dist/i18n.jsx.map +1 -0
  109. package/dist/index.d.ts +35 -0
  110. package/dist/index.d.ts.map +1 -0
  111. package/dist/index.js +36 -0
  112. package/dist/index.js.map +1 -0
  113. package/dist/notify.d.ts +41 -0
  114. package/dist/notify.d.ts.map +1 -0
  115. package/dist/notify.jsx +58 -0
  116. package/dist/notify.jsx.map +1 -0
  117. package/dist/pages/ai-assistant-settings-section.d.ts +3 -0
  118. package/dist/pages/ai-assistant-settings-section.d.ts.map +1 -0
  119. package/dist/pages/ai-assistant-settings-section.jsx +126 -0
  120. package/dist/pages/ai-assistant-settings-section.jsx.map +1 -0
  121. package/dist/pages/audit-log-page.d.ts +3 -0
  122. package/dist/pages/audit-log-page.d.ts.map +1 -0
  123. package/dist/pages/audit-log-page.jsx +354 -0
  124. package/dist/pages/audit-log-page.jsx.map +1 -0
  125. package/dist/pages/edit-page.d.ts +7 -0
  126. package/dist/pages/edit-page.d.ts.map +1 -0
  127. package/dist/pages/edit-page.jsx +614 -0
  128. package/dist/pages/edit-page.jsx.map +1 -0
  129. package/dist/pages/export-dialog.d.ts +11 -0
  130. package/dist/pages/export-dialog.d.ts.map +1 -0
  131. package/dist/pages/export-dialog.jsx +102 -0
  132. package/dist/pages/export-dialog.jsx.map +1 -0
  133. package/dist/pages/home-page.d.ts +3 -0
  134. package/dist/pages/home-page.d.ts.map +1 -0
  135. package/dist/pages/home-page.jsx +211 -0
  136. package/dist/pages/home-page.jsx.map +1 -0
  137. package/dist/pages/list-page.d.ts +42 -0
  138. package/dist/pages/list-page.d.ts.map +1 -0
  139. package/dist/pages/list-page.jsx +1596 -0
  140. package/dist/pages/list-page.jsx.map +1 -0
  141. package/dist/pages/login-page.d.ts +11 -0
  142. package/dist/pages/login-page.d.ts.map +1 -0
  143. package/dist/pages/login-page.jsx +157 -0
  144. package/dist/pages/login-page.jsx.map +1 -0
  145. package/dist/pages/settings-page.d.ts +5 -0
  146. package/dist/pages/settings-page.d.ts.map +1 -0
  147. package/dist/pages/settings-page.jsx +787 -0
  148. package/dist/pages/settings-page.jsx.map +1 -0
  149. package/dist/pages/settings-shared.d.ts +51 -0
  150. package/dist/pages/settings-shared.d.ts.map +1 -0
  151. package/dist/pages/settings-shared.jsx +66 -0
  152. package/dist/pages/settings-shared.jsx.map +1 -0
  153. package/dist/pages/show-page.d.ts +7 -0
  154. package/dist/pages/show-page.d.ts.map +1 -0
  155. package/dist/pages/show-page.jsx +147 -0
  156. package/dist/pages/show-page.jsx.map +1 -0
  157. package/dist/pages/wizard-create-page.d.ts +14 -0
  158. package/dist/pages/wizard-create-page.d.ts.map +1 -0
  159. package/dist/pages/wizard-create-page.jsx +106 -0
  160. package/dist/pages/wizard-create-page.jsx.map +1 -0
  161. package/dist/property-renderer.d.ts +8 -0
  162. package/dist/property-renderer.d.ts.map +1 -0
  163. package/dist/property-renderer.jsx +690 -0
  164. package/dist/property-renderer.jsx.map +1 -0
  165. package/dist/provider.d.ts +20 -0
  166. package/dist/provider.d.ts.map +1 -0
  167. package/dist/provider.jsx +32 -0
  168. package/dist/provider.jsx.map +1 -0
  169. package/dist/realtime.d.ts +22 -0
  170. package/dist/realtime.d.ts.map +1 -0
  171. package/dist/realtime.js +38 -0
  172. package/dist/realtime.js.map +1 -0
  173. package/dist/reference.d.ts +52 -0
  174. package/dist/reference.d.ts.map +1 -0
  175. package/dist/reference.jsx +224 -0
  176. package/dist/reference.jsx.map +1 -0
  177. package/dist/relations.d.ts +11 -0
  178. package/dist/relations.d.ts.map +1 -0
  179. package/dist/relations.js +36 -0
  180. package/dist/relations.js.map +1 -0
  181. package/dist/router.d.ts +82 -0
  182. package/dist/router.d.ts.map +1 -0
  183. package/dist/router.jsx +187 -0
  184. package/dist/router.jsx.map +1 -0
  185. package/dist/show-when.d.ts +7 -0
  186. package/dist/show-when.d.ts.map +1 -0
  187. package/dist/show-when.js +77 -0
  188. package/dist/show-when.js.map +1 -0
  189. package/dist/types.d.ts +194 -0
  190. package/dist/types.d.ts.map +1 -0
  191. package/dist/types.js +18 -0
  192. package/dist/types.js.map +1 -0
  193. package/dist/use-dashboard-charts.d.ts +93 -0
  194. package/dist/use-dashboard-charts.d.ts.map +1 -0
  195. package/dist/use-dashboard-charts.js +263 -0
  196. package/dist/use-dashboard-charts.js.map +1 -0
  197. package/dist/use-hotkey.d.ts +17 -0
  198. package/dist/use-hotkey.d.ts.map +1 -0
  199. package/dist/use-hotkey.js +103 -0
  200. package/dist/use-hotkey.js.map +1 -0
  201. package/dist/user-directory.d.ts +18 -0
  202. package/dist/user-directory.d.ts.map +1 -0
  203. package/dist/user-directory.js +51 -0
  204. package/dist/user-directory.js.map +1 -0
  205. package/dist/validation.d.ts +22 -0
  206. package/dist/validation.d.ts.map +1 -0
  207. package/dist/validation.js +338 -0
  208. package/dist/validation.js.map +1 -0
  209. package/package.json +59 -0
  210. package/src/action-guard.ts +20 -0
  211. package/src/action-menu.tsx +161 -0
  212. package/src/admin-app.tsx +630 -0
  213. package/src/admin-router.tsx +273 -0
  214. package/src/breadcrumbs.tsx +75 -0
  215. package/src/client.ts +1093 -0
  216. package/src/component-loader.ts +33 -0
  217. package/src/components/ai-assistant-widget.tsx +565 -0
  218. package/src/components/ai-fill-dialog.tsx +143 -0
  219. package/src/components/chart-builder-dialog.tsx +618 -0
  220. package/src/components/chart-widget.tsx +654 -0
  221. package/src/components/global-search-dialog.tsx +272 -0
  222. package/src/components/group-settings-dialog.tsx +93 -0
  223. package/src/components/move-chart-dialog.tsx +130 -0
  224. package/src/components/reference-multi-table-dialog.tsx +196 -0
  225. package/src/components/related-records-tabs.tsx +130 -0
  226. package/src/components/revisions-button.tsx +237 -0
  227. package/src/components/wizard-form.tsx +302 -0
  228. package/src/dashboard/time-series.ts +125 -0
  229. package/src/dialogs.tsx +265 -0
  230. package/src/export.ts +140 -0
  231. package/src/extension-registry.ts +195 -0
  232. package/src/header-controls.tsx +125 -0
  233. package/src/hooks.ts +509 -0
  234. package/src/hotkey-help.tsx +56 -0
  235. package/src/hotkey-registry.tsx +60 -0
  236. package/src/i18n.tsx +267 -0
  237. package/src/index.ts +192 -0
  238. package/src/notify.tsx +94 -0
  239. package/src/pages/ai-assistant-settings-section.tsx +167 -0
  240. package/src/pages/audit-log-page.tsx +580 -0
  241. package/src/pages/edit-page.tsx +743 -0
  242. package/src/pages/export-dialog.tsx +154 -0
  243. package/src/pages/home-page.tsx +318 -0
  244. package/src/pages/list-page.tsx +2645 -0
  245. package/src/pages/login-page.tsx +242 -0
  246. package/src/pages/settings-page.tsx +1143 -0
  247. package/src/pages/settings-shared.tsx +134 -0
  248. package/src/pages/show-page.tsx +223 -0
  249. package/src/pages/wizard-create-page.tsx +164 -0
  250. package/src/property-renderer.tsx +1143 -0
  251. package/src/provider.tsx +70 -0
  252. package/src/realtime.ts +55 -0
  253. package/src/reference.tsx +386 -0
  254. package/src/relations.ts +55 -0
  255. package/src/router.tsx +211 -0
  256. package/src/show-when.ts +76 -0
  257. package/src/types.ts +198 -0
  258. package/src/use-dashboard-charts.ts +362 -0
  259. package/src/use-hotkey.ts +128 -0
  260. package/src/user-directory.ts +56 -0
  261. 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
+ }