create-secra 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +6 -0
  2. package/README.zh-CN.md +6 -0
  3. package/antd-adapter-template/apps/core/index.html +13 -0
  4. package/antd-adapter-template/apps/core/package.json +18 -0
  5. package/antd-adapter-template/apps/core/public/favicon.ico +1 -0
  6. package/antd-adapter-template/apps/core/public/favicon.svg +1 -0
  7. package/antd-adapter-template/apps/core/public/logo.svg +1 -0
  8. package/antd-adapter-template/apps/core/src/api/auth.ts +49 -0
  9. package/antd-adapter-template/apps/core/src/assets/react.svg +1 -0
  10. package/antd-adapter-template/apps/core/src/components/AntdGlobalProvider.tsx +87 -0
  11. package/antd-adapter-template/apps/core/src/components/AntdRootLayout.tsx +10 -0
  12. package/antd-adapter-template/apps/core/src/components/layout.tsx +387 -0
  13. package/antd-adapter-template/apps/core/src/guards/auth-route-guard.ts +45 -0
  14. package/antd-adapter-template/apps/core/src/main.tsx +65 -0
  15. package/antd-adapter-template/apps/core/src/pages/auth/components/account-login-fields.tsx +60 -0
  16. package/antd-adapter-template/apps/core/src/pages/auth/components/phone-login-fields.tsx +60 -0
  17. package/antd-adapter-template/apps/core/src/pages/auth/login.tsx +169 -0
  18. package/antd-adapter-template/apps/core/src/pages/index.tsx +156 -0
  19. package/antd-adapter-template/apps/core/src/router.ts +42 -0
  20. package/antd-adapter-template/apps/core/src/shims/use-sync-external-store-shim.ts +3 -0
  21. package/antd-adapter-template/apps/core/src/theme/theme.css +48 -0
  22. package/antd-adapter-template/apps/core/src/types/crypto-js.d.ts +5 -0
  23. package/antd-adapter-template/apps/core/src/utils/index.ts +12 -0
  24. package/antd-adapter-template/apps/core/src/utils/md5.ts +6 -0
  25. package/antd-adapter-template/apps/core/tsconfig.app.json +11 -0
  26. package/antd-adapter-template/apps/core/tsconfig.json +13 -0
  27. package/antd-adapter-template/apps/core/tsconfig.node.json +7 -0
  28. package/antd-adapter-template/apps/core/vite.config.ts +118 -0
  29. package/antd-adapter-template/eslint.config.js +23 -0
  30. package/antd-adapter-template/package.json +63 -0
  31. package/antd-adapter-template/packages/sdk/.swcrc +18 -0
  32. package/antd-adapter-template/packages/sdk/package.json +52 -0
  33. package/antd-adapter-template/packages/sdk/src/build/index.ts +28 -0
  34. package/antd-adapter-template/packages/sdk/src/build/plugins/auto-import.ts +46 -0
  35. package/antd-adapter-template/packages/sdk/src/build/plugins/bundle-analyzer.ts +33 -0
  36. package/antd-adapter-template/packages/sdk/src/build/plugins/remove-console.ts +23 -0
  37. package/antd-adapter-template/packages/sdk/src/build/plugins/unocss.ts +202 -0
  38. package/antd-adapter-template/packages/sdk/src/build/plugins/unplugin-icon.ts +43 -0
  39. package/antd-adapter-template/packages/sdk/src/components/i18n-switch-dropdown.tsx +139 -0
  40. package/antd-adapter-template/packages/sdk/src/components/index.ts +2 -0
  41. package/antd-adapter-template/packages/sdk/src/components/theme-switch-dropdown.tsx +131 -0
  42. package/antd-adapter-template/packages/sdk/src/hooks/auth/core.ts +101 -0
  43. package/antd-adapter-template/packages/sdk/src/hooks/auth/index.ts +139 -0
  44. package/antd-adapter-template/packages/sdk/src/hooks/auth/with-auth.tsx +41 -0
  45. package/antd-adapter-template/packages/sdk/src/hooks/index.ts +1 -0
  46. package/antd-adapter-template/packages/sdk/src/i18n/index.ts +150 -0
  47. package/antd-adapter-template/packages/sdk/src/index.ts +11 -0
  48. package/antd-adapter-template/packages/sdk/src/request/index.ts +436 -0
  49. package/antd-adapter-template/packages/sdk/src/storage/README.md +30 -0
  50. package/antd-adapter-template/packages/sdk/src/storage/index.ts +57 -0
  51. package/antd-adapter-template/packages/sdk/src/styles/reset.css +111 -0
  52. package/antd-adapter-template/packages/sdk/src/theme/index.ts +466 -0
  53. package/antd-adapter-template/packages/sdk/tsconfig.json +16 -0
  54. package/antd-adapter-template/pnpm-workspace.yaml +3 -0
  55. package/antd-adapter-template/tsconfig.app.json +29 -0
  56. package/antd-adapter-template/tsconfig.json +7 -0
  57. package/antd-adapter-template/tsconfig.node.json +27 -0
  58. package/antd-adapter-template/turbo.json +17 -0
  59. package/bin/index.mjs +165 -33
  60. package/package.json +3 -2
  61. package/template/apps/core/src/main.tsx +11 -18
  62. package/template/apps/core/src/router.ts +5 -1
  63. package/template/package.json +1 -1
  64. package/template/packages/sdk/src/build/plugins/unocss.ts +3 -0
@@ -0,0 +1,111 @@
1
+ /* Reset CSS - 常用的样式重置 */
2
+
3
+ /* 使用更直观的盒模型 */
4
+ *,
5
+ *::before,
6
+ *::after {
7
+ box-sizing: border-box;
8
+ }
9
+
10
+ /* 移除默认的 margin 和 padding */
11
+ * {
12
+ margin: 0;
13
+ padding: 0;
14
+ }
15
+
16
+ /* 设置根元素字体大小和行高 */
17
+ html {
18
+ font-size: 16px;
19
+ line-height: 1.5;
20
+ -webkit-text-size-adjust: 100%;
21
+ -moz-text-size-adjust: 100%;
22
+ text-size-adjust: 100%;
23
+ }
24
+
25
+ /* 设置 body 样式 */
26
+ body {
27
+ margin: 0;
28
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
29
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
30
+ sans-serif;
31
+ -webkit-font-smoothing: antialiased;
32
+ -moz-osx-font-smoothing: grayscale;
33
+ }
34
+
35
+ /* 移除列表样式 */
36
+ ul,
37
+ ol {
38
+ list-style: none;
39
+ }
40
+
41
+ /* 移除链接默认样式 */
42
+ a {
43
+ text-decoration: none;
44
+ color: inherit;
45
+ }
46
+
47
+ /* 移除按钮默认样式 */
48
+ button {
49
+ background: none;
50
+ border: none;
51
+ padding: 0;
52
+ font: inherit;
53
+ cursor: pointer;
54
+ outline: inherit;
55
+ }
56
+
57
+ /* 移除输入框默认样式 */
58
+ input,
59
+ textarea,
60
+ select {
61
+ font: inherit;
62
+ border: none;
63
+ outline: none;
64
+ }
65
+
66
+ /* 移除图片默认样式 */
67
+ img,
68
+ picture,
69
+ video,
70
+ canvas,
71
+ svg {
72
+ display: block;
73
+ max-width: 100%;
74
+ height: auto;
75
+ }
76
+
77
+ /* 移除表格默认样式 */
78
+ table {
79
+ border-collapse: collapse;
80
+ border-spacing: 0;
81
+ }
82
+
83
+ /* 移除标题默认样式 */
84
+ h1,
85
+ h2,
86
+ h3,
87
+ h4,
88
+ h5,
89
+ h6 {
90
+ font-size: inherit;
91
+ font-weight: inherit;
92
+ }
93
+
94
+ /* 移除段落默认样式 */
95
+ p {
96
+ margin: 0;
97
+ }
98
+
99
+ /* 移除引用默认样式 */
100
+ blockquote,
101
+ q {
102
+ quotes: none;
103
+ }
104
+
105
+ blockquote::before,
106
+ blockquote::after,
107
+ q::before,
108
+ q::after {
109
+ content: '';
110
+ content: none;
111
+ }
@@ -0,0 +1,466 @@
1
+ import { useConfig, useTheme, type ThemeConfig, type ThemeContextValue } from '@vlian/framework/core';
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
+ import type { ConfigProviderProps } from 'antd';
4
+
5
+ export type ThemeMode = NonNullable<ThemeConfig['mode']>;
6
+
7
+ type ResolvedThemeMode = Exclude<ThemeMode, 'system'>;
8
+
9
+ type CssThemeTokenName =
10
+ | 'background'
11
+ | 'foreground'
12
+ | 'card'
13
+ | 'card-foreground'
14
+ | 'popover'
15
+ | 'popover-foreground'
16
+ | 'primary'
17
+ | 'primary-foreground'
18
+ | 'secondary'
19
+ | 'secondary-foreground'
20
+ | 'muted'
21
+ | 'muted-foreground'
22
+ | 'accent'
23
+ | 'accent-foreground'
24
+ | 'destructive'
25
+ | 'destructive-foreground'
26
+ | 'border'
27
+ | 'input'
28
+ | 'ring';
29
+
30
+ type CssThemeTokenMap = Partial<Record<CssThemeTokenName, string>>;
31
+
32
+ type CssThemeConfig = Partial<Record<ResolvedThemeMode, CssThemeTokenMap>>;
33
+
34
+ const MODE_CYCLE: readonly ThemeMode[] = ['light', 'dark', 'system'] as const;
35
+
36
+ const DEFAULT_THEME_TOKENS: Record<ResolvedThemeMode, Record<CssThemeTokenName, string>> = {
37
+ light: {
38
+ background: '0 0% 100%',
39
+ foreground: '222.2 84% 4.9%',
40
+ card: '0 0% 100%',
41
+ 'card-foreground': '222.2 84% 4.9%',
42
+ popover: '0 0% 100%',
43
+ 'popover-foreground': '222.2 84% 4.9%',
44
+ primary: '221.2 83.2% 53.3%',
45
+ 'primary-foreground': '210 40% 98%',
46
+ secondary: '210 40% 96.1%',
47
+ 'secondary-foreground': '222.2 47.4% 11.2%',
48
+ muted: '210 40% 96.1%',
49
+ 'muted-foreground': '215.4 16.3% 46.9%',
50
+ accent: '210 40% 96.1%',
51
+ 'accent-foreground': '222.2 47.4% 11.2%',
52
+ destructive: '0 84.2% 60.2%',
53
+ 'destructive-foreground': '210 40% 98%',
54
+ border: '214.3 31.8% 91.4%',
55
+ input: '214.3 31.8% 91.4%',
56
+ ring: '221.2 83.2% 53.3%',
57
+ },
58
+ dark: {
59
+ background: '222.2 84% 4.9%',
60
+ foreground: '210 40% 98%',
61
+ card: '222.2 84% 4.9%',
62
+ 'card-foreground': '210 40% 98%',
63
+ popover: '222.2 84% 4.9%',
64
+ 'popover-foreground': '210 40% 98%',
65
+ primary: '217.2 91.2% 59.8%',
66
+ 'primary-foreground': '222.2 47.4% 11.2%',
67
+ secondary: '217.2 32.6% 17.5%',
68
+ 'secondary-foreground': '210 40% 98%',
69
+ muted: '217.2 32.6% 17.5%',
70
+ 'muted-foreground': '215 20.2% 65.1%',
71
+ accent: '217.2 32.6% 17.5%',
72
+ 'accent-foreground': '210 40% 98%',
73
+ destructive: '0 62.8% 30.6%',
74
+ 'destructive-foreground': '210 40% 98%',
75
+ border: '217.2 32.6% 17.5%',
76
+ input: '217.2 32.6% 17.5%',
77
+ ring: '224.3 76.3% 48%',
78
+ },
79
+ };
80
+
81
+ const normalizeMode = (mode: ThemeConfig['mode']): ThemeMode => {
82
+ if (mode === 'light' || mode === 'dark' || mode === 'system') {
83
+ return mode;
84
+ }
85
+ return 'light';
86
+ };
87
+
88
+ const normalizeHexColor = (value: string): string | null => {
89
+ const hex = value.trim().replace(/^#/, '');
90
+ if (!/^[0-9a-fA-F]{3}$|^[0-9a-fA-F]{6}$/.test(hex)) {
91
+ return null;
92
+ }
93
+ if (hex.length === 3) {
94
+ return `#${hex
95
+ .split('')
96
+ .map((char) => `${char}${char}`)
97
+ .join('')}`;
98
+ }
99
+ return `#${hex}`;
100
+ };
101
+
102
+ const hexToHslTriplet = (hexColor: string): string | null => {
103
+ const normalizedHex = normalizeHexColor(hexColor);
104
+ if (!normalizedHex) {
105
+ return null;
106
+ }
107
+
108
+ const hex = normalizedHex.slice(1);
109
+ const r = parseInt(hex.slice(0, 2), 16) / 255;
110
+ const g = parseInt(hex.slice(2, 4), 16) / 255;
111
+ const b = parseInt(hex.slice(4, 6), 16) / 255;
112
+
113
+ const max = Math.max(r, g, b);
114
+ const min = Math.min(r, g, b);
115
+ const delta = max - min;
116
+
117
+ let h = 0;
118
+ const l = (max + min) / 2;
119
+ const s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
120
+
121
+ if (delta !== 0) {
122
+ if (max === r) {
123
+ h = ((g - b) / delta) % 6;
124
+ } else if (max === g) {
125
+ h = (b - r) / delta + 2;
126
+ } else {
127
+ h = (r - g) / delta + 4;
128
+ }
129
+ }
130
+
131
+ const hue = Math.round((h * 60 + 360) % 360);
132
+ const saturation = Number((s * 100).toFixed(1));
133
+ const lightness = Number((l * 100).toFixed(1));
134
+ return `${hue} ${saturation}% ${lightness}%`;
135
+ };
136
+
137
+ const readThemeTokenConfig = (custom: ThemeConfig['custom']): CssThemeConfig => {
138
+ const source = (custom as { tokens?: CssThemeConfig } | undefined)?.tokens;
139
+ return source ?? {};
140
+ };
141
+
142
+ const hasTokenDiff = (current: CssThemeTokenMap | undefined, next: CssThemeTokenMap): boolean => {
143
+ for (const [key, value] of Object.entries(next)) {
144
+ if (current?.[key as CssThemeTokenName] !== value) {
145
+ return true;
146
+ }
147
+ }
148
+ return false;
149
+ };
150
+
151
+ const getSystemDark = (): boolean => {
152
+ if (typeof window === 'undefined') {
153
+ return false;
154
+ }
155
+ return window.matchMedia('(prefers-color-scheme: dark)').matches;
156
+ };
157
+
158
+ const toHslColor = (triplet: string): string => `hsl(${triplet})`;
159
+
160
+ export interface ThemeSwitchContextValue extends ThemeContextValue {
161
+ mode: ThemeMode;
162
+ resolvedMode: ResolvedThemeMode;
163
+ setThemeMode: (mode: ThemeMode) => void;
164
+ toggleThemeMode: () => void;
165
+ cycleThemeMode: () => void;
166
+ }
167
+
168
+ export interface UnifiedThemeSwitchContextValue extends ThemeSwitchContextValue {
169
+ setAntdTheme: (nextTheme: NonNullable<ConfigProviderProps['theme']>) => void;
170
+ setThemeTokens: (mode: ResolvedThemeMode, tokens: CssThemeTokenMap) => void;
171
+ themeTokens: Record<CssThemeTokenName, string>;
172
+ }
173
+
174
+ export const useThemeSwitch = (): UnifiedThemeSwitchContextValue => {
175
+ const themeContext = useTheme();
176
+ const configContext = useConfig();
177
+ const mode = normalizeMode(themeContext.theme.mode);
178
+ const [isSystemDark, setIsSystemDark] = useState<boolean>(getSystemDark);
179
+ const { setTheme } = themeContext;
180
+ const { setAntdConfig, antdConfig } = configContext;
181
+ const antdConfigRef = useRef(antdConfig);
182
+ const resolvedMode: ResolvedThemeMode = mode === 'system' ? (isSystemDark ? 'dark' : 'light') : mode;
183
+
184
+ useEffect(() => {
185
+ antdConfigRef.current = antdConfig;
186
+ }, [antdConfig]);
187
+
188
+ const setThemeMode = useCallback(
189
+ (nextMode: ThemeMode) => {
190
+ setTheme((prev) => {
191
+ const previous = prev ?? {};
192
+ if (previous.mode === nextMode) {
193
+ return previous;
194
+ }
195
+ return { ...previous, mode: nextMode };
196
+ });
197
+ },
198
+ [setTheme],
199
+ );
200
+
201
+ const toggleThemeMode = useCallback(() => {
202
+ const nextMode: ThemeMode = mode === 'dark' ? 'light' : 'dark';
203
+ setThemeMode(nextMode);
204
+ }, [mode, setThemeMode]);
205
+
206
+ const cycleThemeMode = useCallback(() => {
207
+ const index = MODE_CYCLE.indexOf(mode);
208
+ const nextMode = MODE_CYCLE[(index + 1) % MODE_CYCLE.length];
209
+ setThemeMode(nextMode);
210
+ }, [mode, setThemeMode]);
211
+
212
+ const setAntdTheme = useCallback(
213
+ (nextTheme: NonNullable<ConfigProviderProps['theme']>) => {
214
+ const currentAntdConfig = antdConfigRef.current;
215
+ const currentTheme = (currentAntdConfig as ConfigProviderProps | undefined)?.theme;
216
+ setAntdConfig({
217
+ ...(currentAntdConfig ?? {}),
218
+ theme: {
219
+ ...(currentTheme ?? {}),
220
+ ...nextTheme,
221
+ token: {
222
+ ...(currentTheme?.token ?? {}),
223
+ ...(nextTheme.token ?? {}),
224
+ },
225
+ },
226
+ });
227
+ },
228
+ [setAntdConfig],
229
+ );
230
+
231
+ const setThemeTokens = useCallback(
232
+ (targetMode: ResolvedThemeMode, tokens: CssThemeTokenMap) => {
233
+ setTheme((prev) => {
234
+ const previous = prev ?? {};
235
+ const themeTokenConfig = readThemeTokenConfig(previous.custom);
236
+ const currentTokens = themeTokenConfig[targetMode];
237
+ if (!hasTokenDiff(currentTokens, tokens)) {
238
+ return previous;
239
+ }
240
+ return {
241
+ ...previous,
242
+ custom: {
243
+ ...(previous.custom ?? {}),
244
+ tokens: {
245
+ ...themeTokenConfig,
246
+ [targetMode]: {
247
+ ...(themeTokenConfig[targetMode] ?? {}),
248
+ ...tokens,
249
+ },
250
+ },
251
+ },
252
+ };
253
+ });
254
+ },
255
+ [setTheme],
256
+ );
257
+
258
+ useEffect(() => {
259
+ if (typeof window === 'undefined') {
260
+ return;
261
+ }
262
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
263
+ const handleChange = (event: MediaQueryListEvent) => {
264
+ setIsSystemDark(event.matches);
265
+ };
266
+ setIsSystemDark(mediaQuery.matches);
267
+ if (typeof mediaQuery.addEventListener === 'function') {
268
+ mediaQuery.addEventListener('change', handleChange);
269
+ return () => {
270
+ mediaQuery.removeEventListener('change', handleChange);
271
+ };
272
+ }
273
+
274
+ mediaQuery.addListener(handleChange);
275
+ return () => {
276
+ mediaQuery.removeListener(handleChange);
277
+ };
278
+ }, []);
279
+
280
+ const effectiveThemeTokens = useMemo(() => {
281
+ const fromCustom = readThemeTokenConfig(themeContext.theme.custom)[resolvedMode] ?? {};
282
+ const primaryFromTheme = themeContext.theme.primaryColor ? hexToHslTriplet(themeContext.theme.primaryColor) : null;
283
+ const base = DEFAULT_THEME_TOKENS[resolvedMode];
284
+ const primary = fromCustom.primary ?? primaryFromTheme ?? base.primary;
285
+ return {
286
+ ...base,
287
+ ...fromCustom,
288
+ primary,
289
+ ring: fromCustom.ring ?? primary,
290
+ };
291
+ }, [resolvedMode, themeContext.theme.custom, themeContext.theme.primaryColor]);
292
+
293
+ useEffect(() => {
294
+ if (typeof document === 'undefined') {
295
+ return;
296
+ }
297
+ const root = document.documentElement;
298
+ const isDark = resolvedMode === 'dark';
299
+ root.classList.toggle('dark', isDark);
300
+ root.style.colorScheme = isDark ? 'dark' : 'light';
301
+ root.dataset.theme = resolvedMode;
302
+ }, [resolvedMode]);
303
+
304
+ useEffect(() => {
305
+ if (typeof document === 'undefined') {
306
+ return;
307
+ }
308
+ const root = document.documentElement;
309
+ for (const [tokenName, tokenValue] of Object.entries(effectiveThemeTokens)) {
310
+ root.style.setProperty(`--${tokenName}`, tokenValue);
311
+ }
312
+ }, [effectiveThemeTokens]);
313
+
314
+ return {
315
+ ...themeContext,
316
+ mode,
317
+ resolvedMode,
318
+ setThemeMode,
319
+ toggleThemeMode,
320
+ cycleThemeMode,
321
+ setAntdTheme,
322
+ setThemeTokens,
323
+ themeTokens: effectiveThemeTokens,
324
+ };
325
+ };
326
+
327
+ export interface UnifiedThemePreset {
328
+ key: string;
329
+ label: string;
330
+ primaryColor: string;
331
+ tokens?: Record<ResolvedThemeMode, CssThemeTokenMap>;
332
+ }
333
+
334
+ export interface UseUnifiedThemePresetOptions {
335
+ defaultPresetKey?: string;
336
+ borderRadius?: number;
337
+ }
338
+
339
+ export interface UnifiedThemePresetContextValue extends UnifiedThemeSwitchContextValue {
340
+ presetKey: string;
341
+ setPresetKey: (presetKey: string) => void;
342
+ activePreset: UnifiedThemePreset | null;
343
+ applyPreset: (presetKey: string) => void;
344
+ setUnifiedPrimaryColor: (primaryColor: string) => void;
345
+ }
346
+
347
+ export const useUnifiedThemePreset = (
348
+ presets: readonly UnifiedThemePreset[],
349
+ options?: UseUnifiedThemePresetOptions,
350
+ ): UnifiedThemePresetContextValue => {
351
+ const themeSwitch = useThemeSwitch();
352
+ const { setTheme, setThemeTokens, setAntdTheme, themeTokens, theme } = themeSwitch;
353
+ const { defaultPresetKey, borderRadius = 8 } = options ?? {};
354
+
355
+ const fallbackPreset = presets[0] ?? null;
356
+ const [presetKey, setPresetKey] = useState<string>(defaultPresetKey ?? fallbackPreset?.key ?? '');
357
+ const antdSyncSignatureRef = useRef<string>('');
358
+
359
+ const activePreset = useMemo(() => {
360
+ if (presets.length === 0) {
361
+ return null;
362
+ }
363
+ return presets.find((item) => item.key === presetKey) ?? fallbackPreset;
364
+ }, [fallbackPreset, presetKey, presets]);
365
+
366
+ useEffect(() => {
367
+ try {
368
+ if (!activePreset) {
369
+ return;
370
+ }
371
+ const expectedPrimary = activePreset.primaryColor;
372
+ const shouldSyncPrimary = theme.primaryColor !== expectedPrimary;
373
+ if (shouldSyncPrimary) {
374
+ setTheme((prev) => {
375
+ const previous = prev ?? {};
376
+ if (previous.primaryColor === expectedPrimary) {
377
+ return previous;
378
+ }
379
+ return { ...previous, primaryColor: expectedPrimary };
380
+ });
381
+ }
382
+ if (activePreset.tokens?.light) {
383
+ setThemeTokens('light', activePreset.tokens.light);
384
+ }
385
+ if (activePreset.tokens?.dark) {
386
+ setThemeTokens('dark', activePreset.tokens.dark);
387
+ }
388
+ } catch (error) {
389
+ console.error('[sdk/useUnifiedThemePreset] apply preset failed', error);
390
+ }
391
+ }, [activePreset, setTheme, setThemeTokens, theme.primaryColor]);
392
+
393
+ useEffect(() => {
394
+ try {
395
+ const signature = [
396
+ theme.primaryColor ?? '',
397
+ themeTokens.background,
398
+ themeTokens.card,
399
+ themeTokens.foreground,
400
+ themeTokens.border,
401
+ themeTokens.accent,
402
+ String(borderRadius),
403
+ ].join('|');
404
+ if (antdSyncSignatureRef.current === signature) {
405
+ return;
406
+ }
407
+ antdSyncSignatureRef.current = signature;
408
+ setAntdTheme({
409
+ token: {
410
+ colorPrimary: theme.primaryColor,
411
+ colorInfo: theme.primaryColor,
412
+ colorBgBase: toHslColor(themeTokens.background),
413
+ colorBgContainer: toHslColor(themeTokens.card),
414
+ colorTextBase: toHslColor(themeTokens.foreground),
415
+ colorBorder: toHslColor(themeTokens.border),
416
+ colorFillSecondary: toHslColor(themeTokens.accent),
417
+ borderRadius,
418
+ },
419
+ });
420
+ } catch (error) {
421
+ console.error('[sdk/useUnifiedThemePreset] sync antd theme failed', error);
422
+ }
423
+ }, [
424
+ borderRadius,
425
+ setAntdTheme,
426
+ theme.primaryColor,
427
+ themeTokens.accent,
428
+ themeTokens.background,
429
+ themeTokens.border,
430
+ themeTokens.card,
431
+ themeTokens.foreground,
432
+ ]);
433
+
434
+ const applyPreset = useCallback((nextPresetKey: string) => {
435
+ setPresetKey(nextPresetKey);
436
+ }, []);
437
+
438
+ const setUnifiedPrimaryColor = useCallback(
439
+ (primaryColor: string) => {
440
+ try {
441
+ setTheme((prev) => {
442
+ const previous = prev ?? {};
443
+ return { ...previous, primaryColor };
444
+ });
445
+ const hsl = hexToHslTriplet(primaryColor);
446
+ if (!hsl) {
447
+ return;
448
+ }
449
+ setThemeTokens('light', { primary: hsl, ring: hsl });
450
+ setThemeTokens('dark', { primary: hsl, ring: hsl });
451
+ } catch (error) {
452
+ console.error('[sdk/useUnifiedThemePreset] set primary failed', error);
453
+ }
454
+ },
455
+ [setTheme, setThemeTokens],
456
+ );
457
+
458
+ return {
459
+ ...themeSwitch,
460
+ presetKey,
461
+ setPresetKey,
462
+ activePreset,
463
+ applyPreset,
464
+ setUnifiedPrimaryColor,
465
+ };
466
+ };
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "lib": ["ES2022", "DOM"],
6
+ "jsx": "react-jsx",
7
+ "moduleResolution": "bundler",
8
+ "types": ["react", "react-dom"],
9
+ "esModuleInterop": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "strict": true,
12
+ "skipLibCheck": true,
13
+ "outDir": "dist"
14
+ },
15
+ "include": ["src"]
16
+ }
@@ -0,0 +1,3 @@
1
+ packages:
2
+ - "apps/*"
3
+ - "packages/*"
@@ -0,0 +1,29 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "composite": true,
5
+ "target": "ES2022",
6
+ "useDefineForClassFields": true,
7
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
8
+ "module": "ESNext",
9
+ "types": ["vite/client"],
10
+ "skipLibCheck": true,
11
+
12
+ /* Bundler mode */
13
+ "moduleResolution": "bundler",
14
+ "allowImportingTsExtensions": true,
15
+ "verbatimModuleSyntax": true,
16
+ "moduleDetection": "force",
17
+ "noEmit": true,
18
+ "jsx": "react-jsx",
19
+
20
+ /* Linting */
21
+ "strict": true,
22
+ "noUnusedLocals": true,
23
+ "noUnusedParameters": true,
24
+ "erasableSyntaxOnly": true,
25
+ "noFallthroughCasesInSwitch": true,
26
+ "noUncheckedSideEffectImports": true
27
+ },
28
+ "include": ["src"]
29
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "composite": true,
5
+ "target": "ES2023",
6
+ "lib": ["ES2023"],
7
+ "module": "ESNext",
8
+ "types": ["node"],
9
+ "skipLibCheck": true,
10
+
11
+ /* Bundler mode */
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "moduleDetection": "force",
16
+ "noEmit": true,
17
+
18
+ /* Linting */
19
+ "strict": true,
20
+ "noUnusedLocals": true,
21
+ "noUnusedParameters": true,
22
+ "erasableSyntaxOnly": true,
23
+ "noFallthroughCasesInSwitch": true,
24
+ "noUncheckedSideEffectImports": true
25
+ },
26
+ "include": ["vite.config.ts"]
27
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "https://turbo.build/schema.json",
3
+ "tasks": {
4
+ "build": {
5
+ "dependsOn": ["^build"],
6
+ "outputs": ["dist/**", "build/**"]
7
+ },
8
+ "dev": {
9
+ "cache": false,
10
+ "persistent": true
11
+ },
12
+ "lint": {},
13
+ "test": {
14
+ "dependsOn": ["^test"]
15
+ }
16
+ }
17
+ }