@modern-admin/web 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.
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#2563eb" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <ellipse cx="12" cy="5" rx="9" ry="3"/>
3
+ <path d="M3 5V19A9 3 0 0 0 21 19V5"/>
4
+ <path d="M3 12A9 3 0 0 0 21 12"/>
5
+ </svg>
@@ -0,0 +1,21 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <link rel="icon" type="image/svg+xml" href="./favicon.svg" />
7
+ <title>Modern Admin</title>
8
+ <!--
9
+ The host server (e.g. @modern-admin/nest StaticController) replaces
10
+ this marker with `<script>window.__MODERN_ADMIN__ = {...}</script>`
11
+ before sending the response. When the file is served raw (dev mode,
12
+ smoke tests), the standalone entry falls back to same-origin defaults.
13
+ -->
14
+ <!--MODERN_ADMIN_CONFIG-->
15
+ <script type="module" crossorigin src="./assets/index-Bz-CBvit.js"></script>
16
+ <link rel="stylesheet" crossorigin href="./assets/index-Bq7jl8a_.css">
17
+ </head>
18
+ <body>
19
+ <div id="root"></div>
20
+ </body>
21
+ </html>
package/index.html ADDED
@@ -0,0 +1,20 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
+ <title>Modern Admin</title>
8
+ <!--
9
+ The host server (e.g. @modern-admin/nest StaticController) replaces
10
+ this marker with `<script>window.__MODERN_ADMIN__ = {...}</script>`
11
+ before sending the response. When the file is served raw (dev mode,
12
+ smoke tests), the standalone entry falls back to same-origin defaults.
13
+ -->
14
+ <!--MODERN_ADMIN_CONFIG-->
15
+ </head>
16
+ <body>
17
+ <div id="root"></div>
18
+ <script type="module" src="/src/standalone.tsx"></script>
19
+ </body>
20
+ </html>
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@modern-admin/web",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Pre-built React admin SPA. Mount it into a host page or let @modern-admin/nest serve the standalone bundle.",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/modern-admin/modern-admin.git",
10
+ "directory": "packages/web"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "src",
15
+ "index.html",
16
+ "vite.config.ts",
17
+ "tsconfig.json"
18
+ ],
19
+ "main": "./dist/lib/index.js",
20
+ "types": "./dist/lib/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/lib/index.d.ts",
24
+ "default": "./dist/lib/index.js"
25
+ },
26
+ "./standalone/*": "./dist/standalone/*",
27
+ "./package.json": "./package.json"
28
+ },
29
+ "publishConfig": {
30
+ "registry": "https://registry.npmjs.org",
31
+ "access": "public",
32
+ "main": "./dist/lib/index.js",
33
+ "types": "./dist/lib/index.d.ts",
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/lib/index.d.ts",
37
+ "default": "./dist/lib/index.js"
38
+ },
39
+ "./standalone/*": "./dist/standalone/*",
40
+ "./package.json": "./package.json"
41
+ }
42
+ },
43
+ "peerDependencies": {
44
+ "react": "^19.2.6",
45
+ "react-dom": "^19.2.6"
46
+ },
47
+ "dependencies": {
48
+ "@modern-admin/core": "0.1.0",
49
+ "@modern-admin/i18n": "0.1.0",
50
+ "@modern-admin/react": "0.1.0",
51
+ "@modern-admin/ui": "0.1.0",
52
+ "@tanstack/react-query": "^5.100.9"
53
+ },
54
+ "devDependencies": {
55
+ "@modern-admin/tsconfig": "0.1.0",
56
+ "@tailwindcss/vite": "^4.2.4",
57
+ "@types/react": "^19.2.14",
58
+ "@types/react-dom": "^19.2.3",
59
+ "@vitejs/plugin-react": "^6.0.1",
60
+ "react": "^19.2.6",
61
+ "react-dom": "^19.2.6",
62
+ "tailwindcss": "^4.2.4",
63
+ "typescript": "^6.0.3",
64
+ "vite": "^8.0.10",
65
+ "vite-plugin-dts": "^5.0.1"
66
+ }
67
+ }
package/src/app.tsx ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Root <App> for the admin SPA. Wraps the AdminApp shell with i18n and the
3
+ * ModernAdmin provider, wiring everything from a single runtime config.
4
+ *
5
+ * Used by both the standalone bundle (window.__MODERN_ADMIN__) and the
6
+ * library `mount()` entry point — hosts compose it the same way.
7
+ */
8
+
9
+ import { type ReactElement, useMemo } from 'react'
10
+ import {
11
+ AdminApp,
12
+ type ComponentLoader,
13
+ I18nProvider,
14
+ ModernAdminProvider,
15
+ } from '@modern-admin/react'
16
+ import { builtinLocales } from '@modern-admin/i18n'
17
+ import type { ModernAdminRuntimeConfig } from './runtime-config.js'
18
+
19
+ export interface AppProps {
20
+ config: ModernAdminRuntimeConfig
21
+ /**
22
+ * Optional custom property-type components. The standalone bundle leaves
23
+ * this unset; the library `mount()` lets callers pass demo / project-
24
+ * specific components.
25
+ */
26
+ components?: ComponentLoader
27
+ }
28
+
29
+ export function App({ config, components }: AppProps): ReactElement {
30
+ // Filter the built-in 9-locale bundle by the host's whitelist. Empty /
31
+ // omitted → expose everything; the language switcher in the header
32
+ // collapses itself when only one locale survives the filter.
33
+ const enabledLocales = useMemo(() => {
34
+ if (!config.locales || config.locales.length === 0) return builtinLocales
35
+ const codes = new Set(config.locales)
36
+ const filtered = builtinLocales.filter((l) => codes.has(l.code))
37
+ return filtered.length > 0 ? filtered : builtinLocales
38
+ }, [config.locales])
39
+ return (
40
+ <I18nProvider
41
+ locales={enabledLocales}
42
+ defaultLocale={config.defaultLocale}
43
+ fallbackLocale={config.fallbackLocale}
44
+ metadataTranslations={config.metadataTranslations}
45
+ >
46
+ <ModernAdminProvider
47
+ components={components}
48
+ clientOptions={{
49
+ baseUrl: config.apiUrl,
50
+ credentials: config.credentials ?? 'include',
51
+ headers: config.headers,
52
+ persistDemoSession: config.persistDemoSession,
53
+ authBasePath: config.authBasePath,
54
+ }}
55
+ >
56
+ <AdminApp
57
+ loginHint={config.loginHint}
58
+ basePath={config.basePath}
59
+ showSidebarResourceIds={config.showSidebarResourceIds}
60
+ />
61
+ </ModernAdminProvider>
62
+ </I18nProvider>
63
+ )
64
+ }
package/src/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @modern-admin/web — prebuilt admin SPA, with both a library API
3
+ * (`mount()` + React `<App>`) and a standalone bundle for backends to
4
+ * serve as static assets.
5
+ */
6
+
7
+ export { App, type AppProps } from './app.js'
8
+ export { mount, type MountOptions, type MountedAdmin } from './mount.js'
9
+ export {
10
+ readWindowConfig,
11
+ type ModernAdminBrand,
12
+ type ModernAdminRuntimeConfig,
13
+ } from './runtime-config.js'
package/src/mount.tsx ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Imperative mounting API for embedding the admin SPA inside another React
3
+ * tree or vanilla HTML page. Consumers call once at startup:
4
+ *
5
+ * import { mount } from '@modern-admin/web'
6
+ * mount(document.getElementById('root')!, { apiUrl: 'https://api.example.com' })
7
+ *
8
+ * For the prebuilt standalone bundle, `src/standalone.tsx` calls this for
9
+ * you using `window.__MODERN_ADMIN__`.
10
+ */
11
+
12
+ import { StrictMode } from 'react'
13
+ import { createRoot, type Root } from 'react-dom/client'
14
+ import { type ComponentLoader } from '@modern-admin/react'
15
+ import { initTheme } from '@modern-admin/ui'
16
+ import '@modern-admin/ui/styles.css'
17
+ import { App } from './app.js'
18
+ import type { ModernAdminRuntimeConfig } from './runtime-config.js'
19
+
20
+ export interface MountOptions {
21
+ config: ModernAdminRuntimeConfig
22
+ /** Optional ComponentLoader with custom property-type components. */
23
+ components?: ComponentLoader
24
+ }
25
+
26
+ export interface MountedAdmin {
27
+ /** Unmounts the admin SPA from its container. */
28
+ unmount(): void
29
+ }
30
+
31
+ /**
32
+ * Renders the admin SPA into `container`. Returns a handle with `unmount()`
33
+ * for hosts that need to tear it down dynamically (rare — most users mount
34
+ * once and never unmount).
35
+ */
36
+ export function mount(container: Element, options: MountOptions): MountedAdmin {
37
+ initTheme()
38
+ const root: Root = createRoot(container)
39
+ root.render(
40
+ <StrictMode>
41
+ <App config={options.config} components={options.components} />
42
+ </StrictMode>,
43
+ )
44
+ return {
45
+ unmount: () => root.unmount(),
46
+ }
47
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Runtime configuration for the prebuilt admin SPA.
3
+ *
4
+ * The standalone bundle reads this from `window.__MODERN_ADMIN__`; the
5
+ * library `mount()` API takes it as an argument. Either way it is the
6
+ * single source of truth for "where is the API", "which translations to
7
+ * apply", and how the admin client should behave at runtime.
8
+ *
9
+ * No build-time env vars: one bundle serves any deployment.
10
+ */
11
+
12
+ import type { MetadataTranslations } from '@modern-admin/react'
13
+
14
+ export interface ModernAdminBrand {
15
+ /** Shown in the sidebar header and login screen. */
16
+ title?: string
17
+ /** Optional logo image URL. */
18
+ logoUrl?: string
19
+ }
20
+
21
+ export interface ModernAdminRuntimeConfig {
22
+ /**
23
+ * Absolute base URL of the admin API (the @modern-admin/nest backend).
24
+ * Leave undefined to use same-origin — recommended when the SPA is served
25
+ * by the same NestJS process that exposes the admin API.
26
+ */
27
+ apiUrl?: string
28
+ /** RequestCredentials forwarded to every fetch. Defaults to 'include'. */
29
+ credentials?: RequestCredentials
30
+ /** Extra headers attached to every request (e.g. CSRF, custom auth). */
31
+ headers?: Record<string, string>
32
+ /** Helper line shown on the login screen — e.g. demo credentials. */
33
+ loginHint?: string
34
+ /** Initial UI locale code. Falls back to the persisted choice / 'en'. */
35
+ defaultLocale?: string
36
+ /** Locale used when a translation is missing. Defaults to 'en'. */
37
+ fallbackLocale?: string
38
+ /**
39
+ * Whitelist of locale codes to expose in the header switcher (filters
40
+ * the built-in 9-locale bundle: `en`, `ru`, `de`, `es`, `fr`, `it`, `ja`,
41
+ * `pl`, `pt-BR`). Behaviour:
42
+ * - omitted / empty array → all built-in locales available
43
+ * - single code → switcher hides, that locale is forced
44
+ * - multiple codes → switcher lists only those locales
45
+ */
46
+ locales?: string[]
47
+ /** Optional per-resource / per-property metadata translations. */
48
+ metadataTranslations?: MetadataTranslations
49
+ /** Branding overrides (title, logo). */
50
+ brand?: ModernAdminBrand
51
+ /** Persist the demo session credentials in localStorage. */
52
+ persistDemoSession?: boolean
53
+ /**
54
+ * Path under which the host mounts Better Auth's Node handler. Drives
55
+ * the sign-in / sign-out endpoints — defaults to `/admin/api/auth`,
56
+ * matching the canonical CLI scaffold (and `ModernAdminStaticUiModule`
57
+ * mounted at `/admin`). Override only when the host mounts Better Auth
58
+ * elsewhere; pass *without* a trailing slash.
59
+ */
60
+ authBasePath?: string
61
+ /**
62
+ * URL prefix where the SPA is mounted (e.g. `/admin`). Injected
63
+ * automatically by `ModernAdminStaticUiMiddleware` from its `path` option
64
+ * so you normally do not need to set this. The router uses it as its
65
+ * basepath so all internal navigation and deep-link refreshes stay under
66
+ * the correct prefix. Pass without a trailing slash.
67
+ */
68
+ basePath?: string
69
+ /**
70
+ * When true, the sidebar resource list shows the raw resource id next
71
+ * to the localized label (e.g. "Posts (posts)") whenever the label
72
+ * differs from the id. Defaults to `false` — keeping the sidebar tidy.
73
+ * The home page tile and selector dropdowns (chart builder, etc.)
74
+ * always show both regardless of this flag.
75
+ */
76
+ showSidebarResourceIds?: boolean
77
+ }
78
+
79
+ declare global {
80
+ interface Window {
81
+ __MODERN_ADMIN__?: ModernAdminRuntimeConfig
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Reads runtime config from `window.__MODERN_ADMIN__`. Returns an empty
87
+ * object (same-origin defaults) when nothing is injected.
88
+ */
89
+ export function readWindowConfig(): ModernAdminRuntimeConfig {
90
+ if (typeof window === 'undefined') return {}
91
+ return window.__MODERN_ADMIN__ ?? {}
92
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Entry point for the prebuilt standalone bundle. Picks up the runtime
3
+ * config from `window.__MODERN_ADMIN__` (injected by the host server,
4
+ * typically @modern-admin/nest's StaticController) and mounts into
5
+ * `#root`.
6
+ *
7
+ * No build-time configuration — one bundle works for any deployment.
8
+ */
9
+
10
+ import { mount } from './mount.js'
11
+ import { readWindowConfig } from './runtime-config.js'
12
+
13
+ const container = document.getElementById('root')
14
+ if (!container) {
15
+ throw new Error('[modern-admin] expected #root container in the host page')
16
+ }
17
+
18
+ mount(container, { config: readWindowConfig() })
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "composite": false,
5
+ "noEmit": true,
6
+ "jsx": "react-jsx",
7
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
8
+ "types": ["bun", "vite/client"]
9
+ },
10
+ "include": ["src/**/*", "vite.config.ts"]
11
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Two-mode build:
3
+ *
4
+ * bun run build:lib → dist/lib/ (ESM library — `mount()` API)
5
+ * bun run build:standalone → dist/standalone/ (Prebuilt SPA + index.html)
6
+ *
7
+ * `mode` is set by Vite from the `--mode` CLI flag. Other modes (dev,
8
+ * preview) get the standalone SPA config — that's what you serve from
9
+ * `bun run dev`.
10
+ */
11
+
12
+ import { defineConfig, type UserConfig } from 'vite'
13
+ import react from '@vitejs/plugin-react'
14
+ import tailwindcss from '@tailwindcss/vite'
15
+ import dts from 'vite-plugin-dts'
16
+ import { fileURLToPath } from 'node:url'
17
+ import path from 'node:path'
18
+
19
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
20
+
21
+ // External deps for the library build — host apps provide their own copy
22
+ // (deduped by the workspace / package manager). The standalone SPA bundle
23
+ // inlines all of these so the served `<script>` is self-contained.
24
+ const LIB_EXTERNALS = [
25
+ 'react',
26
+ 'react-dom',
27
+ 'react-dom/client',
28
+ /^react\//,
29
+ /^react-dom\//,
30
+ /^@modern-admin\//,
31
+ /^@tanstack\//,
32
+ /^@radix-ui\//,
33
+ /^lucide-react/,
34
+ /^@hookform\//,
35
+ /^react-hook-form/,
36
+ 'zod',
37
+ 'class-variance-authority',
38
+ 'clsx',
39
+ 'tailwind-merge',
40
+ 'cmdk',
41
+ 'sonner',
42
+ 'date-fns',
43
+ 'react-day-picker',
44
+ /^@tiptap\//,
45
+ 'tiptap-markdown',
46
+ 'dompurify',
47
+ 'marked',
48
+ 'recharts',
49
+ 'tw-animate-css',
50
+ ]
51
+
52
+ const libConfig: UserConfig = {
53
+ plugins: [
54
+ react(),
55
+ tailwindcss(),
56
+ dts({
57
+ entryRoot: 'src',
58
+ outDirs: 'dist/lib',
59
+ include: ['src/**/*.ts', 'src/**/*.tsx'],
60
+ exclude: ['src/standalone.tsx'],
61
+ }),
62
+ ],
63
+ // Don't copy public/ (favicon.svg etc.) into the library output — those
64
+ // assets only make sense for the standalone HTML build.
65
+ publicDir: false,
66
+ build: {
67
+ outDir: 'dist/lib',
68
+ emptyOutDir: true,
69
+ sourcemap: true,
70
+ lib: {
71
+ entry: path.resolve(__dirname, 'src/index.ts'),
72
+ formats: ['es'],
73
+ fileName: () => 'index.js',
74
+ },
75
+ rollupOptions: {
76
+ external: LIB_EXTERNALS,
77
+ },
78
+ },
79
+ }
80
+
81
+ const standaloneConfig: UserConfig = {
82
+ plugins: [react(), tailwindcss()],
83
+ // Relative base so the same bundle can be mounted under any path
84
+ // (e.g. `/admin/`) — the server rewrites asset URLs in index.html.
85
+ base: './',
86
+ build: {
87
+ outDir: 'dist/standalone',
88
+ emptyOutDir: true,
89
+ sourcemap: true,
90
+ rollupOptions: {
91
+ input: path.resolve(__dirname, 'index.html'),
92
+ },
93
+ },
94
+ }
95
+
96
+ const devConfig: UserConfig = {
97
+ plugins: [react(), tailwindcss()],
98
+ server: {
99
+ port: Number(process.env.WEB_PORT ?? 3000),
100
+ host: true,
101
+ },
102
+ }
103
+
104
+ export default defineConfig(({ mode }) => {
105
+ if (mode === 'lib') return libConfig
106
+ if (mode === 'standalone') return standaloneConfig
107
+ return devConfig
108
+ })