@nocobase/app-template-default 0.0.0-stage → 1.0.0-beta.57
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/.cursor/mcp.json +8 -0
- package/.mcp.json +8 -0
- package/.prettierignore +20 -0
- package/.vscode/mcp.json +8 -0
- package/AGENTS.md +432 -0
- package/CLAUDE.md +9 -0
- package/Dockerfile +132 -0
- package/Dockerfile.dockerignore +45 -0
- package/README.MD +481 -0
- package/cli/plugins.ts +14 -0
- package/client/agent-annotations-host.ts +19 -0
- package/client/app.ts +38 -0
- package/client/components/back-button.tsx +49 -0
- package/client/components/breadcrumbs.tsx +167 -0
- package/client/components/inbox-badge.ts +49 -0
- package/client/components/inbox-button.tsx +132 -0
- package/client/components/inbox-header-button.tsx +25 -0
- package/client/components/loading.tsx +35 -0
- package/client/components/page-container.tsx +24 -0
- package/client/components/page-header.tsx +33 -0
- package/client/components/route-child-page.tsx +121 -0
- package/client/components/route-dialog.tsx +10 -0
- package/client/components/route-drawer.tsx +10 -0
- package/client/components/route-overlay.tsx +190 -0
- package/client/components/ui/alert.tsx +75 -0
- package/client/components/ui/badge.tsx +51 -0
- package/client/components/ui/breadcrumb.tsx +121 -0
- package/client/components/ui/button.tsx +58 -0
- package/client/components/ui/collapsible.tsx +19 -0
- package/client/components/ui/context-menu.tsx +271 -0
- package/client/components/ui/dialog.tsx +160 -0
- package/client/components/ui/dropdown-menu.tsx +270 -0
- package/client/components/ui/empty.tsx +103 -0
- package/client/components/ui/field.tsx +236 -0
- package/client/components/ui/input-group.tsx +158 -0
- package/client/components/ui/input.tsx +20 -0
- package/client/components/ui/kbd.tsx +26 -0
- package/client/components/ui/label.tsx +18 -0
- package/client/components/ui/popover.tsx +89 -0
- package/client/components/ui/select.tsx +200 -0
- package/client/components/ui/separator.tsx +22 -0
- package/client/components/ui/sheet.tsx +137 -0
- package/client/components/ui/sidebar.tsx +723 -0
- package/client/components/ui/skeleton.tsx +13 -0
- package/client/components/ui/spinner.tsx +20 -0
- package/client/components/ui/tabs.tsx +79 -0
- package/client/components/ui/textarea.tsx +17 -0
- package/client/components/ui/toast.tsx +231 -0
- package/client/components/ui/tooltip.tsx +63 -0
- package/client/components/use-route-overlay.ts +26 -0
- package/client/config/api.ts +13 -0
- package/client/config/app.ts +8 -0
- package/client/config/auth.ts +18 -0
- package/client/config/index.ts +17 -0
- package/client/extensions/nocobase-auth-forms/field-props.ts +13 -0
- package/client/extensions/nocobase-auth-forms/form-parts.tsx +101 -0
- package/client/extensions/nocobase-auth-forms/password-input.tsx +41 -0
- package/client/extensions/nocobase-auth-forms/password-login-form.tsx +123 -0
- package/client/extensions/nocobase-auth-forms/password-registration-form.tsx +193 -0
- package/client/extensions/nocobase-auth-forms/password-reset-form.tsx +132 -0
- package/client/extensions/nocobase-auth-forms/password-reset-request-form.tsx +99 -0
- package/client/extensions/nocobase-auth-methods/auth-methods.tsx +170 -0
- package/client/extensions/nocobase-auth-split-layout/auth-split-layout.tsx +126 -0
- package/client/extensions/nocobase-device-approval/device-approval.tsx +277 -0
- package/client/extensions/nocobase-device-approval/locales/en-US.ts +55 -0
- package/client/extensions/nocobase-device-approval/locales/zh-CN.ts +27 -0
- package/client/extensions/nocobase-device-approval/use-device-approval.ts +176 -0
- package/client/extensions/nocobase-file-component-ui/README.md +13 -0
- package/client/extensions/nocobase-file-component-ui/components/file-list.tsx +147 -0
- package/client/extensions/nocobase-file-component-ui/components/file-preview-dialog.tsx +263 -0
- package/client/extensions/nocobase-file-component-ui/components/file-preview-field.tsx +68 -0
- package/client/extensions/nocobase-file-component-ui/components/file-thumbnail.tsx +73 -0
- package/client/extensions/nocobase-file-component-ui/components/file-upload-field.tsx +386 -0
- package/client/extensions/nocobase-file-component-ui/components/previewers/file-preview-content.tsx +255 -0
- package/client/extensions/nocobase-file-component-ui/components/previewers/office-open-xml-preview.tsx +226 -0
- package/client/extensions/nocobase-file-component-ui/index.ts +8 -0
- package/client/extensions/nocobase-file-component-ui/lib/file-preview.ts +194 -0
- package/client/extensions/nocobase-file-component-ui/lib/file-url.ts +22 -0
- package/client/extensions/nocobase-file-component-ui/lib/message-key.ts +18 -0
- package/client/extensions/nocobase-file-component-ui/types.ts +69 -0
- package/client/extensions/nocobase-inbox/decision-actions-bar.tsx +140 -0
- package/client/extensions/nocobase-inbox/frame.ts +31 -0
- package/client/extensions/nocobase-inbox/inbox-actions.ts +21 -0
- package/client/extensions/nocobase-inbox/inbox-detail.tsx +386 -0
- package/client/extensions/nocobase-inbox/inbox-item.tsx +274 -0
- package/client/extensions/nocobase-inbox/inbox-list.tsx +433 -0
- package/client/extensions/nocobase-inbox/inbox-page.tsx +545 -0
- package/client/extensions/nocobase-inbox/locales/en-US.ts +111 -0
- package/client/extensions/nocobase-inbox/locales/zh-CN.ts +57 -0
- package/client/extensions/nocobase-inbox/model.ts +428 -0
- package/client/extensions/nocobase-inbox/registry-scope.tsx +49 -0
- package/client/extensions/nocobase-inbox/registry.ts +303 -0
- package/client/extensions/nocobase-inbox/source.ts +117 -0
- package/client/hooks/use-mobile.ts +21 -0
- package/client/index.tsx +60 -0
- package/client/layouts/app-layout.tsx +105 -0
- package/client/layouts/components/app-brand.tsx +49 -0
- package/client/layouts/components/app-sidebar.tsx +196 -0
- package/client/layouts/components/header-actions.tsx +50 -0
- package/client/layouts/components/language-switcher.tsx +95 -0
- package/client/layouts/components/layout-header.tsx +17 -0
- package/client/layouts/components/navigation-menu.tsx +411 -0
- package/client/layouts/components/sidebar-footer.tsx +107 -0
- package/client/layouts/components/surface-empty.tsx +35 -0
- package/client/layouts/components/user-menu.tsx +127 -0
- package/client/layouts/index.ts +2 -0
- package/client/layouts/settings-layout.tsx +172 -0
- package/client/layouts/use-sidebar-preference.ts +57 -0
- package/client/lib/constants.ts +5 -0
- package/client/lib/toaster.ts +56 -0
- package/client/lib/utils.ts +1 -0
- package/client/locales/en-US.ts +178 -0
- package/client/locales/index.ts +9 -0
- package/client/locales/zh-CN.ts +161 -0
- package/client/pages/auth/device.tsx +41 -0
- package/client/pages/auth/forgot-password.tsx +39 -0
- package/client/pages/auth/login.tsx +66 -0
- package/client/pages/auth/register.tsx +46 -0
- package/client/pages/auth/reset-password.tsx +38 -0
- package/client/pages/auth/shared.tsx +111 -0
- package/client/pages/home.tsx +16 -0
- package/client/pages/inbox.tsx +12 -0
- package/client/plugins.ts +41 -0
- package/client/react-providers.ts +27 -0
- package/client/route-overrides.ts +9 -0
- package/client/routes.ts +75 -0
- package/client/routing/app-router.tsx +127 -0
- package/client/routing/client-route.tsx +129 -0
- package/client/routing/route-context.tsx +85 -0
- package/client/routing/route-navigation.ts +153 -0
- package/client/routing/route-tree.tsx +42 -0
- package/client/routing/standalone-page-layout.tsx +15 -0
- package/client/runtime.ts +26 -0
- package/client/service-provider.ts +40 -0
- package/client/source-extensions.ts +19 -0
- package/client/startup.tsx +22 -0
- package/client/styles.css +122 -0
- package/client/theme/index.ts +4 -0
- package/client/theme/theme-context.ts +12 -0
- package/client/theme/theme-preferences.ts +84 -0
- package/client/theme/theme-presets.ts +7 -0
- package/client/theme/theme-provider.tsx +106 -0
- package/client/theme/theme-settings.tsx +176 -0
- package/client/theme/themes/compact.css +140 -0
- package/client/theme/themes/default.css +117 -0
- package/client/vite-env.d.ts +5 -0
- package/components.json +27 -0
- package/config.example.yml +172 -0
- package/database/main/migrations/.gitkeep +0 -0
- package/database/main/migrations/202610060001_create_device_code.ts +37 -0
- package/database/main/seeds/.gitkeep +0 -0
- package/database/tsconfig.json +4 -0
- package/ecosystem.config.js +35 -0
- package/eslint.config.js +24 -0
- package/index.html +100 -0
- package/package.json +200 -3
- package/playwright.config.ts +10 -0
- package/public/assets/favicon.ico +0 -0
- package/public/assets/logo-dark.png +0 -0
- package/public/assets/logo-mark-dark.png +0 -0
- package/public/assets/logo-mark.png +0 -0
- package/public/assets/logo.png +0 -0
- package/server/app.ts +50 -0
- package/server/config/ai.ts +27 -0
- package/server/config/api.ts +8 -0
- package/server/config/app.ts +40 -0
- package/server/config/auth.ts +36 -0
- package/server/config/authorization.ts +18 -0
- package/server/config/caching.ts +21 -0
- package/server/config/database.ts +22 -0
- package/server/config/drive.ts +30 -0
- package/server/config/i18n.ts +17 -0
- package/server/config/index.ts +73 -0
- package/server/config/jobs.ts +27 -0
- package/server/config/logging.ts +23 -0
- package/server/config/notification.ts +11 -0
- package/server/config/queue.ts +28 -0
- package/server/config/scheduler.ts +23 -0
- package/server/config/secrets.ts +10 -0
- package/server/config/server.ts +26 -0
- package/server/config/session.ts +60 -0
- package/server/config/snowflake.ts +21 -0
- package/server/config/spa.ts +24 -0
- package/server/config/users.ts +11 -0
- package/server/config/workflow.ts +24 -0
- package/server/config.ts +24 -0
- package/server/embedded.ts +23 -0
- package/server/locales/en-US.ts +10 -0
- package/server/locales/index.ts +10 -0
- package/server/locales/zh-CN.ts +5 -0
- package/server/plugins.ts +41 -0
- package/server/providers/index.ts +5 -0
- package/server/routes/index.ts +6 -0
- package/server/runtime.ts +24 -0
- package/server/standalone.ts +29 -0
- package/server/tsconfig.json +4 -0
- package/tailwind.config.mjs +59 -0
- package/tests/components/app-layout-routes.test.tsx +132 -0
- package/tests/components/app-title.test.tsx +57 -0
- package/tests/components/auth-i18n.test.tsx +87 -0
- package/tests/components/back-button.test.tsx +80 -0
- package/tests/components/file-preview.test.tsx +101 -0
- package/tests/components/header-hover.test.tsx +87 -0
- package/tests/components/inbox.test.tsx +139 -0
- package/tests/components/language-switcher.test.tsx +298 -0
- package/tests/components/layout-containers.test.tsx +15 -0
- package/tests/components/layout-i18n.test.tsx +187 -0
- package/tests/components/loading.test.tsx +25 -0
- package/tests/components/navigation-menu.test.tsx +355 -0
- package/tests/components/page-harness.test.tsx +318 -0
- package/tests/components/primitive-labels.test.tsx +80 -0
- package/tests/components/sidebar-permissions.test.tsx +110 -0
- package/tests/components/sidebar-preference.test.tsx +42 -0
- package/tests/fixtures/theme-tokens.html +12 -0
- package/tests/fixtures/theme-tokens.tsx +111 -0
- package/tests/logic/account-permissions.test.tsx +204 -0
- package/tests/logic/app-locale-coverage.test.ts +136 -0
- package/tests/logic/app-server.test.ts +1589 -0
- package/tests/logic/asset-url-build.test.ts +106 -0
- package/tests/logic/client-auth.test.tsx +122 -0
- package/tests/logic/client-plugin-registry.test.ts +63 -0
- package/tests/logic/client-route-recovery.test.tsx +80 -0
- package/tests/logic/client-routes.test.ts +126 -0
- package/tests/logic/client-runtime.test.ts +112 -0
- package/tests/logic/client-settings.test.tsx +841 -0
- package/tests/logic/client-shell.test.tsx +466 -0
- package/tests/logic/client-theme.test.tsx +441 -0
- package/tests/logic/config.test.ts +197 -0
- package/tests/logic/dev-client-watch.test.ts +108 -0
- package/tests/logic/dev-routes-production-build.test.ts +168 -0
- package/tests/logic/device-code-migration.test.ts +70 -0
- package/tests/logic/lifecycle.test.ts +221 -0
- package/tests/logic/plugin-commands.test.ts +69 -0
- package/tests/logic/plugin-page-authz.test.ts +76 -0
- package/tests/logic/route-navigation.test.ts +66 -0
- package/tests/logic/route-overlay.test.tsx +353 -0
- package/tests/logic/server-imports.test.ts +88 -0
- package/tests/logic/tailwind-sources.test.ts +151 -0
- package/tests/logic/template-package-name.test.ts +88 -0
- package/tests/logic/theme-preferences.test.ts +86 -0
- package/tests/logic/theme-tokens.test.ts +248 -0
- package/tests/logic/toaster.test.tsx +108 -0
- package/tests/logic/user-menu-sign-out.test.tsx +98 -0
- package/tests/setup/client-config.ts +27 -0
- package/tsconfig.json +11 -0
- package/tsconfig.node.json +12 -0
- package/tsconfig.server.json +19 -0
- package/vite.config.ts +92 -0
- package/vitest.config.ts +37 -0
- package/README.md +0 -3
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { readAppClientRuntimeConfig } from '@nocobase/app-client/runtime';
|
|
2
|
+
|
|
3
|
+
// Shared preference helpers for normal client startup and the React provider.
|
|
4
|
+
export function themeStorageKeys(base: string): {
|
|
5
|
+
mode: string;
|
|
6
|
+
preset: string;
|
|
7
|
+
} {
|
|
8
|
+
const path = base.replace(/^\/+|\/+$/g, '');
|
|
9
|
+
const scope = path ? encodeURIComponent(path) : '%2F';
|
|
10
|
+
return {
|
|
11
|
+
mode: `nocobase:${scope}:theme:color-scheme`,
|
|
12
|
+
preset: `nocobase:${scope}:theme:preset`,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function readThemePreference(key: string): string | null {
|
|
17
|
+
try {
|
|
18
|
+
return localStorage.getItem(key);
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function initializeTheme(
|
|
25
|
+
base: string,
|
|
26
|
+
presets: readonly string[],
|
|
27
|
+
): void {
|
|
28
|
+
const defaults = readThemeDefaults(presets);
|
|
29
|
+
const keys = themeStorageKeys(base);
|
|
30
|
+
const savedMode = readThemePreference(keys.mode);
|
|
31
|
+
const mode =
|
|
32
|
+
savedMode && ['light', 'dark', 'system'].includes(savedMode)
|
|
33
|
+
? savedMode
|
|
34
|
+
: defaults.mode;
|
|
35
|
+
const savedPreset = readThemePreference(keys.preset);
|
|
36
|
+
const preset =
|
|
37
|
+
savedPreset && presets.includes(savedPreset)
|
|
38
|
+
? savedPreset
|
|
39
|
+
: defaults.preset;
|
|
40
|
+
const resolved =
|
|
41
|
+
mode === 'system'
|
|
42
|
+
? window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
43
|
+
? 'dark'
|
|
44
|
+
: 'light'
|
|
45
|
+
: mode;
|
|
46
|
+
document.documentElement.classList.remove('light', 'dark');
|
|
47
|
+
document.documentElement.classList.add(resolved);
|
|
48
|
+
document.documentElement.dataset.theme = preset;
|
|
49
|
+
document.documentElement.style.colorScheme = resolved;
|
|
50
|
+
// next-themes reads storage itself; normalize invalid modes before it mounts.
|
|
51
|
+
if (savedMode && !['light', 'dark', 'system'].includes(savedMode)) {
|
|
52
|
+
try {
|
|
53
|
+
localStorage.removeItem(keys.mode);
|
|
54
|
+
} catch {
|
|
55
|
+
/* Storage can be unavailable. */
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Read the same server-injected config at startup and Provider mount without
|
|
61
|
+
// coupling the shared client runtime to application-owned theme presets.
|
|
62
|
+
export function readThemeDefaults(presets: readonly string[]): {
|
|
63
|
+
mode: string;
|
|
64
|
+
preset: string;
|
|
65
|
+
} {
|
|
66
|
+
const config = readAppClientRuntimeConfig() as { app?: unknown };
|
|
67
|
+
const app = config.app;
|
|
68
|
+
const values =
|
|
69
|
+
typeof app === 'object' && app !== null
|
|
70
|
+
? (app as Record<string, unknown>)
|
|
71
|
+
: {};
|
|
72
|
+
const mode = values.defaultColorScheme;
|
|
73
|
+
const preset = values.defaultTheme;
|
|
74
|
+
return {
|
|
75
|
+
mode:
|
|
76
|
+
mode === 'light' || mode === 'dark' || mode === 'system'
|
|
77
|
+
? mode
|
|
78
|
+
: 'system',
|
|
79
|
+
preset:
|
|
80
|
+
typeof preset === 'string' && presets.includes(preset)
|
|
81
|
+
? preset
|
|
82
|
+
: presets[0],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const themePresets = [
|
|
2
|
+
{ id: 'compact', labelKey: 'appearance.themes.compact' },
|
|
3
|
+
{ id: 'default', labelKey: 'appearance.themes.default' },
|
|
4
|
+
] as const;
|
|
5
|
+
|
|
6
|
+
export type ThemePresetId = (typeof themePresets)[number]['id'];
|
|
7
|
+
export const defaultThemePreset: ThemePresetId = 'compact';
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { PresetContext } from './theme-context';
|
|
2
|
+
import { resolveAppBase } from '@nocobase/app-client';
|
|
3
|
+
import {
|
|
4
|
+
ThemeProvider as NextThemesProvider,
|
|
5
|
+
type ThemeProviderProps,
|
|
6
|
+
} from 'next-themes';
|
|
7
|
+
import { useEffect, useState, type ReactElement } from 'react';
|
|
8
|
+
import {
|
|
9
|
+
readThemeDefaults,
|
|
10
|
+
readThemePreference,
|
|
11
|
+
themeStorageKeys,
|
|
12
|
+
} from './theme-preferences';
|
|
13
|
+
import { themePresets, type ThemePresetId } from './theme-presets';
|
|
14
|
+
|
|
15
|
+
const validPreset = (
|
|
16
|
+
value: string | null,
|
|
17
|
+
fallback: ThemePresetId,
|
|
18
|
+
): ThemePresetId =>
|
|
19
|
+
themePresets.find((item) => item.id === value)?.id ?? fallback;
|
|
20
|
+
|
|
21
|
+
function ModeStorageSync({
|
|
22
|
+
storageKey,
|
|
23
|
+
defaultTheme,
|
|
24
|
+
}: {
|
|
25
|
+
storageKey: string;
|
|
26
|
+
defaultTheme: string;
|
|
27
|
+
}): null {
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const sync = (event: StorageEvent) => {
|
|
30
|
+
if (
|
|
31
|
+
event.key === null ||
|
|
32
|
+
(event.key === storageKey &&
|
|
33
|
+
event.newValue !== defaultTheme &&
|
|
34
|
+
!['light', 'dark', 'system'].includes(event.newValue ?? ''))
|
|
35
|
+
) {
|
|
36
|
+
// Consume invalid mode events before next-themes can persist its fallback.
|
|
37
|
+
// Re-dispatch a valid value for its state-only synchronization path.
|
|
38
|
+
// Let clear events through so the preset listener also resets.
|
|
39
|
+
if (event.key === storageKey) event.stopImmediatePropagation();
|
|
40
|
+
queueMicrotask(() => {
|
|
41
|
+
window.dispatchEvent(
|
|
42
|
+
new StorageEvent('storage', {
|
|
43
|
+
key: storageKey,
|
|
44
|
+
newValue: defaultTheme,
|
|
45
|
+
}),
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
window.addEventListener('storage', sync, true);
|
|
51
|
+
return () => window.removeEventListener('storage', sync, true);
|
|
52
|
+
}, [defaultTheme, storageKey]);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function AppThemeProvider({
|
|
57
|
+
children,
|
|
58
|
+
attribute = 'class',
|
|
59
|
+
defaultTheme,
|
|
60
|
+
enableSystem = true,
|
|
61
|
+
storageKey = themeStorageKeys(resolveAppBase()).mode,
|
|
62
|
+
...props
|
|
63
|
+
}: ThemeProviderProps): ReactElement {
|
|
64
|
+
const [defaults] = useState(() =>
|
|
65
|
+
readThemeDefaults(themePresets.map(({ id }) => id)),
|
|
66
|
+
);
|
|
67
|
+
const mode = defaultTheme ?? defaults.mode;
|
|
68
|
+
const fallbackPreset = defaults.preset as ThemePresetId;
|
|
69
|
+
const presetKey = themeStorageKeys(resolveAppBase()).preset;
|
|
70
|
+
const [preset, setPreset] = useState(() =>
|
|
71
|
+
validPreset(readThemePreference(presetKey), fallbackPreset),
|
|
72
|
+
);
|
|
73
|
+
const selectPreset = (value: ThemePresetId) => {
|
|
74
|
+
setPreset(value);
|
|
75
|
+
try {
|
|
76
|
+
localStorage.setItem(presetKey, value);
|
|
77
|
+
} catch {
|
|
78
|
+
/* Keep the in-memory selection. */
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
document.documentElement.dataset.theme = preset;
|
|
83
|
+
}, [preset]);
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
const sync = (event: StorageEvent) => {
|
|
86
|
+
if (event.key === presetKey || event.key === null)
|
|
87
|
+
setPreset(validPreset(event.newValue, fallbackPreset));
|
|
88
|
+
};
|
|
89
|
+
window.addEventListener('storage', sync, true);
|
|
90
|
+
return () => window.removeEventListener('storage', sync, true);
|
|
91
|
+
}, [presetKey, fallbackPreset]);
|
|
92
|
+
return (
|
|
93
|
+
<NextThemesProvider
|
|
94
|
+
{...props}
|
|
95
|
+
attribute={attribute}
|
|
96
|
+
defaultTheme={mode}
|
|
97
|
+
enableSystem={enableSystem}
|
|
98
|
+
storageKey={storageKey}
|
|
99
|
+
>
|
|
100
|
+
<ModeStorageSync storageKey={storageKey} defaultTheme={mode} />
|
|
101
|
+
<PresetContext.Provider value={{ preset, setPreset: selectPreset }}>
|
|
102
|
+
{children}
|
|
103
|
+
</PresetContext.Provider>
|
|
104
|
+
</NextThemesProvider>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { Check, Monitor, Moon, Palette, Sun } from 'lucide-react';
|
|
2
|
+
import { useTheme } from 'next-themes';
|
|
3
|
+
import { useTranslation } from '@nocobase/i18n/client';
|
|
4
|
+
import { useId, type ReactElement } from 'react';
|
|
5
|
+
import { Button } from '#components/ui/button';
|
|
6
|
+
import {
|
|
7
|
+
Popover,
|
|
8
|
+
PopoverTrigger,
|
|
9
|
+
PopoverContent,
|
|
10
|
+
PopoverTitle,
|
|
11
|
+
} from '#components/ui/popover';
|
|
12
|
+
import { cn } from 'cn';
|
|
13
|
+
import { useThemePreset } from './theme-context';
|
|
14
|
+
import { themePresets } from './theme-presets';
|
|
15
|
+
|
|
16
|
+
const COLOR_MODES = [
|
|
17
|
+
{ id: 'light', icon: Sun },
|
|
18
|
+
{ id: 'dark', icon: Moon },
|
|
19
|
+
{ id: 'system', icon: Monitor },
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
export function ThemeSettings(): ReactElement {
|
|
23
|
+
const { theme, setTheme } = useTheme();
|
|
24
|
+
const { preset, setPreset } = useThemePreset();
|
|
25
|
+
const { t } = useTranslation();
|
|
26
|
+
const id = useId();
|
|
27
|
+
const title = t('appearance.title', { defaultValue: 'Appearance' });
|
|
28
|
+
return (
|
|
29
|
+
<Popover>
|
|
30
|
+
<PopoverTrigger
|
|
31
|
+
openOnHover
|
|
32
|
+
delay={0}
|
|
33
|
+
render={
|
|
34
|
+
<Button
|
|
35
|
+
aria-label={title}
|
|
36
|
+
size='icon'
|
|
37
|
+
variant='outline'
|
|
38
|
+
className='size-10 rounded-xl border-border/70 bg-background/60 hover:bg-accent/50'
|
|
39
|
+
/>
|
|
40
|
+
}
|
|
41
|
+
>
|
|
42
|
+
<Palette aria-hidden='true' className='size-5' />
|
|
43
|
+
</PopoverTrigger>
|
|
44
|
+
{/* Keep the panel width independent of density; its content still uses theme tokens. */}
|
|
45
|
+
<PopoverContent
|
|
46
|
+
align='end'
|
|
47
|
+
className='w-xs max-w-[calc(100vw-2rem)] gap-4 p-4'
|
|
48
|
+
>
|
|
49
|
+
<div className='flex items-center justify-between'>
|
|
50
|
+
<PopoverTitle className='text-sm font-semibold text-foreground'>
|
|
51
|
+
{title}
|
|
52
|
+
</PopoverTitle>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<fieldset className='space-y-2'>
|
|
56
|
+
<legend className='text-xs font-medium text-muted-foreground'>
|
|
57
|
+
{t('appearance.mode', { defaultValue: 'Color mode' })}
|
|
58
|
+
</legend>
|
|
59
|
+
<div className='grid grid-cols-3 gap-1 rounded-lg bg-muted/70 p-1'>
|
|
60
|
+
{COLOR_MODES.map(({ id: mode, icon: ModeIcon }) => {
|
|
61
|
+
const isSelected = theme === mode;
|
|
62
|
+
return (
|
|
63
|
+
<label
|
|
64
|
+
key={mode}
|
|
65
|
+
className={cn(
|
|
66
|
+
'relative flex min-w-0 cursor-pointer items-center justify-center gap-1.5 rounded-md px-2 py-1.5 text-xs font-medium transition-colors select-none has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-ring',
|
|
67
|
+
isSelected
|
|
68
|
+
? 'bg-background text-foreground shadow-xs'
|
|
69
|
+
: 'text-muted-foreground hover:bg-background/40 hover:text-foreground',
|
|
70
|
+
)}
|
|
71
|
+
>
|
|
72
|
+
<input
|
|
73
|
+
type='radio'
|
|
74
|
+
name={id + '-mode'}
|
|
75
|
+
value={mode}
|
|
76
|
+
checked={isSelected}
|
|
77
|
+
onChange={() => setTheme(mode)}
|
|
78
|
+
className='sr-only'
|
|
79
|
+
/>
|
|
80
|
+
<ModeIcon aria-hidden='true' className='size-3.5 shrink-0' />
|
|
81
|
+
<span className='whitespace-nowrap'>
|
|
82
|
+
{t('appearance.' + mode, {
|
|
83
|
+
defaultValue: mode[0].toUpperCase() + mode.slice(1),
|
|
84
|
+
})}
|
|
85
|
+
</span>
|
|
86
|
+
</label>
|
|
87
|
+
);
|
|
88
|
+
})}
|
|
89
|
+
</div>
|
|
90
|
+
</fieldset>
|
|
91
|
+
|
|
92
|
+
<fieldset className='space-y-2'>
|
|
93
|
+
<legend className='text-xs font-medium text-muted-foreground'>
|
|
94
|
+
{t('appearance.preset', { defaultValue: 'Theme' })}
|
|
95
|
+
</legend>
|
|
96
|
+
<div className='grid grid-cols-2 gap-2.5'>
|
|
97
|
+
{themePresets.map((item) => {
|
|
98
|
+
const isSelected = preset === item.id;
|
|
99
|
+
return (
|
|
100
|
+
<label
|
|
101
|
+
key={item.id}
|
|
102
|
+
className={cn(
|
|
103
|
+
'group relative flex cursor-pointer flex-col rounded-xl border p-2 text-left transition-all select-none has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-ring',
|
|
104
|
+
isSelected
|
|
105
|
+
? 'border-primary ring-2 ring-primary/20 bg-primary/5 shadow-xs'
|
|
106
|
+
: 'border-border/70 hover:border-border hover:bg-muted/30',
|
|
107
|
+
)}
|
|
108
|
+
>
|
|
109
|
+
<input
|
|
110
|
+
type='radio'
|
|
111
|
+
name={id + '-preset'}
|
|
112
|
+
value={item.id}
|
|
113
|
+
checked={isSelected}
|
|
114
|
+
onChange={() => setPreset(item.id)}
|
|
115
|
+
className='sr-only'
|
|
116
|
+
/>
|
|
117
|
+
<div className='mb-2 h-16 w-full'>
|
|
118
|
+
<div
|
|
119
|
+
aria-hidden='true'
|
|
120
|
+
data-theme={item.id}
|
|
121
|
+
className='theme-preview relative flex h-full overflow-hidden rounded-lg border border-border/80 bg-background shadow-2xs'
|
|
122
|
+
>
|
|
123
|
+
{/* Mini Sidebar */}
|
|
124
|
+
<div className='flex w-6 flex-col gap-1 border-r border-sidebar-border/50 bg-sidebar p-1'>
|
|
125
|
+
<div className='size-2 rounded-xs bg-sidebar-primary-foreground' />
|
|
126
|
+
<div className='h-1 w-full rounded-xs bg-sidebar-foreground/30' />
|
|
127
|
+
<div className='h-1 w-full rounded-xs bg-sidebar-foreground/20' />
|
|
128
|
+
</div>
|
|
129
|
+
{/* Mini Main Content Area */}
|
|
130
|
+
<div className='flex flex-1 flex-col p-1.5'>
|
|
131
|
+
<div className='mb-1 flex items-center justify-between border-b border-border/40 pb-1'>
|
|
132
|
+
<div className='h-1 w-7 rounded-xs bg-foreground/70' />
|
|
133
|
+
<div className='size-1.5 rounded-full bg-primary' />
|
|
134
|
+
</div>
|
|
135
|
+
<div className='flex flex-1 flex-col justify-between'>
|
|
136
|
+
<div className='h-1 w-full rounded-xs bg-muted' />
|
|
137
|
+
<div className='flex items-center justify-between pt-1'>
|
|
138
|
+
<div className='h-1 w-6 rounded-xs bg-muted' />
|
|
139
|
+
<div className='h-3 w-5 rounded-xs bg-primary shadow-xs' />
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
<div className='mt-auto flex min-h-5 items-center justify-between gap-2 px-0.5'>
|
|
146
|
+
<span
|
|
147
|
+
className={cn(
|
|
148
|
+
'text-xs transition-colors',
|
|
149
|
+
isSelected
|
|
150
|
+
? 'font-medium text-foreground'
|
|
151
|
+
: 'text-muted-foreground group-hover:text-foreground',
|
|
152
|
+
)}
|
|
153
|
+
>
|
|
154
|
+
{t(item.labelKey, {
|
|
155
|
+
defaultValue:
|
|
156
|
+
item.id.charAt(0).toUpperCase() + item.id.slice(1),
|
|
157
|
+
})}
|
|
158
|
+
</span>
|
|
159
|
+
{isSelected && (
|
|
160
|
+
<span
|
|
161
|
+
aria-hidden='true'
|
|
162
|
+
className='flex size-3.5 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground'
|
|
163
|
+
>
|
|
164
|
+
<Check className='size-2 stroke-[3]' />
|
|
165
|
+
</span>
|
|
166
|
+
)}
|
|
167
|
+
</div>
|
|
168
|
+
</label>
|
|
169
|
+
);
|
|
170
|
+
})}
|
|
171
|
+
</div>
|
|
172
|
+
</fieldset>
|
|
173
|
+
</PopoverContent>
|
|
174
|
+
</Popover>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
:root[data-theme='compact'],
|
|
2
|
+
.theme-preview[data-theme='compact'] {
|
|
3
|
+
--font-sans:
|
|
4
|
+
ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC',
|
|
5
|
+
'Microsoft YaHei', sans-serif;
|
|
6
|
+
--font-serif:
|
|
7
|
+
ui-serif, Georgia, Cambria, 'Times New Roman', 'Songti SC', SimSun, serif;
|
|
8
|
+
--font-mono:
|
|
9
|
+
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
|
|
10
|
+
'Courier New', monospace;
|
|
11
|
+
--font-heading: var(--font-sans);
|
|
12
|
+
--spacing: 0.2rem;
|
|
13
|
+
--text-xs: 0.75rem;
|
|
14
|
+
--text-xs--line-height: calc(1 / 0.75);
|
|
15
|
+
--text-sm: 0.875rem;
|
|
16
|
+
--text-sm--line-height: calc(1.2 / 0.875);
|
|
17
|
+
--text-base: 1rem;
|
|
18
|
+
--text-base--line-height: calc(1.4 / 1);
|
|
19
|
+
--text-lg: 1.125rem;
|
|
20
|
+
--text-lg--line-height: calc(1.6 / 1.125);
|
|
21
|
+
--text-xl: 1.25rem;
|
|
22
|
+
--text-xl--line-height: calc(1.6 / 1.25);
|
|
23
|
+
--text-2xl: 1.5rem;
|
|
24
|
+
--text-2xl--line-height: calc(1.8 / 1.5);
|
|
25
|
+
--text-3xl: 1.875rem;
|
|
26
|
+
--text-3xl--line-height: calc(2.1 / 1.875);
|
|
27
|
+
--text-4xl: 2.25rem;
|
|
28
|
+
--text-4xl--line-height: calc(2.3 / 2.25);
|
|
29
|
+
--text-5xl: 3rem;
|
|
30
|
+
--text-5xl--line-height: 1;
|
|
31
|
+
--text-6xl: 3.75rem;
|
|
32
|
+
--text-6xl--line-height: 1;
|
|
33
|
+
--text-7xl: 4.5rem;
|
|
34
|
+
--text-7xl--line-height: 1;
|
|
35
|
+
--text-8xl: 6rem;
|
|
36
|
+
--text-8xl--line-height: 1;
|
|
37
|
+
--text-9xl: 8rem;
|
|
38
|
+
--text-9xl--line-height: 1;
|
|
39
|
+
--shadow-2xs: 0 1px rgb(0 0 0 / 0.05);
|
|
40
|
+
--shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
|
41
|
+
--shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
42
|
+
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
|
43
|
+
--shadow-lg:
|
|
44
|
+
0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
45
|
+
--shadow-xl:
|
|
46
|
+
0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
|
47
|
+
--shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
|
|
48
|
+
--radius: 0.375rem;
|
|
49
|
+
--background: oklch(0.985 0 0);
|
|
50
|
+
--foreground: oklch(0.16 0 0);
|
|
51
|
+
--card: oklch(1 0 0);
|
|
52
|
+
--card-foreground: oklch(0.16 0 0);
|
|
53
|
+
--popover: oklch(1 0 0);
|
|
54
|
+
--popover-foreground: oklch(0.16 0 0);
|
|
55
|
+
--primary: oklch(0.18 0 0);
|
|
56
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
57
|
+
--secondary: oklch(0.96 0 0);
|
|
58
|
+
--secondary-foreground: oklch(0.22 0 0);
|
|
59
|
+
--muted: oklch(0.96 0 0);
|
|
60
|
+
--muted-foreground: oklch(0.52 0 0);
|
|
61
|
+
--accent: oklch(0.94 0 0);
|
|
62
|
+
--accent-foreground: oklch(0.22 0 0);
|
|
63
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
64
|
+
--border: oklch(0.9 0 0);
|
|
65
|
+
--input: oklch(0.9 0 0);
|
|
66
|
+
--ring: oklch(0.55 0 0);
|
|
67
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
68
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
69
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
70
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
71
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
72
|
+
--sidebar: var(--card);
|
|
73
|
+
--sidebar-foreground: var(--card-foreground);
|
|
74
|
+
--sidebar-primary: color-mix(in oklch, var(--primary) 10%, var(--sidebar));
|
|
75
|
+
--sidebar-primary-foreground: var(--primary);
|
|
76
|
+
--sidebar-accent: var(--muted);
|
|
77
|
+
--sidebar-accent-foreground: var(--foreground);
|
|
78
|
+
--sidebar-border: var(--border);
|
|
79
|
+
--sidebar-ring: var(--ring);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
:root.dark[data-theme='compact'],
|
|
83
|
+
:root.dark .theme-preview[data-theme='compact'] {
|
|
84
|
+
--background: oklch(0.13 0 0);
|
|
85
|
+
--foreground: oklch(0.96 0 0);
|
|
86
|
+
--card: oklch(0.18 0 0);
|
|
87
|
+
--card-foreground: oklch(0.985 0 0);
|
|
88
|
+
--popover: oklch(0.18 0 0);
|
|
89
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
90
|
+
--primary: oklch(0.94 0 0);
|
|
91
|
+
--primary-foreground: oklch(0.16 0 0);
|
|
92
|
+
--secondary: oklch(0.24 0 0);
|
|
93
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
94
|
+
--muted: oklch(0.23 0 0);
|
|
95
|
+
--muted-foreground: oklch(0.7 0 0);
|
|
96
|
+
--accent: oklch(0.27 0 0);
|
|
97
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
98
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
99
|
+
--border: oklch(1 0 0 / 10%);
|
|
100
|
+
--input: oklch(1 0 0 / 15%);
|
|
101
|
+
--ring: oklch(0.62 0 0);
|
|
102
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
103
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
104
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
105
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
106
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
107
|
+
--sidebar: var(--card);
|
|
108
|
+
--sidebar-foreground: var(--card-foreground);
|
|
109
|
+
--sidebar-primary: color-mix(in oklch, var(--primary) 10%, var(--sidebar));
|
|
110
|
+
--sidebar-primary-foreground: var(--primary);
|
|
111
|
+
--sidebar-accent: var(--muted);
|
|
112
|
+
--sidebar-accent-foreground: var(--foreground);
|
|
113
|
+
--sidebar-border: var(--border);
|
|
114
|
+
--sidebar-ring: var(--ring);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* The Switch track is fixed in pixels while its thumb follows `--spacing`, so this preset's
|
|
118
|
+
smaller spacing leaves the checked knob short of the end. The literal values equal 4.6/8
|
|
119
|
+
(sm 3.5/6) times the default spacing; deriving the track from the token restores the
|
|
120
|
+
geometry. A descendant selector, so the token comparison only sees the declarations above. */
|
|
121
|
+
:root[data-theme='compact'] [data-slot='switch'][data-size='default'],
|
|
122
|
+
.theme-preview[data-theme='compact'] [data-slot='switch'][data-size='default'] {
|
|
123
|
+
width: calc(var(--spacing) * 8);
|
|
124
|
+
height: calc(var(--spacing) * 4.6);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
:root[data-theme='compact'] [data-slot='switch'][data-size='sm'],
|
|
128
|
+
.theme-preview[data-theme='compact'] [data-slot='switch'][data-size='sm'] {
|
|
129
|
+
width: calc(var(--spacing) * 6);
|
|
130
|
+
height: calc(var(--spacing) * 3.5);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Recharts draws a pie from fixed-pixel radii (`innerRadius={50}`) while the chart box height
|
|
134
|
+
follows `--spacing`, so this preset leaves the ring visibly thinner. Rendering the pie's
|
|
135
|
+
subtree at the spacious density restores the ring and the legend it sits beside; `:has`
|
|
136
|
+
limits the override to pie charts, leaving the h-72 trend chart scaled. */
|
|
137
|
+
:root[data-theme='compact'] [data-slot='chart']:has(.recharts-pie),
|
|
138
|
+
.theme-preview[data-theme='compact'] [data-slot='chart']:has(.recharts-pie) {
|
|
139
|
+
--spacing: 0.25rem;
|
|
140
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
:root,
|
|
2
|
+
:root[data-theme='default'],
|
|
3
|
+
.theme-preview[data-theme='default'] {
|
|
4
|
+
--font-sans:
|
|
5
|
+
ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC',
|
|
6
|
+
'Microsoft YaHei', sans-serif;
|
|
7
|
+
--font-serif:
|
|
8
|
+
ui-serif, Georgia, Cambria, 'Times New Roman', 'Songti SC', SimSun, serif;
|
|
9
|
+
--font-mono:
|
|
10
|
+
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
|
|
11
|
+
'Courier New', monospace;
|
|
12
|
+
--font-heading: var(--font-sans);
|
|
13
|
+
--spacing: 0.25rem;
|
|
14
|
+
--text-xs: 0.75rem;
|
|
15
|
+
--text-xs--line-height: calc(1 / 0.75);
|
|
16
|
+
--text-sm: 0.875rem;
|
|
17
|
+
--text-sm--line-height: calc(1.25 / 0.875);
|
|
18
|
+
--text-base: 1rem;
|
|
19
|
+
--text-base--line-height: calc(1.5 / 1);
|
|
20
|
+
--text-lg: 1.125rem;
|
|
21
|
+
--text-lg--line-height: calc(1.75 / 1.125);
|
|
22
|
+
--text-xl: 1.25rem;
|
|
23
|
+
--text-xl--line-height: calc(1.75 / 1.25);
|
|
24
|
+
--text-2xl: 1.5rem;
|
|
25
|
+
--text-2xl--line-height: calc(2 / 1.5);
|
|
26
|
+
--text-3xl: 1.875rem;
|
|
27
|
+
--text-3xl--line-height: calc(2.25 / 1.875);
|
|
28
|
+
--text-4xl: 2.25rem;
|
|
29
|
+
--text-4xl--line-height: calc(2.5 / 2.25);
|
|
30
|
+
--text-5xl: 3rem;
|
|
31
|
+
--text-5xl--line-height: 1;
|
|
32
|
+
--text-6xl: 3.75rem;
|
|
33
|
+
--text-6xl--line-height: 1;
|
|
34
|
+
--text-7xl: 4.5rem;
|
|
35
|
+
--text-7xl--line-height: 1;
|
|
36
|
+
--text-8xl: 6rem;
|
|
37
|
+
--text-8xl--line-height: 1;
|
|
38
|
+
--text-9xl: 8rem;
|
|
39
|
+
--text-9xl--line-height: 1;
|
|
40
|
+
--shadow-2xs: 0 1px rgb(0 0 0 / 0.05);
|
|
41
|
+
--shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
|
42
|
+
--shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
43
|
+
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
|
44
|
+
--shadow-lg:
|
|
45
|
+
0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
46
|
+
--shadow-xl:
|
|
47
|
+
0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
|
48
|
+
--shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
|
|
49
|
+
--radius: 0.5rem;
|
|
50
|
+
--background: oklch(0.985 0 0);
|
|
51
|
+
--foreground: oklch(0.16 0 0);
|
|
52
|
+
--card: oklch(1 0 0);
|
|
53
|
+
--card-foreground: oklch(0.16 0 0);
|
|
54
|
+
--popover: oklch(1 0 0);
|
|
55
|
+
--popover-foreground: oklch(0.16 0 0);
|
|
56
|
+
--primary: oklch(0.18 0 0);
|
|
57
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
58
|
+
--secondary: oklch(0.96 0 0);
|
|
59
|
+
--secondary-foreground: oklch(0.22 0 0);
|
|
60
|
+
--muted: oklch(0.96 0 0);
|
|
61
|
+
--muted-foreground: oklch(0.52 0 0);
|
|
62
|
+
--accent: oklch(0.94 0 0);
|
|
63
|
+
--accent-foreground: oklch(0.22 0 0);
|
|
64
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
65
|
+
--border: oklch(0.9 0 0);
|
|
66
|
+
--input: oklch(0.9 0 0);
|
|
67
|
+
--ring: oklch(0.55 0 0);
|
|
68
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
69
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
70
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
71
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
72
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
73
|
+
--sidebar: var(--card);
|
|
74
|
+
--sidebar-foreground: var(--card-foreground);
|
|
75
|
+
--sidebar-primary: color-mix(in oklch, var(--primary) 10%, var(--sidebar));
|
|
76
|
+
--sidebar-primary-foreground: var(--primary);
|
|
77
|
+
--sidebar-accent: var(--muted);
|
|
78
|
+
--sidebar-accent-foreground: var(--foreground);
|
|
79
|
+
--sidebar-border: var(--border);
|
|
80
|
+
--sidebar-ring: var(--ring);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
:root.dark,
|
|
84
|
+
:root.dark[data-theme='default'],
|
|
85
|
+
:root.dark .theme-preview[data-theme='default'] {
|
|
86
|
+
--background: oklch(0.13 0 0);
|
|
87
|
+
--foreground: oklch(0.96 0 0);
|
|
88
|
+
--card: oklch(0.18 0 0);
|
|
89
|
+
--card-foreground: oklch(0.985 0 0);
|
|
90
|
+
--popover: oklch(0.18 0 0);
|
|
91
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
92
|
+
--primary: oklch(0.94 0 0);
|
|
93
|
+
--primary-foreground: oklch(0.16 0 0);
|
|
94
|
+
--secondary: oklch(0.24 0 0);
|
|
95
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
96
|
+
--muted: oklch(0.23 0 0);
|
|
97
|
+
--muted-foreground: oklch(0.7 0 0);
|
|
98
|
+
--accent: oklch(0.27 0 0);
|
|
99
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
100
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
101
|
+
--border: oklch(1 0 0 / 10%);
|
|
102
|
+
--input: oklch(1 0 0 / 15%);
|
|
103
|
+
--ring: oklch(0.62 0 0);
|
|
104
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
105
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
106
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
107
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
108
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
109
|
+
--sidebar: var(--card);
|
|
110
|
+
--sidebar-foreground: var(--card-foreground);
|
|
111
|
+
--sidebar-primary: color-mix(in oklch, var(--primary) 10%, var(--sidebar));
|
|
112
|
+
--sidebar-primary-foreground: var(--primary);
|
|
113
|
+
--sidebar-accent: var(--muted);
|
|
114
|
+
--sidebar-accent-foreground: var(--foreground);
|
|
115
|
+
--sidebar-border: var(--border);
|
|
116
|
+
--sidebar-ring: var(--ring);
|
|
117
|
+
}
|
package/components.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "base-nova",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "client/styles.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"iconLibrary": "lucide",
|
|
14
|
+
"rtl": false,
|
|
15
|
+
"aliases": {
|
|
16
|
+
"components": "#components",
|
|
17
|
+
"utils": "#lib/utils",
|
|
18
|
+
"ui": "#components/ui",
|
|
19
|
+
"lib": "#lib",
|
|
20
|
+
"hooks": "#hooks"
|
|
21
|
+
},
|
|
22
|
+
"menuColor": "default",
|
|
23
|
+
"menuAccent": "subtle",
|
|
24
|
+
"registries": {
|
|
25
|
+
"@nocobase": "https://ui.nocobase.com/r/{name}.json"
|
|
26
|
+
}
|
|
27
|
+
}
|