@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
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// UI Extension Registry — lets Pro plugins add sidebar items, settings
|
|
2
|
+
// sections, custom property renderers, and full-page routes without
|
|
3
|
+
// modifying `packages/react` directly.
|
|
4
|
+
//
|
|
5
|
+
// Usage: call `register*` functions during module initialisation (before
|
|
6
|
+
// `<AdminApp>` renders). Every consumer of `@modern-admin/react` can import
|
|
7
|
+
// and call these helpers; the registry is a process-global singleton.
|
|
8
|
+
//
|
|
9
|
+
// Design notes:
|
|
10
|
+
// • Module-global (not React-context-based) so registration can happen in
|
|
11
|
+
// plain JS outside any component tree.
|
|
12
|
+
// • Idempotent: re-registering the same `key` is silently ignored so hot-
|
|
13
|
+
// reload doesn't produce duplicates.
|
|
14
|
+
// • Zero external runtime deps — only imports prop-type interfaces from
|
|
15
|
+
// `./types.ts` which is already bundled.
|
|
16
|
+
|
|
17
|
+
import type * as React from 'react'
|
|
18
|
+
import type { PropertyDisplayProps, PropertyEditorProps } from './types.js'
|
|
19
|
+
|
|
20
|
+
// ─── Extension shapes ─────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Extra navigation item rendered in the sidebar below the built-in entries
|
|
24
|
+
* (Home, Audit Log) and above the resource list.
|
|
25
|
+
*/
|
|
26
|
+
export interface SidebarExtension {
|
|
27
|
+
/** Stable unique key (e.g. `'rbac'`). Duplicate keys are silently ignored. */
|
|
28
|
+
key: string
|
|
29
|
+
/** Label rendered next to the icon in the sidebar. */
|
|
30
|
+
label: string
|
|
31
|
+
/** Lucide-compatible icon component. */
|
|
32
|
+
icon: React.ComponentType<{ className?: string }>
|
|
33
|
+
/**
|
|
34
|
+
* Extension route key — navigates to `/ext/<extensionKey>` within the
|
|
35
|
+
* admin shell (i.e., `{ name: 'extension', key: extensionKey }`).
|
|
36
|
+
* Must match the `key` of a corresponding `registerExtensionRoute` call
|
|
37
|
+
* so the router can render the right component.
|
|
38
|
+
*/
|
|
39
|
+
extensionKey: string
|
|
40
|
+
/**
|
|
41
|
+
* Optional capability gate. When set, the item is rendered only when
|
|
42
|
+
* `features[featureGate] === true` in the admin config (i.e. the backend
|
|
43
|
+
* has explicitly enabled the subsystem). Leave unset to always show.
|
|
44
|
+
*/
|
|
45
|
+
featureGate?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Extra section added to the Settings page navigation. The consumer's
|
|
50
|
+
* `component` is rendered as the main content area when the section is active.
|
|
51
|
+
*/
|
|
52
|
+
export interface SettingsExtension {
|
|
53
|
+
/**
|
|
54
|
+
* Unique section key (appears in the URL as `/settings/<key>`). Must be
|
|
55
|
+
* URL-safe (no slashes, encodes cleanly). Duplicate keys are ignored.
|
|
56
|
+
*/
|
|
57
|
+
key: string
|
|
58
|
+
/**
|
|
59
|
+
* i18n translation key for the section label shown in the settings nav
|
|
60
|
+
* (e.g. `'rbac:settings.title'`). Falls back to `key` if the key is missing
|
|
61
|
+
* from the active locale.
|
|
62
|
+
*/
|
|
63
|
+
labelKey: string
|
|
64
|
+
/** Lucide-compatible icon component shown next to the label. */
|
|
65
|
+
icon: React.ComponentType<{ className?: string }>
|
|
66
|
+
/**
|
|
67
|
+
* Component rendered as the main content when this section is active.
|
|
68
|
+
* It receives no props — read what you need via hooks or internal state.
|
|
69
|
+
*/
|
|
70
|
+
component: React.ComponentType
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Custom property type renderer pair. When registered for `type`, the
|
|
75
|
+
* built-in `PropertyDisplay` and `PropertyEditor` switches fall through
|
|
76
|
+
* to this extension instead of rendering the default plain-text fallback.
|
|
77
|
+
*/
|
|
78
|
+
export interface PropertyExtension {
|
|
79
|
+
/** Renders a read-only value cell (list / show view). */
|
|
80
|
+
display: React.ComponentType<PropertyDisplayProps>
|
|
81
|
+
/** Renders an editable form field (edit / new view). */
|
|
82
|
+
editor: React.ComponentType<PropertyEditorProps>
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Full-page route rendered inside the authenticated admin shell at the
|
|
87
|
+
* reserved path `/ext/<key>`.
|
|
88
|
+
*/
|
|
89
|
+
export interface RouteExtension {
|
|
90
|
+
/**
|
|
91
|
+
* URL-safe key (no slashes). The route becomes `/ext/<key>`. Must be
|
|
92
|
+
* unique — duplicate keys are silently ignored.
|
|
93
|
+
*/
|
|
94
|
+
key: string
|
|
95
|
+
/** Component rendered as the page content inside the shell layout. */
|
|
96
|
+
component: React.ComponentType
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ─── Internal registry ────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
interface RegistryData {
|
|
102
|
+
sidebarItems: SidebarExtension[]
|
|
103
|
+
settingsSections: SettingsExtension[]
|
|
104
|
+
propertyEditors: Map<string, PropertyExtension>
|
|
105
|
+
routes: RouteExtension[]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const registry: RegistryData = {
|
|
109
|
+
sidebarItems: [],
|
|
110
|
+
settingsSections: [],
|
|
111
|
+
propertyEditors: new Map(),
|
|
112
|
+
routes: [],
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─── Registration API ─────────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Register a sidebar navigation item for an extension page.
|
|
119
|
+
* Call this during module initialisation, before `<AdminApp>` renders.
|
|
120
|
+
* Re-registering the same `key` is a no-op.
|
|
121
|
+
*/
|
|
122
|
+
export function registerSidebarItem(ext: SidebarExtension): void {
|
|
123
|
+
if (!registry.sidebarItems.find((e) => e.key === ext.key)) {
|
|
124
|
+
registry.sidebarItems.push(ext)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Register a custom section in the Settings page.
|
|
130
|
+
* Call this during module initialisation, before `<AdminApp>` renders.
|
|
131
|
+
* Re-registering the same `key` is a no-op.
|
|
132
|
+
*/
|
|
133
|
+
export function registerSettingsSection(ext: SettingsExtension): void {
|
|
134
|
+
if (!registry.settingsSections.find((e) => e.key === ext.key)) {
|
|
135
|
+
registry.settingsSections.push(ext)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Register a custom property type renderer (display + editor pair).
|
|
141
|
+
* When `property.type === type`, the built-in switch delegates to this
|
|
142
|
+
* extension instead of the plain-text fallback.
|
|
143
|
+
* Re-registering the same `type` overwrites the previous entry.
|
|
144
|
+
*/
|
|
145
|
+
export function registerPropertyExtension(type: string, ext: PropertyExtension): void {
|
|
146
|
+
registry.propertyEditors.set(type, ext)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Register a full-page extension route at `/ext/<key>` inside the admin
|
|
151
|
+
* shell. A matching `SidebarExtension` with `extensionKey === key` provides
|
|
152
|
+
* the navigation entry.
|
|
153
|
+
* Re-registering the same `key` is a no-op.
|
|
154
|
+
*/
|
|
155
|
+
export function registerExtensionRoute(ext: RouteExtension): void {
|
|
156
|
+
if (!registry.routes.find((r) => r.key === ext.key)) {
|
|
157
|
+
registry.routes.push(ext)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ─── Accessors (consumed by shell components at render time) ──────────────────
|
|
162
|
+
|
|
163
|
+
/** Returns all registered sidebar extensions in registration order. */
|
|
164
|
+
export function getSidebarExtensions(): SidebarExtension[] {
|
|
165
|
+
return registry.sidebarItems
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Returns all registered settings sections in registration order. */
|
|
169
|
+
export function getSettingsSectionExtensions(): SettingsExtension[] {
|
|
170
|
+
return registry.settingsSections
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Returns the registered property extension for `type`, or `undefined` if
|
|
175
|
+
* no extension covers it (fall through to plain-text).
|
|
176
|
+
*/
|
|
177
|
+
export function getPropertyExtension(type: string): PropertyExtension | undefined {
|
|
178
|
+
return registry.propertyEditors.get(type)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Returns the registered route extension for `key`, or `undefined`. */
|
|
182
|
+
export function getRouteExtension(key: string): RouteExtension | undefined {
|
|
183
|
+
return registry.routes.find((r) => r.key === key)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Clears all registrations.
|
|
188
|
+
* @internal For unit tests only — do not call in production code.
|
|
189
|
+
*/
|
|
190
|
+
export function _resetExtensionRegistry(): void {
|
|
191
|
+
registry.sidebarItems = []
|
|
192
|
+
registry.settingsSections = []
|
|
193
|
+
registry.propertyEditors.clear()
|
|
194
|
+
registry.routes = []
|
|
195
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Theme + language switchers for the admin header. Both render as
|
|
2
|
+
// dropdown menus using shadcn primitives and reflect persistent state
|
|
3
|
+
// (theme via @modern-admin/ui's lib/theme; locale via I18nProvider).
|
|
4
|
+
|
|
5
|
+
import * as React from 'react'
|
|
6
|
+
import {
|
|
7
|
+
Button,
|
|
8
|
+
DropdownMenu,
|
|
9
|
+
DropdownMenuContent,
|
|
10
|
+
DropdownMenuItem,
|
|
11
|
+
DropdownMenuLabel,
|
|
12
|
+
DropdownMenuSeparator,
|
|
13
|
+
DropdownMenuTrigger,
|
|
14
|
+
ScrollArea,
|
|
15
|
+
readThemeMode,
|
|
16
|
+
setThemeMode,
|
|
17
|
+
type ThemeMode,
|
|
18
|
+
} from '@modern-admin/ui'
|
|
19
|
+
import { Check, Languages, Monitor, Moon, Sun } from 'lucide-react'
|
|
20
|
+
import { useI18n } from './i18n.js'
|
|
21
|
+
|
|
22
|
+
/** Single dropdown row with a leading icon, label, and trailing check when active. */
|
|
23
|
+
function MenuOption({
|
|
24
|
+
icon,
|
|
25
|
+
label,
|
|
26
|
+
active,
|
|
27
|
+
onSelect,
|
|
28
|
+
}: {
|
|
29
|
+
icon?: React.ReactNode
|
|
30
|
+
label: React.ReactNode
|
|
31
|
+
active: boolean
|
|
32
|
+
onSelect(): void
|
|
33
|
+
}): React.ReactElement {
|
|
34
|
+
return (
|
|
35
|
+
<DropdownMenuItem
|
|
36
|
+
onSelect={(e) => {
|
|
37
|
+
e.preventDefault()
|
|
38
|
+
onSelect()
|
|
39
|
+
}}
|
|
40
|
+
className="gap-3 px-3 py-2"
|
|
41
|
+
>
|
|
42
|
+
{icon && <span className="flex size-4 items-center justify-center text-muted-foreground">{icon}</span>}
|
|
43
|
+
<span className="flex-1 truncate">{label}</span>
|
|
44
|
+
{active && <Check className="size-4 text-primary" />}
|
|
45
|
+
</DropdownMenuItem>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function ThemeToggle(): React.ReactElement {
|
|
50
|
+
const { t } = useI18n()
|
|
51
|
+
const [mode, setMode] = React.useState<ThemeMode>(() => readThemeMode())
|
|
52
|
+
const apply = (next: ThemeMode): void => {
|
|
53
|
+
setThemeMode(next)
|
|
54
|
+
setMode(next)
|
|
55
|
+
}
|
|
56
|
+
const Icon = mode === 'dark' ? Moon : mode === 'light' ? Sun : Monitor
|
|
57
|
+
return (
|
|
58
|
+
<DropdownMenu>
|
|
59
|
+
<DropdownMenuTrigger asChild>
|
|
60
|
+
<Button variant="ghost" size="icon" aria-label={t('common:toggleTheme')}>
|
|
61
|
+
<Icon className="size-4" />
|
|
62
|
+
</Button>
|
|
63
|
+
</DropdownMenuTrigger>
|
|
64
|
+
<DropdownMenuContent align="end" sideOffset={8} className="w-44 p-1">
|
|
65
|
+
<DropdownMenuLabel className="px-3 py-2 text-xs uppercase tracking-wide text-muted-foreground">
|
|
66
|
+
{t('common:theme')}
|
|
67
|
+
</DropdownMenuLabel>
|
|
68
|
+
<DropdownMenuSeparator />
|
|
69
|
+
<MenuOption
|
|
70
|
+
icon={<Sun className="size-4" />}
|
|
71
|
+
label={t('common:themeLight')}
|
|
72
|
+
active={mode === 'light'}
|
|
73
|
+
onSelect={() => apply('light')}
|
|
74
|
+
/>
|
|
75
|
+
<MenuOption
|
|
76
|
+
icon={<Moon className="size-4" />}
|
|
77
|
+
label={t('common:themeDark')}
|
|
78
|
+
active={mode === 'dark'}
|
|
79
|
+
onSelect={() => apply('dark')}
|
|
80
|
+
/>
|
|
81
|
+
<MenuOption
|
|
82
|
+
icon={<Monitor className="size-4" />}
|
|
83
|
+
label={t('common:themeSystem')}
|
|
84
|
+
active={mode === 'system'}
|
|
85
|
+
onSelect={() => apply('system')}
|
|
86
|
+
/>
|
|
87
|
+
</DropdownMenuContent>
|
|
88
|
+
</DropdownMenu>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function LanguageSwitcher(): React.ReactElement | null {
|
|
93
|
+
const { locale, setLocale, availableLocales, t } = useI18n()
|
|
94
|
+
const locales = availableLocales()
|
|
95
|
+
// With a single registered locale there is nothing to switch between —
|
|
96
|
+
// hide the control entirely (consumers control the set via I18nProvider's
|
|
97
|
+
// `locales` prop, which the standalone shell wires from runtime config).
|
|
98
|
+
if (locales.length <= 1) return null
|
|
99
|
+
return (
|
|
100
|
+
<DropdownMenu>
|
|
101
|
+
<DropdownMenuTrigger asChild>
|
|
102
|
+
<Button variant="ghost" size="sm" className="gap-2">
|
|
103
|
+
<Languages className="size-4" />
|
|
104
|
+
<span className="text-xs uppercase">{locale}</span>
|
|
105
|
+
</Button>
|
|
106
|
+
</DropdownMenuTrigger>
|
|
107
|
+
<DropdownMenuContent align="end" sideOffset={8} className="w-52 p-1">
|
|
108
|
+
<DropdownMenuLabel className="px-3 py-2 text-xs uppercase tracking-wide text-muted-foreground">
|
|
109
|
+
{t('common:language')}
|
|
110
|
+
</DropdownMenuLabel>
|
|
111
|
+
<DropdownMenuSeparator />
|
|
112
|
+
<ScrollArea className="max-h-72">
|
|
113
|
+
{locales.map((l) => (
|
|
114
|
+
<MenuOption
|
|
115
|
+
key={l.code}
|
|
116
|
+
label={l.name}
|
|
117
|
+
active={l.code === locale}
|
|
118
|
+
onSelect={() => setLocale(l.code)}
|
|
119
|
+
/>
|
|
120
|
+
))}
|
|
121
|
+
</ScrollArea>
|
|
122
|
+
</DropdownMenuContent>
|
|
123
|
+
</DropdownMenu>
|
|
124
|
+
)
|
|
125
|
+
}
|