@svadmin/core 0.9.0 → 0.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/core",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Core SDK — hooks, types, context, i18n, permissions, router",
5
5
  "type": "module",
6
6
  "sideEffects": false,
package/src/index.ts CHANGED
@@ -37,8 +37,8 @@ export { audit, setAuditHandler, setAuditLogProvider, getAuditLogProvider } from
37
37
  export type { AuditLogProvider } from './audit';
38
38
  export { setChatProvider, getChatProvider, setChatContext, getChatContext } from './chatProvider.svelte';
39
39
  export type { ChatProvider, ChatMessage, ChatContext, ChatAction } from './chatProvider.svelte';
40
- export { getTheme, setTheme, toggleTheme, getResolvedTheme, getColorTheme, setColorTheme, colorThemes, configureTheme, getThemeConfig, clearCssOverrides } from './theme.svelte';
41
- export type { ThemeMode, ColorTheme, ThemeStrategy, ThemeConfig } from './theme.svelte';
40
+ export { getTheme, setTheme, toggleTheme, getResolvedTheme, getColorTheme, setColorTheme, colorThemes, configureTheme, getThemeConfig, clearCssOverrides, builtinPresets, registerColorPreset, getColorPresets } from './theme.svelte';
41
+ export type { ThemeMode, ColorTheme, ThemeStrategy, ThemeConfig, ColorPreset } from './theme.svelte';
42
42
  export { setUnsavedChanges, getUnsavedChanges, initUnsavedChangesNotifier } from './unsaved-changes.svelte';
43
43
  export { setAdminOptions, getAdminOptions, getTextTransformers } from './options';
44
44
  export type { AdminOptions, TextTransformers, OvertimeConfig } from './options';
@@ -59,7 +59,7 @@ export type {
59
59
  DeleteParams, DeleteResult,
60
60
  DeleteManyParams, DeleteManyResult,
61
61
  Pagination, Sort, Filter, Identity,
62
- ResourceDefinition, FieldDefinition,
62
+ ResourceDefinition, FieldDefinition, MenuItem,
63
63
  AuthActionResult, CheckResult,
64
64
  ResourceTypeMap, KnownResources, InferData,
65
65
  BaseRecord,
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // Supports two class strategies:
4
4
  // - 'standard' (default): adds 'dark' class for dark mode (light-first)
5
- // - 'dark-first': adds 'light' class for light mode (dark-first, e.g. Elygate-style)
5
+ // - 'dark-first': adds 'light' class for light mode (dark-first)
6
6
 
7
7
  export type ThemeMode = 'light' | 'dark' | 'system';
8
8
  export type ColorTheme = 'blue' | 'green' | 'rose' | 'orange' | 'violet' | 'zinc';
@@ -10,6 +10,220 @@ export type ColorTheme = 'blue' | 'green' | 'rose' | 'orange' | 'violet' | 'zinc
10
10
  /** Controls how the theme class is applied to <html> */
11
11
  export type ThemeStrategy = 'standard' | 'dark-first';
12
12
 
13
+ // ── Color Preset System ──────────────────────────────────
14
+
15
+ /** A color preset defines CSS variable overrides for light and dark modes. */
16
+ export interface ColorPreset {
17
+ /** Unique name (e.g. 'indigo', 'blue') */
18
+ name: string;
19
+ /** Display label (e.g. 'Indigo', 'Blue') */
20
+ label: string;
21
+ /** Preview swatch color (hex) for UI pickers */
22
+ color: string;
23
+ /** CSS variable overrides for light mode */
24
+ light: Record<string, string>;
25
+ /** CSS variable overrides for dark mode */
26
+ dark: Record<string, string>;
27
+ }
28
+
29
+ /** Built-in color presets — consumers can extend via registerColorPreset(). */
30
+ export const builtinPresets: Record<string, ColorPreset> = {
31
+ neutral: {
32
+ name: 'neutral',
33
+ label: 'Neutral',
34
+ color: '#71717a',
35
+ light: {
36
+ '--primary': 'oklch(0.205 0.006 286)',
37
+ '--primary-foreground': 'oklch(0.985 0 0)',
38
+ '--ring': 'oklch(0.205 0.006 286)',
39
+ '--sidebar-primary': 'oklch(0.205 0.006 286)',
40
+ '--sidebar-primary-foreground': 'oklch(0.985 0 0)',
41
+ '--sidebar-ring': 'oklch(0.205 0.006 286)',
42
+ '--chart-1': 'oklch(0.488 0.243 264.376)',
43
+ },
44
+ dark: {
45
+ '--primary': 'oklch(0.922 0.004 286)',
46
+ '--primary-foreground': 'oklch(0.205 0.006 286)',
47
+ '--ring': 'oklch(0.556 0.004 286)',
48
+ '--sidebar-primary': 'oklch(0.922 0.004 286)',
49
+ '--sidebar-primary-foreground': 'oklch(0.205 0.006 286)',
50
+ '--sidebar-ring': 'oklch(0.556 0.004 286)',
51
+ '--chart-1': 'oklch(0.585 0.233 264)',
52
+ },
53
+ },
54
+ indigo: {
55
+ name: 'indigo',
56
+ label: 'Indigo',
57
+ color: '#4f46e5',
58
+ light: {
59
+ '--primary': 'oklch(0.488 0.243 264.376)',
60
+ '--primary-foreground': 'oklch(0.985 0 0)',
61
+ '--ring': 'oklch(0.488 0.243 264.376)',
62
+ '--sidebar-primary': 'oklch(0.488 0.243 264.376)',
63
+ '--sidebar-primary-foreground': 'oklch(0.985 0 0)',
64
+ '--sidebar-ring': 'oklch(0.488 0.243 264.376)',
65
+ '--chart-1': 'oklch(0.488 0.243 264.376)',
66
+ },
67
+ dark: {
68
+ '--primary': 'oklch(0.585 0.233 264)',
69
+ '--primary-foreground': 'oklch(0.15 0.04 265)',
70
+ '--ring': 'oklch(0.55 0.18 264)',
71
+ '--sidebar-primary': 'oklch(0.585 0.233 264)',
72
+ '--sidebar-primary-foreground': 'oklch(0.985 0 0)',
73
+ '--sidebar-ring': 'oklch(0.55 0.18 264)',
74
+ '--chart-1': 'oklch(0.585 0.233 264)',
75
+ },
76
+ },
77
+ blue: {
78
+ name: 'blue',
79
+ label: 'Blue',
80
+ color: '#3b82f6',
81
+ light: {
82
+ '--primary': 'oklch(0.546 0.245 262.881)',
83
+ '--primary-foreground': 'oklch(0.985 0 0)',
84
+ '--ring': 'oklch(0.546 0.245 262.881)',
85
+ '--sidebar-primary': 'oklch(0.546 0.245 262.881)',
86
+ '--sidebar-primary-foreground': 'oklch(0.985 0 0)',
87
+ '--sidebar-ring': 'oklch(0.546 0.245 262.881)',
88
+ '--chart-1': 'oklch(0.546 0.245 262.881)',
89
+ },
90
+ dark: {
91
+ '--primary': 'oklch(0.623 0.214 259.815)',
92
+ '--primary-foreground': 'oklch(0.15 0.04 265)',
93
+ '--ring': 'oklch(0.546 0.245 262.881)',
94
+ '--sidebar-primary': 'oklch(0.623 0.214 259.815)',
95
+ '--sidebar-primary-foreground': 'oklch(0.985 0 0)',
96
+ '--sidebar-ring': 'oklch(0.546 0.245 262.881)',
97
+ '--chart-1': 'oklch(0.623 0.214 259.815)',
98
+ },
99
+ },
100
+ green: {
101
+ name: 'green',
102
+ label: 'Green',
103
+ color: '#22c55e',
104
+ light: {
105
+ '--primary': 'oklch(0.527 0.185 150.069)',
106
+ '--primary-foreground': 'oklch(0.985 0 0)',
107
+ '--ring': 'oklch(0.527 0.185 150.069)',
108
+ '--sidebar-primary': 'oklch(0.527 0.185 150.069)',
109
+ '--sidebar-primary-foreground': 'oklch(0.985 0 0)',
110
+ '--sidebar-ring': 'oklch(0.527 0.185 150.069)',
111
+ '--chart-1': 'oklch(0.527 0.185 150.069)',
112
+ },
113
+ dark: {
114
+ '--primary': 'oklch(0.627 0.194 149.214)',
115
+ '--primary-foreground': 'oklch(0.15 0.05 150)',
116
+ '--ring': 'oklch(0.527 0.185 150.069)',
117
+ '--sidebar-primary': 'oklch(0.627 0.194 149.214)',
118
+ '--sidebar-primary-foreground': 'oklch(0.985 0 0)',
119
+ '--sidebar-ring': 'oklch(0.527 0.185 150.069)',
120
+ '--chart-1': 'oklch(0.627 0.194 149.214)',
121
+ },
122
+ },
123
+ rose: {
124
+ name: 'rose',
125
+ label: 'Rose',
126
+ color: '#f43f5e',
127
+ light: {
128
+ '--primary': 'oklch(0.577 0.245 27.325)',
129
+ '--primary-foreground': 'oklch(0.985 0 0)',
130
+ '--ring': 'oklch(0.577 0.245 27.325)',
131
+ '--sidebar-primary': 'oklch(0.577 0.245 27.325)',
132
+ '--sidebar-primary-foreground': 'oklch(0.985 0 0)',
133
+ '--sidebar-ring': 'oklch(0.577 0.245 27.325)',
134
+ '--chart-1': 'oklch(0.577 0.245 27.325)',
135
+ },
136
+ dark: {
137
+ '--primary': 'oklch(0.577 0.245 27.325)',
138
+ '--primary-foreground': 'oklch(0.985 0 0)',
139
+ '--ring': 'oklch(0.577 0.245 27.325)',
140
+ '--sidebar-primary': 'oklch(0.577 0.245 27.325)',
141
+ '--sidebar-primary-foreground': 'oklch(0.985 0 0)',
142
+ '--sidebar-ring': 'oklch(0.577 0.245 27.325)',
143
+ '--chart-1': 'oklch(0.577 0.245 27.325)',
144
+ },
145
+ },
146
+ orange: {
147
+ name: 'orange',
148
+ label: 'Orange',
149
+ color: '#f97316',
150
+ light: {
151
+ '--primary': 'oklch(0.646 0.222 41.116)',
152
+ '--primary-foreground': 'oklch(0.985 0 0)',
153
+ '--ring': 'oklch(0.646 0.222 41.116)',
154
+ '--sidebar-primary': 'oklch(0.646 0.222 41.116)',
155
+ '--sidebar-primary-foreground': 'oklch(0.985 0 0)',
156
+ '--sidebar-ring': 'oklch(0.646 0.222 41.116)',
157
+ '--chart-1': 'oklch(0.646 0.222 41.116)',
158
+ },
159
+ dark: {
160
+ '--primary': 'oklch(0.646 0.222 41.116)',
161
+ '--primary-foreground': 'oklch(0.985 0 0)',
162
+ '--ring': 'oklch(0.646 0.222 41.116)',
163
+ '--sidebar-primary': 'oklch(0.646 0.222 41.116)',
164
+ '--sidebar-primary-foreground': 'oklch(0.985 0 0)',
165
+ '--sidebar-ring': 'oklch(0.646 0.222 41.116)',
166
+ '--chart-1': 'oklch(0.646 0.222 41.116)',
167
+ },
168
+ },
169
+ violet: {
170
+ name: 'violet',
171
+ label: 'Violet',
172
+ color: '#8b5cf6',
173
+ light: {
174
+ '--primary': 'oklch(0.541 0.281 293.009)',
175
+ '--primary-foreground': 'oklch(0.985 0 0)',
176
+ '--ring': 'oklch(0.541 0.281 293.009)',
177
+ '--sidebar-primary': 'oklch(0.541 0.281 293.009)',
178
+ '--sidebar-primary-foreground': 'oklch(0.985 0 0)',
179
+ '--sidebar-ring': 'oklch(0.541 0.281 293.009)',
180
+ '--chart-1': 'oklch(0.541 0.281 293.009)',
181
+ },
182
+ dark: {
183
+ '--primary': 'oklch(0.541 0.281 293.009)',
184
+ '--primary-foreground': 'oklch(0.985 0 0)',
185
+ '--ring': 'oklch(0.541 0.281 293.009)',
186
+ '--sidebar-primary': 'oklch(0.541 0.281 293.009)',
187
+ '--sidebar-primary-foreground': 'oklch(0.985 0 0)',
188
+ '--sidebar-ring': 'oklch(0.541 0.281 293.009)',
189
+ '--chart-1': 'oklch(0.541 0.281 293.009)',
190
+ },
191
+ },
192
+ };
193
+
194
+ /** Register a custom color preset. Overwrites any built-in preset with the same name. */
195
+ export function registerColorPreset(preset: ColorPreset): void {
196
+ builtinPresets[preset.name] = preset;
197
+ // Update colorThemes display list
198
+ const idx = colorThemes.findIndex(ct => ct.id === preset.name);
199
+ const entry = { id: preset.name as ColorTheme, label: preset.label, color: preset.color };
200
+ if (idx >= 0) colorThemes[idx] = entry;
201
+ else colorThemes.push(entry);
202
+ }
203
+
204
+ /** Resolve a preset by name or return the preset object directly. */
205
+ function resolvePreset(preset: ColorPreset | string): ColorPreset | undefined {
206
+ if (typeof preset === 'string') return builtinPresets[preset];
207
+ return preset;
208
+ }
209
+
210
+ /** Apply a color preset's CSS variables based on current resolved theme. */
211
+ function applyColorPreset(preset: ColorPreset): void {
212
+ if (typeof document === 'undefined') return;
213
+ const resolved = mode === 'system' ? getSystemPreference() : mode;
214
+ const vars = resolved === 'dark' ? preset.dark : preset.light;
215
+ const root = document.documentElement;
216
+ for (const [key, value] of Object.entries(vars)) {
217
+ const cssVar = key.startsWith('--') ? key : `--${key}`;
218
+ root.style.setProperty(cssVar, value);
219
+ }
220
+ }
221
+
222
+ /** Get all available color presets (built-in + registered). */
223
+ export function getColorPresets(): ColorPreset[] {
224
+ return Object.values(builtinPresets);
225
+ }
226
+
13
227
  export interface ThemeConfig {
14
228
  /** Class strategy: 'standard' toggles '.dark', 'dark-first' toggles '.light' */
15
229
  strategy?: ThemeStrategy;
@@ -17,6 +231,8 @@ export interface ThemeConfig {
17
231
  cssOverrides?: Record<string, string>;
18
232
  /** Whether to disable the built-in color-scheme attribute */
19
233
  disableColorScheme?: boolean;
234
+ /** Built-in color preset name (e.g. 'indigo', 'blue') or a custom ColorPreset object */
235
+ colorPreset?: ColorPreset | string;
20
236
  }
21
237
 
22
238
  const STORAGE_KEY = 'svadmin-theme';
@@ -33,7 +249,12 @@ export function configureTheme(config: ThemeConfig): void {
33
249
  themeConfig = { ...config };
34
250
  // Re-apply the current theme with new strategy
35
251
  applyTheme(mode);
36
- // Apply CSS overrides
252
+ // Apply color preset first (lower priority)
253
+ if (config.colorPreset) {
254
+ const preset = resolvePreset(config.colorPreset);
255
+ if (preset) applyColorPreset(preset);
256
+ }
257
+ // Then apply manual CSS overrides (higher priority)
37
258
  if (config.cssOverrides) {
38
259
  applyCssOverrides(config.cssOverrides);
39
260
  }
@@ -70,15 +291,12 @@ export function clearCssOverrides(keys?: string[]): void {
70
291
  }
71
292
  }
72
293
 
73
- // ── Color themes (display metadata) ──────────────────────
74
- export const colorThemes: { id: ColorTheme; label: string; color: string }[] = [
75
- { id: 'blue', label: 'Blue', color: '#3b82f6' },
76
- { id: 'green', label: 'Green', color: '#22c55e' },
77
- { id: 'rose', label: 'Rose', color: '#f43f5e' },
78
- { id: 'orange', label: 'Orange', color: '#f97316' },
79
- { id: 'violet', label: 'Violet', color: '#8b5cf6' },
80
- { id: 'zinc', label: 'Zinc', color: '#71717a' },
81
- ];
294
+ // ── Color themes (display metadata, derived from presets) ──
295
+ export const colorThemes: { id: ColorTheme | string; label: string; color: string }[] = Object.values(builtinPresets).map(p => ({
296
+ id: p.name as ColorTheme,
297
+ label: p.label,
298
+ color: p.color,
299
+ }));
82
300
 
83
301
  // ── Dark/Light mode ──────────────────────────────────────
84
302
 
package/src/types.ts CHANGED
@@ -260,6 +260,30 @@ export interface ResourceDefinition {
260
260
  meta?: Record<string, unknown> & { dataProviderName?: string; parent?: string };
261
261
  }
262
262
 
263
+ // ─── MenuItem (Multi-Level Menu) ──────────────────────────────
264
+
265
+ export interface MenuItem {
266
+ /** Unique identifier, also used as i18n fallback key: t(`menu.${name}`) */
267
+ name: string;
268
+ /** Display label. Falls back to `t('menu.${name}')` if omitted */
269
+ label?: string;
270
+ /** Icon name string (maps to lucide icon set) or Svelte component */
271
+ icon?: string;
272
+ /** Navigation path — omit for parent-only menu nodes */
273
+ href?: string;
274
+ /** Open in new tab (useful for external links) */
275
+ target?: '_blank' | '_self';
276
+ /** Permission check metadata */
277
+ meta?: {
278
+ resource?: string;
279
+ action?: string;
280
+ /** Hide this item from the menu entirely */
281
+ hidden?: boolean;
282
+ };
283
+ /** Sub-menu items — supports infinite nesting */
284
+ children?: MenuItem[];
285
+ }
286
+
263
287
  export interface FieldDefinition {
264
288
  key: string;
265
289
  label: string;