dishui 0.0.72 → 0.0.74

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 (100) hide show
  1. package/dist/components/Button/index.d.ts +4 -4
  2. package/dist/components/Button/index.js +18 -17
  3. package/dist/components/Button/index.js.map +1 -1
  4. package/dist/components/Card/Metric.d.ts +17 -0
  5. package/dist/components/Card/Metric2.js +50 -0
  6. package/dist/components/Card/Metric2.js.map +1 -0
  7. package/dist/components/Card/index.d.ts +3 -0
  8. package/dist/components/Card/index.js +118 -117
  9. package/dist/components/Card/index.js.map +1 -1
  10. package/dist/components/Group/index.d.ts +2 -0
  11. package/dist/components/GroupButton/index.d.ts +16 -0
  12. package/dist/components/GroupButton/index.js +41 -0
  13. package/dist/components/GroupButton/index.js.map +1 -0
  14. package/dist/components/GroupedTable/index.d.ts +38 -0
  15. package/dist/components/GroupedTable/index.js +92 -0
  16. package/dist/components/GroupedTable/index.js.map +1 -0
  17. package/dist/components/Layout/index.d.ts +4 -2
  18. package/dist/components/Layout/index.js +1 -1
  19. package/dist/components/Layout/index.js.map +1 -1
  20. package/dist/components/NavigationList/index.d.ts +17 -0
  21. package/dist/components/NavigationList/index.js +48 -0
  22. package/dist/components/NavigationList/index.js.map +1 -0
  23. package/dist/components/Section/index.d.ts +28 -0
  24. package/dist/components/Section/index.js +58 -0
  25. package/dist/components/Section/index.js.map +1 -0
  26. package/dist/components/Select/index.d.ts +2 -0
  27. package/dist/components/Select/index.js +83 -82
  28. package/dist/components/Select/index.js.map +1 -1
  29. package/dist/components/SidebarToggleButton/index.d.ts +16 -1
  30. package/dist/components/SidebarToggleButton/index.js +90 -30
  31. package/dist/components/SidebarToggleButton/index.js.map +1 -1
  32. package/dist/components/Skeleton/index.d.ts +7 -0
  33. package/dist/components/Skeleton/index.js +21 -0
  34. package/dist/components/Skeleton/index.js.map +1 -0
  35. package/dist/components/Tree/Tree.js +5 -2
  36. package/dist/components/Tree/Tree.js.map +1 -1
  37. package/dist/components/pro/RichTextEditor/AIRewrite.js +114 -72
  38. package/dist/components/pro/RichTextEditor/AIRewrite.js.map +1 -1
  39. package/dist/components/pro/RichTextEditor/BlockHandle.js +315 -196
  40. package/dist/components/pro/RichTextEditor/BlockHandle.js.map +1 -1
  41. package/dist/components/pro/RichTextEditor/BubbleToolbar.d.ts +1 -1
  42. package/dist/components/pro/RichTextEditor/BubbleToolbar.js +156 -39
  43. package/dist/components/pro/RichTextEditor/BubbleToolbar.js.map +1 -1
  44. package/dist/components/pro/RichTextEditor/CommentPanel.d.ts +2 -1
  45. package/dist/components/pro/RichTextEditor/CommentPanel.js +100 -81
  46. package/dist/components/pro/RichTextEditor/CommentPanel.js.map +1 -1
  47. package/dist/components/pro/RichTextEditor/CommentPopover.d.ts +2 -1
  48. package/dist/components/pro/RichTextEditor/CommentPopover.js +92 -68
  49. package/dist/components/pro/RichTextEditor/CommentPopover.js.map +1 -1
  50. package/dist/components/pro/RichTextEditor/RichTextEditor.js +24 -22
  51. package/dist/components/pro/RichTextEditor/RichTextEditor.js.map +1 -1
  52. package/dist/components/pro/RichTextEditor/SearchReplace.d.ts +7 -0
  53. package/dist/components/pro/RichTextEditor/SlashMenu.js +132 -73
  54. package/dist/components/pro/RichTextEditor/SlashMenu.js.map +1 -1
  55. package/dist/components/pro/RichTextEditor/Toolbar/ColorPicker.d.ts +2 -0
  56. package/dist/components/pro/RichTextEditor/Toolbar/ColorPicker.js +9 -9
  57. package/dist/components/pro/RichTextEditor/Toolbar/ColorPicker.js.map +1 -1
  58. package/dist/components/pro/RichTextEditor/Toolbar/FloatingDialogMenu.js +8 -13
  59. package/dist/components/pro/RichTextEditor/Toolbar/FloatingDialogMenu.js.map +1 -1
  60. package/dist/components/pro/RichTextEditor/Toolbar/MarkdownTemplates.js +22 -3
  61. package/dist/components/pro/RichTextEditor/Toolbar/MarkdownTemplates.js.map +1 -1
  62. package/dist/components/pro/RichTextEditor/Toolbar/Toolbar.d.ts +1 -1
  63. package/dist/components/pro/RichTextEditor/Toolbar/Toolbar.js +346 -313
  64. package/dist/components/pro/RichTextEditor/Toolbar/Toolbar.js.map +1 -1
  65. package/dist/components/pro/RichTextEditor/extensions/CodeBlockView.js +120 -146
  66. package/dist/components/pro/RichTextEditor/extensions/CodeBlockView.js.map +1 -1
  67. package/dist/components/pro/RichTextEditor/extensions/CommentMark.js +2 -2
  68. package/dist/components/pro/RichTextEditor/extensions/CommentMark.js.map +1 -1
  69. package/dist/components/pro/RichTextEditor/extensions/FloatingDialog.js +178 -163
  70. package/dist/components/pro/RichTextEditor/extensions/FloatingDialog.js.map +1 -1
  71. package/dist/components/pro/RichTextEditor/extensions/FontSize.js +8 -5
  72. package/dist/components/pro/RichTextEditor/extensions/FontSize.js.map +1 -1
  73. package/dist/components/pro/RichTextEditor/extensions/MarkdownBlock.d.ts +6 -0
  74. package/dist/components/pro/RichTextEditor/extensions/MarkdownBlock.js +85 -74
  75. package/dist/components/pro/RichTextEditor/extensions/MarkdownBlock.js.map +1 -1
  76. package/dist/components/pro/RichTextEditor/searchUtils.d.ts +23 -0
  77. package/dist/contexts/AppContext.js +47 -45
  78. package/dist/contexts/AppContext.js.map +1 -1
  79. package/dist/dishui.css +1 -1
  80. package/dist/index-nostyled.d.ts +11 -1
  81. package/dist/index.d.ts +11 -1
  82. package/dist/index.js +119 -114
  83. package/dist/layouts/MainLayout.js +9 -10
  84. package/dist/layouts/MainLayout.js.map +1 -1
  85. package/dist/layouts/Sidebar.d.ts +9 -0
  86. package/dist/layouts/Sidebar.js +99 -80
  87. package/dist/layouts/Sidebar.js.map +1 -1
  88. package/dist/layouts/headbar/Headbar.d.ts +1 -1
  89. package/dist/layouts/headbar/Headbar.js +2 -2
  90. package/dist/layouts/headbar/Headbar.js.map +1 -1
  91. package/dist/lite.d.ts +19 -1
  92. package/dist/lite.js +17 -8
  93. package/dist/styles/themeStyles.js +6 -6
  94. package/dist/styles/themeStyles.js.map +1 -1
  95. package/dist/utils/i18n.d.ts +3 -0
  96. package/dist/utils/i18n.js +78 -0
  97. package/dist/utils/i18n.js.map +1 -1
  98. package/dist/utils/menu.js +23 -6
  99. package/dist/utils/menu.js.map +1 -1
  100. package/package.json +8 -2
@@ -1,6 +1,6 @@
1
- import e from "../layouts/Sidebar.js";
2
- import t from "../layouts/headbar/Headbar.js";
3
- import { SidebarToggleButtons as n } from "../components/SidebarToggleButton/index.js";
1
+ import { MobileSidebarToggleButton as e } from "../components/SidebarToggleButton/index.js";
2
+ import t from "../layouts/Sidebar.js";
3
+ import n from "../layouts/headbar/Headbar.js";
4
4
  import r, { createContext as i, useContext as a, useEffect as o, useState as s } from "react";
5
5
  import { jsx as c, jsxs as l } from "react/jsx-runtime";
6
6
  import { Outlet as u, RouterProvider as d } from "@tanstack/react-router";
@@ -10,39 +10,42 @@ var f = i(void 0), p = () => {
10
10
  if (!e) throw Error("useAppContext must be used within an AppShell");
11
11
  return e;
12
12
  }, m = () => p().auth, h = {}, g = () => {
13
- let { title: i, language: a, theme: o, menuItems: d, getMenuItems: f, themes: m, sidebarWidth: g, sidebarCollapsedWidth: _, navbarHeight: v } = p(), [y, b] = s(() => {
13
+ let { title: i, language: a, theme: d, menuItems: f, getMenuItems: m, themes: g, sidebarWidth: _, sidebarCollapsedWidth: v, navbarHeight: y } = p(), [b, x] = s(() => {
14
14
  let e = localStorage.getItem("sidebar-collapsed");
15
15
  return e ? JSON.parse(e) : !1;
16
- }), [x, S] = s(g || 280), C = r.useRef(null);
17
- r.useEffect(() => {
18
- g && S(g);
19
- }, [g]), r.useEffect(() => {
20
- localStorage.setItem("sidebar-collapsed", JSON.stringify(y));
21
- }, [y]);
22
- let w = () => {
23
- b(!y);
24
- }, T = (e) => {
25
- S(e);
26
- }, E = v || 64, D = h.layout || "sidebar", O = h.headbarLeft, k = typeof O == "function" ? O(w) : O;
27
- return D === "topbar" ? /* @__PURE__ */ l("div", {
16
+ }), [S, C] = s(_ || 280), w = r.useRef(null), [T, E] = s(() => typeof window < "u" && window.innerWidth < 768), [D, O] = s(null);
17
+ o(() => {
18
+ let e = () => E(window.innerWidth < 768);
19
+ return window.addEventListener("resize", e), () => window.removeEventListener("resize", e);
20
+ }, []), r.useEffect(() => {
21
+ _ && C(_);
22
+ }, [_]), r.useEffect(() => {
23
+ localStorage.setItem("sidebar-collapsed", JSON.stringify(b));
24
+ }, [b]);
25
+ let k = D ?? (T || b), A = () => {
26
+ O(!k);
27
+ }, j = T ? 16 : 128, M = (e) => {
28
+ C(e);
29
+ }, N = y || 64, P = h.layout || "sidebar", F = h.headbarLeft, I = typeof F == "function" ? F(A) : F;
30
+ return P === "topbar" ? /* @__PURE__ */ l("div", {
28
31
  className: "h-screen flex flex-col bg-base-100",
29
- "data-theme": o,
32
+ "data-theme": d,
30
33
  children: [/* @__PURE__ */ c("div", {
31
34
  style: {
32
- height: `${E}px`,
35
+ height: `${N}px`,
33
36
  flexShrink: 0
34
37
  },
35
- children: /* @__PURE__ */ c(t, {
36
- left: k,
38
+ children: /* @__PURE__ */ c(n, {
39
+ left: I,
37
40
  middle: h.headbarMiddle,
38
41
  right: h.headbarRight,
39
- height: E
42
+ height: N
40
43
  })
41
44
  }), /* @__PURE__ */ c("main", {
42
45
  className: "flex-1 overflow-y-auto",
43
46
  style: {
44
- paddingLeft: "128px",
45
- paddingRight: "128px",
47
+ paddingLeft: `${j}px`,
48
+ paddingRight: `${j}px`,
46
49
  paddingTop: "16px",
47
50
  paddingBottom: "16px"
48
51
  },
@@ -50,55 +53,54 @@ var f = i(void 0), p = () => {
50
53
  })]
51
54
  }) : /* @__PURE__ */ l("div", {
52
55
  className: "h-screen flex flex-col bg-base-100",
53
- "data-theme": o,
56
+ "data-theme": d,
54
57
  children: [/* @__PURE__ */ c("div", {
55
58
  style: {
56
- height: `${E}px`,
59
+ height: `${N}px`,
57
60
  flexShrink: 0
58
61
  },
59
- children: /* @__PURE__ */ c(t, {
60
- left: k,
62
+ children: /* @__PURE__ */ c(n, {
63
+ left: I,
61
64
  middle: h.headbarMiddle,
62
65
  right: h.headbarRight,
63
- height: E
66
+ height: N
64
67
  })
65
68
  }), /* @__PURE__ */ l("div", {
66
69
  className: "flex flex-1 overflow-hidden",
67
- style: { height: `calc(100vh - ${E}px)` },
70
+ style: { height: `calc(100vh - ${N}px)` },
68
71
  children: [
69
72
  /* @__PURE__ */ c("div", {
70
73
  className: "flex-shrink-0",
71
- ref: C,
74
+ ref: w,
72
75
  style: {
73
76
  height: "100%",
74
- overflow: "hidden"
77
+ overflow: "visible"
75
78
  },
76
- children: /* @__PURE__ */ c(e, {
77
- isCollapsed: y,
79
+ children: /* @__PURE__ */ c(t, {
80
+ isCollapsed: k,
78
81
  language: a,
79
- items: d,
80
- getMenuItems: f,
81
- onWidthChange: T,
82
- initialWidth: g || 280,
83
- collapsedWidth: _ || 64
82
+ items: f,
83
+ getMenuItems: m,
84
+ onWidthChange: M,
85
+ onToggle: A,
86
+ initialWidth: S,
87
+ collapsedWidth: v || 64
84
88
  })
85
89
  }),
86
90
  /* @__PURE__ */ c("main", {
87
91
  className: "flex-1 overflow-y-auto",
88
92
  style: {
89
93
  height: "100%",
90
- paddingLeft: "128px",
91
- paddingRight: "128px",
94
+ paddingLeft: `${j}px`,
95
+ paddingRight: `${j}px`,
92
96
  paddingTop: "16px",
93
97
  paddingBottom: "16px"
94
98
  },
95
99
  children: /* @__PURE__ */ c(u, {})
96
100
  }),
97
- /* @__PURE__ */ c(n, {
98
- isCollapsed: y,
99
- onToggle: w,
100
- sidebarWidth: x,
101
- collapsedWidth: _ || 64
101
+ /* @__PURE__ */ c(e, {
102
+ isCollapsed: k,
103
+ onToggle: A
102
104
  })
103
105
  ]
104
106
  })]
@@ -1 +1 @@
1
- {"version":3,"file":"AppContext.js","names":[],"sources":["../../src/contexts/AppContext.tsx"],"sourcesContent":["import React, { createContext, useContext, useState, useEffect } from 'react';\nimport { RouterProvider } from '@tanstack/react-router';\nimport { Router } from '@tanstack/react-router';\nimport Sidebar from '../layouts/Sidebar';\nimport { Headbar } from '../layouts/headbar';\nimport type { SearchConfig } from '../layouts/headbar';\nimport { Outlet } from '@tanstack/react-router';\nimport { MenuItem } from '../types/menu';\nimport { ThemeConfig } from '../utils/themes';\nimport { SidebarToggleButtons } from '../components/SidebarToggleButton';\n\n// 主题类型\nexport type ThemeType = 'light' | 'dark' | 'cursor' | 'retro' | 'cyberpunk' | 'matrix' | 'oceanblue' | 'eyecare' | 'ghibli' | 'pinkgirl' | 'mintgirl' | 'wuxing' | 'jukebox';\n\n// 用户类型定义\nexport type User = {\n id: string;\n name: string;\n email: string;\n roles: string[];\n};\n\n// 认证上下文类型\nexport type AuthContextType = {\n user: User | null;\n isAuthenticated: boolean;\n login: (user: User) => void;\n logout: () => void;\n hasRole: (role: string) => boolean;\n hasAnyRole: (roles: string[]) => boolean;\n};\n\n// 应用上下文类型\nexport type AppContextType = {\n title?: string;\n language: 'zh' | 'en';\n theme: ThemeType;\n setLanguage: (lang: 'zh' | 'en') => void;\n setTheme: (theme: ThemeType) => void;\n auth: AuthContextType;\n menuItems?: MenuItem[];\n getMenuItems?: (language: 'zh' | 'en') => MenuItem[];\n themes?: ThemeConfig[];\n sidebarWidth?: number;\n sidebarCollapsedWidth?: number;\n navbarHeight?: number;\n};\n\nconst AppContext = createContext<AppContextType | undefined>(undefined);\n\nexport const useAppContext = () => {\n const context = useContext(AppContext);\n if (!context) {\n throw new Error('useAppContext must be used within an AppShell');\n }\n return context;\n};\n\n// 导出独立的认证 hook\nexport const useAuth = () => {\n const context = useAppContext();\n return context.auth;\n};\n\n// AppShell 配置类型\nexport type AppShellConfig = {\n title?: string;\n initialLang?: 'zh' | 'en';\n initialTheme?: ThemeType;\n menuItems?: MenuItem[];\n getMenuItems?: (language: 'zh' | 'en') => MenuItem[];\n themes?: ThemeConfig[];\n enableAuth?: boolean;\n onAuthRequired?: () => boolean | void;\n protectedRoutes?: string[];\n publicRoutes?: string[];\n loginUrl?: string;\n authFailedCallback?: (path: string, loginUrl: string) => void;\n /** 布局模式: 'sidebar'(默认,顶栏+侧边栏) | 'topbar'(仅顶栏,无侧边栏) */\n layout?: 'sidebar' | 'topbar';\n sidebarWidth?: number;\n sidebarCollapsedWidth?: number;\n navbarHeight?: number; // Navbar 高度(像素),默认 64px\n searchConfig?: SearchConfig;\n /** Headbar 左侧插槽(传入 ReactNode 或接收 toggleSidebar 的渲染函数) */\n headbarLeft?: React.ReactNode | ((toggleSidebar: () => void) => React.ReactNode);\n /** Headbar 中间插槽 */\n headbarMiddle?: React.ReactNode;\n /** Headbar 右侧插槽 */\n headbarRight?: React.ReactNode;\n};\n\n// 全局配置存储\nlet globalConfig: AppShellConfig = {};\nlet globalAuthState: {\n user: User | null;\n setUser: React.Dispatch<React.SetStateAction<User | null>>;\n} | null = null;\n\n// 导出的布局包装器组件 - 用于路由\nexport const AppLayoutWrapper: React.FC = () => {\n const { title, language, theme, menuItems, getMenuItems, themes, sidebarWidth: configSidebarWidth, sidebarCollapsedWidth, navbarHeight: configNavbarHeight } = useAppContext();\n\n // 从 localStorage 读取侧边栏折叠状态\n const [sidebarCollapsed, setSidebarCollapsed] = useState(() => {\n const saved = localStorage.getItem('sidebar-collapsed');\n return saved ? JSON.parse(saved) : false;\n });\n\n const [sidebarWidth, setSidebarWidth] = useState(configSidebarWidth || 280);\n const sidebarRef = React.useRef<HTMLDivElement>(null);\n\n // 当配置的 sidebarWidth 改变时,更新本地 state\n React.useEffect(() => {\n if (configSidebarWidth) {\n setSidebarWidth(configSidebarWidth);\n }\n }, [configSidebarWidth]);\n\n // 保存侧边栏折叠状态到 localStorage\n React.useEffect(() => {\n localStorage.setItem('sidebar-collapsed', JSON.stringify(sidebarCollapsed));\n }, [sidebarCollapsed]);\n\n const toggleSidebar = () => {\n setSidebarCollapsed(!sidebarCollapsed);\n };\n\n const handleSidebarWidthChange = (width: number) => {\n setSidebarWidth(width);\n };\n\n const navbarHeight = configNavbarHeight || 64; // 默认 64px\n const layout = globalConfig.layout || 'sidebar';\n\n // 解析 headbar 插槽:支持 ReactNode 或渲染函数\n const headbarLeftConfig = globalConfig.headbarLeft;\n const resolvedLeft = typeof headbarLeftConfig === 'function'\n ? headbarLeftConfig(toggleSidebar)\n : headbarLeftConfig;\n\n // topbar 模式:仅顶部导航,无侧边栏\n if (layout === 'topbar') {\n return (\n <div className=\"h-screen flex flex-col bg-base-100\" data-theme={theme}>\n <div style={{ height: `${navbarHeight}px`, flexShrink: 0 }}>\n <Headbar\n left={resolvedLeft}\n middle={globalConfig.headbarMiddle}\n right={globalConfig.headbarRight}\n height={navbarHeight}\n />\n </div>\n <main className=\"flex-1 overflow-y-auto\" style={{ paddingLeft: '128px', paddingRight: '128px', paddingTop: '16px', paddingBottom: '16px' }}>\n <Outlet />\n </main>\n </div>\n );\n }\n\n // sidebar 模式(默认):顶栏 + 侧边栏\n return (\n <div className=\"h-screen flex flex-col bg-base-100\" data-theme={theme}>\n {/* 固定高度的 Headbar */}\n <div style={{ height: `${navbarHeight}px`, flexShrink: 0 }}>\n <Headbar\n left={resolvedLeft}\n middle={globalConfig.headbarMiddle}\n right={globalConfig.headbarRight}\n height={navbarHeight}\n />\n </div>\n\n {/* Sidebar 和 Content 区域 - 独立滚动 */}\n <div className=\"flex flex-1 overflow-hidden\" style={{ height: `calc(100vh - ${navbarHeight}px)` }}>\n {/* Sidebar - 独立滚动 */}\n <div\n className=\"flex-shrink-0\"\n ref={sidebarRef}\n style={{ height: '100%', overflow: 'hidden' }}\n >\n <Sidebar\n isCollapsed={sidebarCollapsed}\n language={language}\n items={menuItems}\n getMenuItems={getMenuItems}\n onWidthChange={handleSidebarWidthChange}\n initialWidth={configSidebarWidth || 280}\n collapsedWidth={sidebarCollapsedWidth || 64}\n />\n </div>\n\n {/* Content - 独立滚动 */}\n <main className=\"flex-1 overflow-y-auto\" style={{ height: '100%', paddingLeft: '128px', paddingRight: '128px', paddingTop: '16px', paddingBottom: '16px' }}>\n <Outlet />\n </main>\n\n {/* 悬浮侧边栏切换按钮 */}\n <SidebarToggleButtons\n isCollapsed={sidebarCollapsed}\n onToggle={toggleSidebar}\n sidebarWidth={sidebarWidth}\n collapsedWidth={sidebarCollapsedWidth || 64}\n />\n </div>\n </div>\n );\n};\n\n// AppShell 属性类型\nexport type AppShellProps = {\n router?: Router<any, any>;\n children?: React.ReactNode;\n} & AppShellConfig;\n\n// AppShell 主组件\nexport const AppShell: React.FC<AppShellProps> = ({\n router,\n children,\n title = 'DiShUI',\n initialLang,\n initialTheme,\n menuItems,\n getMenuItems,\n themes,\n enableAuth = false,\n onAuthRequired,\n protectedRoutes = [],\n publicRoutes = ['/login', '/register'],\n loginUrl = '/login',\n authFailedCallback,\n layout = 'sidebar',\n sidebarWidth = 280,\n sidebarCollapsedWidth = 64,\n navbarHeight = 64,\n searchConfig,\n headbarLeft,\n headbarMiddle,\n headbarRight,\n}) => {\n // 保存全局配置\n globalConfig = {\n title,\n initialLang,\n initialTheme,\n menuItems,\n getMenuItems,\n themes,\n enableAuth,\n onAuthRequired,\n protectedRoutes,\n publicRoutes,\n loginUrl,\n authFailedCallback,\n layout,\n sidebarWidth,\n sidebarCollapsedWidth,\n navbarHeight,\n searchConfig,\n headbarLeft,\n headbarMiddle,\n headbarRight,\n };\n\n // 语言状态管理\n const [language, setLanguage] = useState<'zh' | 'en'>(() => {\n const savedLanguage = localStorage.getItem('app-language');\n if (savedLanguage === 'zh' || savedLanguage === 'en') return savedLanguage;\n return initialLang || 'zh';\n });\n\n // 主题状态管理\n const [theme, setTheme] = useState<ThemeType>(() => {\n const savedTheme = localStorage.getItem('app-theme');\n const validThemes: ThemeType[] = ['light', 'dark', 'cursor', 'retro', 'cyberpunk', 'matrix', 'oceanblue', 'eyecare', 'ghibli', 'pinkgirl', 'mintgirl', 'wuxing', 'jukebox'];\n if (savedTheme && validThemes.includes(savedTheme as ThemeType)) {\n return savedTheme as ThemeType;\n }\n return initialTheme || 'light';\n });\n\n // 用户认证状态管理\n const [user, setUser] = useState<User | null>(() => {\n if (!enableAuth) return null;\n const savedUser = localStorage.getItem('app-user');\n return savedUser ? JSON.parse(savedUser) : null;\n });\n\n // 保存全局认证状态\n globalAuthState = { user, setUser };\n\n // 保存语言设置\n useEffect(() => {\n localStorage.setItem('app-language', language);\n }, [language]);\n\n // 保存主题设置\n useEffect(() => {\n localStorage.setItem('app-theme', theme);\n document.documentElement.setAttribute('data-theme', theme);\n }, [theme]);\n\n // 保存用户信息\n useEffect(() => {\n if (enableAuth) {\n if (user) {\n localStorage.setItem('app-user', JSON.stringify(user));\n } else {\n localStorage.removeItem('app-user');\n }\n }\n }, [user, enableAuth]);\n\n // 认证方法\n const login = (newUser: User) => {\n setUser(newUser);\n };\n\n const logout = () => {\n setUser(null);\n localStorage.removeItem('app-user');\n };\n\n const hasRole = (role: string): boolean => {\n return user?.roles.includes(role) || false;\n };\n\n const hasAnyRole = (roles: string[]): boolean => {\n return roles.some(role => user?.roles.includes(role)) || false;\n };\n\n // 路由守卫 - 检查认证状态\n useEffect(() => {\n if (!enableAuth || !router) return;\n\n const unsubscribe = router.subscribe('onBeforeLoad', ({ pathChanged, toLocation }) => {\n if (!pathChanged) return;\n\n const isPublicRoute = publicRoutes.some(route =>\n toLocation.pathname.startsWith(route)\n );\n\n const isProtectedRoute = protectedRoutes.length === 0 ||\n protectedRoutes.some(route => toLocation.pathname.startsWith(route));\n\n if (isProtectedRoute && !isPublicRoute && !user) {\n // 允许调用方先自行处理认证需求。返回 true 表示已处理,直接放行。\n if (onAuthRequired) {\n const handled = onAuthRequired();\n if (handled) return;\n }\n\n // 未被自定义逻辑处理时,执行认证失败回调或默认重定向。\n if (authFailedCallback) {\n authFailedCallback(toLocation.pathname, loginUrl);\n } else {\n window.location.href = loginUrl;\n }\n }\n });\n\n return () => {\n if (unsubscribe) unsubscribe();\n };\n }, [enableAuth, user, router, protectedRoutes, publicRoutes, onAuthRequired, loginUrl, authFailedCallback]);\n\n const authContext: AuthContextType = {\n user,\n isAuthenticated: !!user,\n login,\n logout,\n hasRole,\n hasAnyRole,\n };\n\n const value: AppContextType = {\n title,\n language,\n theme,\n setLanguage,\n setTheme,\n auth: authContext,\n menuItems,\n getMenuItems,\n themes,\n sidebarWidth,\n sidebarCollapsedWidth,\n navbarHeight,\n };\n\n return (\n <AppContext.Provider value={value}>\n {router ? (\n children || <RouterProvider router={router} />\n ) : (\n children\n )}\n </AppContext.Provider>\n );\n};\n\n// 保持向后兼容的 AppProvider 别名\nexport const AppProvider = AppShell;\n\nexport default AppShell;\n"],"mappings":"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"}
1
+ {"version":3,"file":"AppContext.js","names":[],"sources":["../../src/contexts/AppContext.tsx"],"sourcesContent":["import React, { createContext, useContext, useState, useEffect } from 'react';\nimport { RouterProvider } from '@tanstack/react-router';\nimport { Router } from '@tanstack/react-router';\nimport Sidebar from '../layouts/Sidebar';\nimport { Headbar } from '../layouts/headbar';\nimport type { SearchConfig } from '../layouts/headbar';\nimport { Outlet } from '@tanstack/react-router';\nimport { MenuItem } from '../types/menu';\nimport { ThemeConfig } from '../utils/themes';\nimport { MobileSidebarToggleButton } from '../components/SidebarToggleButton';\n\n// 主题类型\nexport type ThemeType = 'light' | 'dark' | 'cursor' | 'retro' | 'cyberpunk' | 'matrix' | 'oceanblue' | 'eyecare' | 'ghibli' | 'pinkgirl' | 'mintgirl' | 'wuxing' | 'jukebox';\n\n// 用户类型定义\nexport type User = {\n id: string;\n name: string;\n email: string;\n roles: string[];\n};\n\n// 认证上下文类型\nexport type AuthContextType = {\n user: User | null;\n isAuthenticated: boolean;\n login: (user: User) => void;\n logout: () => void;\n hasRole: (role: string) => boolean;\n hasAnyRole: (roles: string[]) => boolean;\n};\n\n// 应用上下文类型\nexport type AppContextType = {\n title?: string;\n language: 'zh' | 'en';\n theme: ThemeType;\n setLanguage: (lang: 'zh' | 'en') => void;\n setTheme: (theme: ThemeType) => void;\n auth: AuthContextType;\n menuItems?: MenuItem[];\n getMenuItems?: (language: 'zh' | 'en') => MenuItem[];\n themes?: ThemeConfig[];\n sidebarWidth?: number;\n sidebarCollapsedWidth?: number;\n navbarHeight?: number;\n};\n\nconst AppContext = createContext<AppContextType | undefined>(undefined);\n\nexport const useAppContext = () => {\n const context = useContext(AppContext);\n if (!context) {\n throw new Error('useAppContext must be used within an AppShell');\n }\n return context;\n};\n\n// 导出独立的认证 hook\nexport const useAuth = () => {\n const context = useAppContext();\n return context.auth;\n};\n\n// AppShell 配置类型\nexport type AppShellConfig = {\n title?: string;\n initialLang?: 'zh' | 'en';\n initialTheme?: ThemeType;\n menuItems?: MenuItem[];\n getMenuItems?: (language: 'zh' | 'en') => MenuItem[];\n themes?: ThemeConfig[];\n enableAuth?: boolean;\n onAuthRequired?: () => boolean | void;\n protectedRoutes?: string[];\n publicRoutes?: string[];\n loginUrl?: string;\n authFailedCallback?: (path: string, loginUrl: string) => void;\n /** 布局模式: 'sidebar'(默认,顶栏+侧边栏) | 'topbar'(仅顶栏,无侧边栏) */\n layout?: 'sidebar' | 'topbar';\n sidebarWidth?: number;\n sidebarCollapsedWidth?: number;\n navbarHeight?: number; // Navbar 高度(像素),默认 64px\n searchConfig?: SearchConfig;\n /** Headbar 左侧插槽(传入 ReactNode 或接收 toggleSidebar 的渲染函数) */\n headbarLeft?: React.ReactNode | ((toggleSidebar: () => void) => React.ReactNode);\n /** Headbar 中间插槽 */\n headbarMiddle?: React.ReactNode;\n /** Headbar 右侧插槽 */\n headbarRight?: React.ReactNode;\n};\n\n// 全局配置存储\nlet globalConfig: AppShellConfig = {};\nlet globalAuthState: {\n user: User | null;\n setUser: React.Dispatch<React.SetStateAction<User | null>>;\n} | null = null;\n\n// 导出的布局包装器组件 - 用于路由\nexport const AppLayoutWrapper: React.FC = () => {\n const { title, language, theme, menuItems, getMenuItems, themes, sidebarWidth: configSidebarWidth, sidebarCollapsedWidth, navbarHeight: configNavbarHeight } = useAppContext();\n\n // 从 localStorage 读取侧边栏折叠状态\n const [sidebarCollapsed, setSidebarCollapsed] = useState(() => {\n const saved = localStorage.getItem('sidebar-collapsed');\n return saved ? JSON.parse(saved) : false;\n });\n\n const [sidebarWidth, setSidebarWidth] = useState(configSidebarWidth || 280);\n const sidebarRef = React.useRef<HTMLDivElement>(null);\n\n // 窄屏(手机)适配:内容留白收窄,侧边栏默认折叠,用户可手动展开\n const [narrowViewport, setNarrowViewport] = useState(() => typeof window !== 'undefined' && window.innerWidth < 768);\n // null = 未手动干预,跟随默认规则(窄屏折叠 / 桌面跟随保存的偏好)\n const [sidebarUserOverride, setSidebarUserOverride] = useState<boolean | null>(null);\n\n useEffect(() => {\n const onResize = () => setNarrowViewport(window.innerWidth < 768);\n window.addEventListener('resize', onResize);\n return () => window.removeEventListener('resize', onResize);\n }, []);\n\n // 当配置的 sidebarWidth 改变时,更新本地 state\n React.useEffect(() => {\n if (configSidebarWidth) {\n setSidebarWidth(configSidebarWidth);\n }\n }, [configSidebarWidth]);\n\n // 保存侧边栏折叠状态到 localStorage\n React.useEffect(() => {\n localStorage.setItem('sidebar-collapsed', JSON.stringify(sidebarCollapsed));\n }, [sidebarCollapsed]);\n\n const effectiveSidebarCollapsed = sidebarUserOverride ?? (narrowViewport || sidebarCollapsed);\n\n const toggleSidebar = () => {\n setSidebarUserOverride(!effectiveSidebarCollapsed);\n };\n\n const contentPaddingX = narrowViewport ? 16 : 128;\n\n const handleSidebarWidthChange = (width: number) => {\n setSidebarWidth(width);\n };\n\n const navbarHeight = configNavbarHeight || 64; // 默认 64px\n const layout = globalConfig.layout || 'sidebar';\n\n // 解析 headbar 插槽:支持 ReactNode 或渲染函数\n const headbarLeftConfig = globalConfig.headbarLeft;\n const resolvedLeft = typeof headbarLeftConfig === 'function'\n ? headbarLeftConfig(toggleSidebar)\n : headbarLeftConfig;\n\n // topbar 模式:仅顶部导航,无侧边栏\n if (layout === 'topbar') {\n return (\n <div className=\"h-screen flex flex-col bg-base-100\" data-theme={theme}>\n <div style={{ height: `${navbarHeight}px`, flexShrink: 0 }}>\n <Headbar\n left={resolvedLeft}\n middle={globalConfig.headbarMiddle}\n right={globalConfig.headbarRight}\n height={navbarHeight}\n />\n </div>\n <main className=\"flex-1 overflow-y-auto\" style={{ paddingLeft: `${contentPaddingX}px`, paddingRight: `${contentPaddingX}px`, paddingTop: '16px', paddingBottom: '16px' }}>\n <Outlet />\n </main>\n </div>\n );\n }\n\n // sidebar 模式(默认):顶栏 + 侧边栏\n return (\n <div className=\"h-screen flex flex-col bg-base-100\" data-theme={theme}>\n {/* 固定高度的 Headbar */}\n <div style={{ height: `${navbarHeight}px`, flexShrink: 0 }}>\n <Headbar\n left={resolvedLeft}\n middle={globalConfig.headbarMiddle}\n right={globalConfig.headbarRight}\n height={navbarHeight}\n />\n </div>\n\n {/* Sidebar 和 Content 区域 - 独立滚动 */}\n <div className=\"flex flex-1 overflow-hidden\" style={{ height: `calc(100vh - ${navbarHeight}px)` }}>\n {/* Sidebar - 独立滚动(overflow visible 以允许右边缘 pill 轻微越界) */}\n <div\n className=\"flex-shrink-0\"\n ref={sidebarRef}\n style={{ height: '100%', overflow: 'visible' }}\n >\n <Sidebar\n isCollapsed={effectiveSidebarCollapsed}\n language={language}\n items={menuItems}\n getMenuItems={getMenuItems}\n onWidthChange={handleSidebarWidthChange}\n onToggle={toggleSidebar}\n initialWidth={sidebarWidth}\n collapsedWidth={sidebarCollapsedWidth || 64}\n />\n </div>\n\n {/* Content - 独立滚动 */}\n <main className=\"flex-1 overflow-y-auto\" style={{ height: '100%', paddingLeft: `${contentPaddingX}px`, paddingRight: `${contentPaddingX}px`, paddingTop: '16px', paddingBottom: '16px' }}>\n <Outlet />\n </main>\n\n {/* 侧边栏切换控件:桌面端 pill 已由 Sidebar 内部渲染,此处仅保留移动端按钮 */}\n <MobileSidebarToggleButton\n isCollapsed={effectiveSidebarCollapsed}\n onToggle={toggleSidebar}\n />\n </div>\n </div>\n );\n};\n\n// AppShell 属性类型\nexport type AppShellProps = {\n router?: Router<any, any>;\n children?: React.ReactNode;\n} & AppShellConfig;\n\n// AppShell 主组件\nexport const AppShell: React.FC<AppShellProps> = ({\n router,\n children,\n title = 'DiShUI',\n initialLang,\n initialTheme,\n menuItems,\n getMenuItems,\n themes,\n enableAuth = false,\n onAuthRequired,\n protectedRoutes = [],\n publicRoutes = ['/login', '/register'],\n loginUrl = '/login',\n authFailedCallback,\n layout = 'sidebar',\n sidebarWidth = 280,\n sidebarCollapsedWidth = 64,\n navbarHeight = 64,\n searchConfig,\n headbarLeft,\n headbarMiddle,\n headbarRight,\n}) => {\n // 保存全局配置\n globalConfig = {\n title,\n initialLang,\n initialTheme,\n menuItems,\n getMenuItems,\n themes,\n enableAuth,\n onAuthRequired,\n protectedRoutes,\n publicRoutes,\n loginUrl,\n authFailedCallback,\n layout,\n sidebarWidth,\n sidebarCollapsedWidth,\n navbarHeight,\n searchConfig,\n headbarLeft,\n headbarMiddle,\n headbarRight,\n };\n\n // 语言状态管理\n const [language, setLanguage] = useState<'zh' | 'en'>(() => {\n const savedLanguage = localStorage.getItem('app-language');\n if (savedLanguage === 'zh' || savedLanguage === 'en') return savedLanguage;\n return initialLang || 'zh';\n });\n\n // 主题状态管理\n const [theme, setTheme] = useState<ThemeType>(() => {\n const savedTheme = localStorage.getItem('app-theme');\n const validThemes: ThemeType[] = ['light', 'dark', 'cursor', 'retro', 'cyberpunk', 'matrix', 'oceanblue', 'eyecare', 'ghibli', 'pinkgirl', 'mintgirl', 'wuxing', 'jukebox'];\n if (savedTheme && validThemes.includes(savedTheme as ThemeType)) {\n return savedTheme as ThemeType;\n }\n return initialTheme || 'light';\n });\n\n // 用户认证状态管理\n const [user, setUser] = useState<User | null>(() => {\n if (!enableAuth) return null;\n const savedUser = localStorage.getItem('app-user');\n return savedUser ? JSON.parse(savedUser) : null;\n });\n\n // 保存全局认证状态\n globalAuthState = { user, setUser };\n\n // 保存语言设置\n useEffect(() => {\n localStorage.setItem('app-language', language);\n }, [language]);\n\n // 保存主题设置\n useEffect(() => {\n localStorage.setItem('app-theme', theme);\n document.documentElement.setAttribute('data-theme', theme);\n }, [theme]);\n\n // 保存用户信息\n useEffect(() => {\n if (enableAuth) {\n if (user) {\n localStorage.setItem('app-user', JSON.stringify(user));\n } else {\n localStorage.removeItem('app-user');\n }\n }\n }, [user, enableAuth]);\n\n // 认证方法\n const login = (newUser: User) => {\n setUser(newUser);\n };\n\n const logout = () => {\n setUser(null);\n localStorage.removeItem('app-user');\n };\n\n const hasRole = (role: string): boolean => {\n return user?.roles.includes(role) || false;\n };\n\n const hasAnyRole = (roles: string[]): boolean => {\n return roles.some(role => user?.roles.includes(role)) || false;\n };\n\n // 路由守卫 - 检查认证状态\n useEffect(() => {\n if (!enableAuth || !router) return;\n\n const unsubscribe = router.subscribe('onBeforeLoad', ({ pathChanged, toLocation }) => {\n if (!pathChanged) return;\n\n const isPublicRoute = publicRoutes.some(route =>\n toLocation.pathname.startsWith(route)\n );\n\n const isProtectedRoute = protectedRoutes.length === 0 ||\n protectedRoutes.some(route => toLocation.pathname.startsWith(route));\n\n if (isProtectedRoute && !isPublicRoute && !user) {\n // 允许调用方先自行处理认证需求。返回 true 表示已处理,直接放行。\n if (onAuthRequired) {\n const handled = onAuthRequired();\n if (handled) return;\n }\n\n // 未被自定义逻辑处理时,执行认证失败回调或默认重定向。\n if (authFailedCallback) {\n authFailedCallback(toLocation.pathname, loginUrl);\n } else {\n window.location.href = loginUrl;\n }\n }\n });\n\n return () => {\n if (unsubscribe) unsubscribe();\n };\n }, [enableAuth, user, router, protectedRoutes, publicRoutes, onAuthRequired, loginUrl, authFailedCallback]);\n\n const authContext: AuthContextType = {\n user,\n isAuthenticated: !!user,\n login,\n logout,\n hasRole,\n hasAnyRole,\n };\n\n const value: AppContextType = {\n title,\n language,\n theme,\n setLanguage,\n setTheme,\n auth: authContext,\n menuItems,\n getMenuItems,\n themes,\n sidebarWidth,\n sidebarCollapsedWidth,\n navbarHeight,\n };\n\n return (\n <AppContext.Provider value={value}>\n {router ? (\n children || <RouterProvider router={router} />\n ) : (\n children\n )}\n </AppContext.Provider>\n );\n};\n\n// 保持向后兼容的 AppProvider 别名\nexport const AppProvider = AppShell;\n\nexport default AppShell;\n"],"mappings":"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"}