@h-ai/ui 0.1.0-alpha.10

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 (237) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +500 -0
  3. package/auto-import.d.ts +96 -0
  4. package/auto-import.js +263 -0
  5. package/dist/components/compounds/Accordion.svelte +142 -0
  6. package/dist/components/compounds/Accordion.svelte.d.ts +25 -0
  7. package/dist/components/compounds/ActionSheet.svelte +116 -0
  8. package/dist/components/compounds/ActionSheet.svelte.d.ts +22 -0
  9. package/dist/components/compounds/Alert.svelte +67 -0
  10. package/dist/components/compounds/Alert.svelte.d.ts +8 -0
  11. package/dist/components/compounds/AppBar.svelte +77 -0
  12. package/dist/components/compounds/AppBar.svelte.d.ts +20 -0
  13. package/dist/components/compounds/BottomNav.svelte +75 -0
  14. package/dist/components/compounds/BottomNav.svelte.d.ts +18 -0
  15. package/dist/components/compounds/Breadcrumb.svelte +49 -0
  16. package/dist/components/compounds/Breadcrumb.svelte.d.ts +4 -0
  17. package/dist/components/compounds/Calendar.svelte +116 -0
  18. package/dist/components/compounds/Calendar.svelte.d.ts +47 -0
  19. package/dist/components/compounds/Card.svelte +77 -0
  20. package/dist/components/compounds/Card.svelte.d.ts +8 -0
  21. package/dist/components/compounds/Combobox.svelte +327 -0
  22. package/dist/components/compounds/Combobox.svelte.d.ts +69 -0
  23. package/dist/components/compounds/Confirm.svelte +90 -0
  24. package/dist/components/compounds/Confirm.svelte.d.ts +4 -0
  25. package/dist/components/compounds/DataTable.svelte +133 -0
  26. package/dist/components/compounds/DataTable.svelte.d.ts +69 -0
  27. package/dist/components/compounds/DatePicker.svelte +135 -0
  28. package/dist/components/compounds/DatePicker.svelte.d.ts +46 -0
  29. package/dist/components/compounds/Drawer.svelte +113 -0
  30. package/dist/components/compounds/Drawer.svelte.d.ts +4 -0
  31. package/dist/components/compounds/Dropdown.svelte +91 -0
  32. package/dist/components/compounds/Dropdown.svelte.d.ts +8 -0
  33. package/dist/components/compounds/Empty.svelte +83 -0
  34. package/dist/components/compounds/Empty.svelte.d.ts +4 -0
  35. package/dist/components/compounds/Form.svelte +96 -0
  36. package/dist/components/compounds/Form.svelte.d.ts +7 -0
  37. package/dist/components/compounds/FormField.svelte +56 -0
  38. package/dist/components/compounds/FormField.svelte.d.ts +4 -0
  39. package/dist/components/compounds/InfiniteScroll.svelte +82 -0
  40. package/dist/components/compounds/InfiniteScroll.svelte.d.ts +22 -0
  41. package/dist/components/compounds/Modal.svelte +123 -0
  42. package/dist/components/compounds/Modal.svelte.d.ts +8 -0
  43. package/dist/components/compounds/PageHeader.svelte +44 -0
  44. package/dist/components/compounds/PageHeader.svelte.d.ts +28 -0
  45. package/dist/components/compounds/Pagination.svelte +157 -0
  46. package/dist/components/compounds/Pagination.svelte.d.ts +4 -0
  47. package/dist/components/compounds/Popover.svelte +137 -0
  48. package/dist/components/compounds/Popover.svelte.d.ts +4 -0
  49. package/dist/components/compounds/PullRefresh.svelte +98 -0
  50. package/dist/components/compounds/PullRefresh.svelte.d.ts +16 -0
  51. package/dist/components/compounds/Result.svelte +84 -0
  52. package/dist/components/compounds/Result.svelte.d.ts +4 -0
  53. package/dist/components/compounds/SafeArea.svelte +37 -0
  54. package/dist/components/compounds/SafeArea.svelte.d.ts +12 -0
  55. package/dist/components/compounds/Skeleton.svelte +75 -0
  56. package/dist/components/compounds/Skeleton.svelte.d.ts +8 -0
  57. package/dist/components/compounds/Steps.svelte +111 -0
  58. package/dist/components/compounds/Steps.svelte.d.ts +4 -0
  59. package/dist/components/compounds/SwipeCell.svelte +115 -0
  60. package/dist/components/compounds/SwipeCell.svelte.d.ts +19 -0
  61. package/dist/components/compounds/Tabs.svelte +79 -0
  62. package/dist/components/compounds/Tabs.svelte.d.ts +8 -0
  63. package/dist/components/compounds/TagInput.svelte +108 -0
  64. package/dist/components/compounds/TagInput.svelte.d.ts +4 -0
  65. package/dist/components/compounds/Timeline.svelte +167 -0
  66. package/dist/components/compounds/Timeline.svelte.d.ts +25 -0
  67. package/dist/components/compounds/ToastContainer.svelte +59 -0
  68. package/dist/components/compounds/ToastContainer.svelte.d.ts +3 -0
  69. package/dist/components/compounds/Tooltip.svelte +41 -0
  70. package/dist/components/compounds/Tooltip.svelte.d.ts +4 -0
  71. package/dist/components/compounds/accordion-types.d.ts +16 -0
  72. package/dist/components/compounds/accordion-types.js +5 -0
  73. package/dist/components/compounds/action-sheet-types.d.ts +14 -0
  74. package/dist/components/compounds/action-sheet-types.js +5 -0
  75. package/dist/components/compounds/bottom-nav-types.d.ts +17 -0
  76. package/dist/components/compounds/bottom-nav-types.js +5 -0
  77. package/dist/components/compounds/index.d.ts +44 -0
  78. package/dist/components/compounds/index.js +54 -0
  79. package/dist/components/compounds/swipe-cell-types.d.ts +14 -0
  80. package/dist/components/compounds/swipe-cell-types.js +5 -0
  81. package/dist/components/compounds/timeline-types.d.ts +20 -0
  82. package/dist/components/compounds/timeline-types.js +5 -0
  83. package/dist/components/index.d.ts +9 -0
  84. package/dist/components/index.js +12 -0
  85. package/dist/components/primitives/Avatar.svelte +104 -0
  86. package/dist/components/primitives/Avatar.svelte.d.ts +8 -0
  87. package/dist/components/primitives/Badge.svelte +33 -0
  88. package/dist/components/primitives/Badge.svelte.d.ts +8 -0
  89. package/dist/components/primitives/BareButton.svelte +45 -0
  90. package/dist/components/primitives/BareButton.svelte.d.ts +4 -0
  91. package/dist/components/primitives/BareInput.svelte +101 -0
  92. package/dist/components/primitives/BareInput.svelte.d.ts +4 -0
  93. package/dist/components/primitives/Button.svelte +60 -0
  94. package/dist/components/primitives/Button.svelte.d.ts +8 -0
  95. package/dist/components/primitives/Checkbox.svelte +58 -0
  96. package/dist/components/primitives/Checkbox.svelte.d.ts +4 -0
  97. package/dist/components/primitives/IconButton.svelte +105 -0
  98. package/dist/components/primitives/IconButton.svelte.d.ts +4 -0
  99. package/dist/components/primitives/Input.svelte +130 -0
  100. package/dist/components/primitives/Input.svelte.d.ts +8 -0
  101. package/dist/components/primitives/Progress.svelte +58 -0
  102. package/dist/components/primitives/Progress.svelte.d.ts +4 -0
  103. package/dist/components/primitives/Radio.svelte +60 -0
  104. package/dist/components/primitives/Radio.svelte.d.ts +25 -0
  105. package/dist/components/primitives/Range.svelte +117 -0
  106. package/dist/components/primitives/Range.svelte.d.ts +27 -0
  107. package/dist/components/primitives/Rating.svelte +148 -0
  108. package/dist/components/primitives/Rating.svelte.d.ts +27 -0
  109. package/dist/components/primitives/Select.svelte +95 -0
  110. package/dist/components/primitives/Select.svelte.d.ts +25 -0
  111. package/dist/components/primitives/Spinner.svelte +54 -0
  112. package/dist/components/primitives/Spinner.svelte.d.ts +4 -0
  113. package/dist/components/primitives/Switch.svelte +53 -0
  114. package/dist/components/primitives/Switch.svelte.d.ts +4 -0
  115. package/dist/components/primitives/Tag.svelte +64 -0
  116. package/dist/components/primitives/Tag.svelte.d.ts +4 -0
  117. package/dist/components/primitives/Textarea.svelte +92 -0
  118. package/dist/components/primitives/Textarea.svelte.d.ts +4 -0
  119. package/dist/components/primitives/ToggleCheckbox.svelte +36 -0
  120. package/dist/components/primitives/ToggleCheckbox.svelte.d.ts +4 -0
  121. package/dist/components/primitives/ToggleInput.svelte +36 -0
  122. package/dist/components/primitives/ToggleInput.svelte.d.ts +4 -0
  123. package/dist/components/primitives/ToggleRadio.svelte +36 -0
  124. package/dist/components/primitives/ToggleRadio.svelte.d.ts +4 -0
  125. package/dist/components/primitives/index.d.ts +26 -0
  126. package/dist/components/primitives/index.js +30 -0
  127. package/dist/components/scenes/ai/AiDocumentDownloadMenu.svelte +387 -0
  128. package/dist/components/scenes/ai/AiDocumentDownloadMenu.svelte.d.ts +38 -0
  129. package/dist/components/scenes/ai/AiDocumentEditor.svelte +2209 -0
  130. package/dist/components/scenes/ai/AiDocumentEditor.svelte.d.ts +4 -0
  131. package/dist/components/scenes/ai/MarkdownRenderer.svelte +412 -0
  132. package/dist/components/scenes/ai/MarkdownRenderer.svelte.d.ts +4 -0
  133. package/dist/components/scenes/ai/document-download.d.ts +40 -0
  134. package/dist/components/scenes/ai/document-download.js +300 -0
  135. package/dist/components/scenes/ai/document-parse.d.ts +28 -0
  136. package/dist/components/scenes/ai/document-parse.js +243 -0
  137. package/dist/components/scenes/ai/document-types.d.ts +164 -0
  138. package/dist/components/scenes/ai/document-types.js +1 -0
  139. package/dist/components/scenes/ai/highlight.d.ts +26 -0
  140. package/dist/components/scenes/ai/highlight.js +151 -0
  141. package/dist/components/scenes/ai/index.d.ts +17 -0
  142. package/dist/components/scenes/ai/index.js +13 -0
  143. package/dist/components/scenes/ai/markdown-parse.d.ts +32 -0
  144. package/dist/components/scenes/ai/markdown-parse.js +164 -0
  145. package/dist/components/scenes/app/FeedbackModal.svelte +160 -0
  146. package/dist/components/scenes/app/FeedbackModal.svelte.d.ts +47 -0
  147. package/dist/components/scenes/app/LanguageSwitch.svelte +136 -0
  148. package/dist/components/scenes/app/LanguageSwitch.svelte.d.ts +50 -0
  149. package/dist/components/scenes/app/SettingsModal.svelte +174 -0
  150. package/dist/components/scenes/app/SettingsModal.svelte.d.ts +31 -0
  151. package/dist/components/scenes/app/ThemeSelector.svelte +176 -0
  152. package/dist/components/scenes/app/ThemeSelector.svelte.d.ts +31 -0
  153. package/dist/components/scenes/app/ThemeToggle.svelte +76 -0
  154. package/dist/components/scenes/app/ThemeToggle.svelte.d.ts +29 -0
  155. package/dist/components/scenes/app/index.d.ts +11 -0
  156. package/dist/components/scenes/app/index.js +11 -0
  157. package/dist/components/scenes/crud/CrudDeleteConfirm.svelte +39 -0
  158. package/dist/components/scenes/crud/CrudDeleteConfirm.svelte.d.ts +18 -0
  159. package/dist/components/scenes/crud/CrudDetailDrawer.svelte +155 -0
  160. package/dist/components/scenes/crud/CrudDetailDrawer.svelte.d.ts +38 -0
  161. package/dist/components/scenes/crud/CrudEditDrawer.svelte +225 -0
  162. package/dist/components/scenes/crud/CrudEditDrawer.svelte.d.ts +48 -0
  163. package/dist/components/scenes/crud/CrudFilterBar.svelte +96 -0
  164. package/dist/components/scenes/crud/CrudFilterBar.svelte.d.ts +31 -0
  165. package/dist/components/scenes/crud/CrudPage.svelte +499 -0
  166. package/dist/components/scenes/crud/CrudPage.svelte.d.ts +114 -0
  167. package/dist/components/scenes/crud/crud-types.d.ts +135 -0
  168. package/dist/components/scenes/crud/crud-types.js +7 -0
  169. package/dist/components/scenes/crud/index.d.ts +12 -0
  170. package/dist/components/scenes/crud/index.js +11 -0
  171. package/dist/components/scenes/crypto/EncryptedInput.svelte +156 -0
  172. package/dist/components/scenes/crypto/EncryptedInput.svelte.d.ts +32 -0
  173. package/dist/components/scenes/crypto/HashDisplay.svelte +97 -0
  174. package/dist/components/scenes/crypto/HashDisplay.svelte.d.ts +4 -0
  175. package/dist/components/scenes/crypto/SignatureDisplay.svelte +161 -0
  176. package/dist/components/scenes/crypto/SignatureDisplay.svelte.d.ts +4 -0
  177. package/dist/components/scenes/crypto/index.d.ts +9 -0
  178. package/dist/components/scenes/crypto/index.js +11 -0
  179. package/dist/components/scenes/iam/ChangePasswordForm.svelte +134 -0
  180. package/dist/components/scenes/iam/ChangePasswordForm.svelte.d.ts +8 -0
  181. package/dist/components/scenes/iam/ForgotPasswordForm.svelte +147 -0
  182. package/dist/components/scenes/iam/ForgotPasswordForm.svelte.d.ts +4 -0
  183. package/dist/components/scenes/iam/LoginForm.svelte +191 -0
  184. package/dist/components/scenes/iam/LoginForm.svelte.d.ts +8 -0
  185. package/dist/components/scenes/iam/PasswordInput.svelte +208 -0
  186. package/dist/components/scenes/iam/PasswordInput.svelte.d.ts +49 -0
  187. package/dist/components/scenes/iam/PermGuard.svelte +69 -0
  188. package/dist/components/scenes/iam/PermGuard.svelte.d.ts +16 -0
  189. package/dist/components/scenes/iam/RegisterForm.svelte +269 -0
  190. package/dist/components/scenes/iam/RegisterForm.svelte.d.ts +8 -0
  191. package/dist/components/scenes/iam/ResetPasswordForm.svelte +153 -0
  192. package/dist/components/scenes/iam/ResetPasswordForm.svelte.d.ts +8 -0
  193. package/dist/components/scenes/iam/UserProfile.svelte +251 -0
  194. package/dist/components/scenes/iam/UserProfile.svelte.d.ts +4 -0
  195. package/dist/components/scenes/iam/index.d.ts +16 -0
  196. package/dist/components/scenes/iam/index.js +18 -0
  197. package/dist/components/scenes/iam/password-utils.d.ts +15 -0
  198. package/dist/components/scenes/iam/password-utils.js +19 -0
  199. package/dist/components/scenes/iam/permission-context.svelte.d.ts +90 -0
  200. package/dist/components/scenes/iam/permission-context.svelte.js +114 -0
  201. package/dist/components/scenes/index.d.ts +13 -0
  202. package/dist/components/scenes/index.js +20 -0
  203. package/dist/components/scenes/storage/AvatarUpload.svelte +218 -0
  204. package/dist/components/scenes/storage/AvatarUpload.svelte.d.ts +4 -0
  205. package/dist/components/scenes/storage/FileList.svelte +260 -0
  206. package/dist/components/scenes/storage/FileList.svelte.d.ts +4 -0
  207. package/dist/components/scenes/storage/FileUpload.svelte +402 -0
  208. package/dist/components/scenes/storage/FileUpload.svelte.d.ts +7 -0
  209. package/dist/components/scenes/storage/ImageUpload.svelte +287 -0
  210. package/dist/components/scenes/storage/ImageUpload.svelte.d.ts +4 -0
  211. package/dist/components/scenes/storage/index.d.ts +10 -0
  212. package/dist/components/scenes/storage/index.js +12 -0
  213. package/dist/components/scenes/types.d.ts +583 -0
  214. package/dist/components/scenes/types.js +7 -0
  215. package/dist/i18n.svelte.d.ts +44 -0
  216. package/dist/i18n.svelte.js +108 -0
  217. package/dist/index.d.ts +13 -0
  218. package/dist/index.js +19 -0
  219. package/dist/messages/en-US.json +242 -0
  220. package/dist/messages/zh-CN.json +242 -0
  221. package/dist/messages.d.ts +29 -0
  222. package/dist/messages.js +42 -0
  223. package/dist/styles/design-tokens.css +58 -0
  224. package/dist/styles/global.css +258 -0
  225. package/dist/styles/mobile.css +105 -0
  226. package/dist/styles/theme.css +31 -0
  227. package/dist/theme-config.d.ts +92 -0
  228. package/dist/theme-config.js +122 -0
  229. package/dist/toast.svelte.d.ts +45 -0
  230. package/dist/toast.svelte.js +77 -0
  231. package/dist/types.d.ts +944 -0
  232. package/dist/types.js +7 -0
  233. package/dist/utils/platform.svelte.d.ts +54 -0
  234. package/dist/utils/platform.svelte.js +89 -0
  235. package/dist/utils.d.ts +46 -0
  236. package/dist/utils.js +163 -0
  237. package/package.json +70 -0
@@ -0,0 +1,176 @@
1
+ <!--
2
+ @component ThemeSelector
3
+ 主题选择器组件,支持内置主题集合的选择和预览。
4
+
5
+ @prop {string} currentTheme - 当前主题
6
+ @prop {function} onchange - 主题变更回调
7
+ @prop {boolean} showPreview - 是否显示主题预览
8
+ @prop {boolean} grouped - 是否按分组显示
9
+
10
+ @example
11
+ <ThemeSelector
12
+ currentTheme={$theme}
13
+ onchange={(theme) => setTheme(theme)}
14
+ showPreview
15
+ grouped
16
+ />
17
+ -->
18
+ <script lang='ts'>
19
+ import type { ThemeInfo } from '../../../theme-config.js'
20
+ import { uiM } from '../../../messages.js'
21
+ import { THEME_GROUPS, THEMES } from '../../../theme-config.js'
22
+ import { cn } from '../../../utils.js'
23
+ import BareButton from '../../primitives/BareButton.svelte'
24
+
25
+ interface Props {
26
+ currentTheme?: string
27
+ /** 选择主题按钮的 aria-label */
28
+ selectLabel?: string
29
+ onchange?: (theme: string) => void
30
+ showPreview?: boolean
31
+ /** 紧凑模式:仅显示预览色块,适用于移动端头部 */
32
+ compact?: boolean
33
+ grouped?: boolean
34
+ class?: string
35
+ }
36
+
37
+ const {
38
+ currentTheme = 'light',
39
+ selectLabel,
40
+ onchange,
41
+ showPreview = true,
42
+ compact = false,
43
+ grouped = true,
44
+ class: className = '',
45
+ }: Props = $props()
46
+
47
+ const displaySelectLabel = $derived(selectLabel ?? uiM('theme_selector_label'))
48
+
49
+ let open = $state(false)
50
+ let containerRef = $state<HTMLDivElement | null>(null)
51
+
52
+ const currentInfo = $derived(
53
+ THEMES.find(t => t.id === currentTheme) ?? THEMES[0],
54
+ )
55
+
56
+ function selectTheme(themeId: string) {
57
+ onchange?.(themeId)
58
+ open = false
59
+ }
60
+
61
+ function renderThemePreview(theme: ThemeInfo) {
62
+ return `
63
+ background: ${theme.bgColor};
64
+ border-color: ${theme.primaryColor};
65
+ `
66
+ }
67
+
68
+ function handleClickOutside(event: MouseEvent) {
69
+ if (containerRef && !containerRef.contains(event.target as Node)) {
70
+ open = false
71
+ }
72
+ }
73
+
74
+ $effect(() => {
75
+ if (open) {
76
+ document.addEventListener('click', handleClickOutside)
77
+ return () => document.removeEventListener('click', handleClickOutside)
78
+ }
79
+ return undefined
80
+ })
81
+ </script>
82
+
83
+ <div bind:this={containerRef} class='dropdown dropdown-end {open ? 'dropdown-open' : ''} {className}'>
84
+ <BareButton
85
+ type='button'
86
+ class='btn btn-ghost {compact ? 'btn-sm btn-square' : 'gap-2'}'
87
+ onclick={() => (open = !open)}
88
+ ariaLabel={displaySelectLabel}
89
+ >
90
+ <!-- 当前主题预览 -->
91
+ {#if showPreview}
92
+ <div
93
+ class='w-5 h-5 rounded-full border-2 shrink-0'
94
+ style={renderThemePreview(currentInfo)}
95
+ ></div>
96
+ {/if}
97
+ {#if !compact}
98
+ <span class='text-sm'>{currentInfo.name}</span>
99
+ <!-- 下拉箭头 -->
100
+ <svg xmlns='http://www.w3.org/2000/svg' class='h-4 w-4' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'>
101
+ <path stroke-linecap='round' stroke-linejoin='round' d='M19 9l-7 7-7-7' />
102
+ </svg>
103
+ {/if}
104
+ </BareButton>
105
+
106
+ {#if open}
107
+ <!-- 主题列表 -->
108
+ <div class='dropdown-content bg-base-100 rounded-box shadow-xl border border-base-content/10 z-50 p-4 max-h-[80vh] overflow-y-auto {compact ? 'w-56 right-0' : ''}'>
109
+ {#if grouped}
110
+ <!-- 分组显示 -->
111
+ {#each THEME_GROUPS as group}
112
+ <div class='mb-4 last:mb-0'>
113
+ <div class='text-xs font-semibold text-base-content/50 uppercase tracking-wider px-2 mb-2'>
114
+ {uiM(group.nameKey)}
115
+ </div>
116
+ <div class='flex flex-wrap gap-2'>
117
+ {#each group.themes as theme (theme.id)}
118
+ <BareButton
119
+ type='button'
120
+ class={cn(
121
+ 'flex items-center gap-2 px-3 py-1.5 rounded-lg text-left transition-colors w-28',
122
+ theme.id === currentTheme
123
+ ? 'bg-primary/10 text-primary ring-1 ring-primary/30'
124
+ : 'hover:bg-base-200',
125
+ )}
126
+ onclick={() => selectTheme(theme.id)}
127
+ >
128
+ <!-- 主题预览色块 -->
129
+ <div
130
+ class='w-4 h-4 rounded border shrink-0'
131
+ style={renderThemePreview(theme)}
132
+ ></div>
133
+ <!-- 主题名称 -->
134
+ <span class='text-sm whitespace-nowrap'>{theme.name}</span>
135
+ <!-- 选中标记 -->
136
+ {#if theme.id === currentTheme}
137
+ <svg xmlns='http://www.w3.org/2000/svg' class='h-4 w-4 text-primary shrink-0' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'>
138
+ <path stroke-linecap='round' stroke-linejoin='round' d='M5 13l4 4L19 7' />
139
+ </svg>
140
+ {/if}
141
+ </BareButton>
142
+ {/each}
143
+ </div>
144
+ </div>
145
+ {/each}
146
+ {:else}
147
+ <!-- 平铺显示 -->
148
+ <div class='flex flex-wrap gap-2'>
149
+ {#each THEMES as theme (theme.id)}
150
+ <BareButton
151
+ type='button'
152
+ class={cn(
153
+ 'flex items-center gap-2 px-3 py-1.5 rounded-lg text-left transition-colors w-28',
154
+ theme.id === currentTheme
155
+ ? 'bg-primary/10 text-primary ring-1 ring-primary/30'
156
+ : 'hover:bg-base-200',
157
+ )}
158
+ onclick={() => selectTheme(theme.id)}
159
+ >
160
+ <div
161
+ class='w-4 h-4 rounded border shrink-0'
162
+ style={renderThemePreview(theme)}
163
+ ></div>
164
+ <span class='text-sm whitespace-nowrap'>{theme.name}</span>
165
+ {#if theme.id === currentTheme}
166
+ <svg xmlns='http://www.w3.org/2000/svg' class='h-4 w-4 text-primary shrink-0' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'>
167
+ <path stroke-linecap='round' stroke-linejoin='round' d='M5 13l4 4L19 7' />
168
+ </svg>
169
+ {/if}
170
+ </BareButton>
171
+ {/each}
172
+ </div>
173
+ {/if}
174
+ </div>
175
+ {/if}
176
+ </div>
@@ -0,0 +1,31 @@
1
+ interface Props {
2
+ currentTheme?: string;
3
+ /** 选择主题按钮的 aria-label */
4
+ selectLabel?: string;
5
+ onchange?: (theme: string) => void;
6
+ showPreview?: boolean;
7
+ /** 紧凑模式:仅显示预览色块,适用于移动端头部 */
8
+ compact?: boolean;
9
+ grouped?: boolean;
10
+ class?: string;
11
+ }
12
+ /**
13
+ * ThemeSelector
14
+ * 主题选择器组件,支持内置主题集合的选择和预览。
15
+ *
16
+ * @prop {string} currentTheme - 当前主题
17
+ * @prop {function} onchange - 主题变更回调
18
+ * @prop {boolean} showPreview - 是否显示主题预览
19
+ * @prop {boolean} grouped - 是否按分组显示
20
+ *
21
+ * @example
22
+ * <ThemeSelector
23
+ * currentTheme={$theme}
24
+ * onchange={(theme) => setTheme(theme)}
25
+ * showPreview
26
+ * grouped
27
+ * />
28
+ */
29
+ declare const ThemeSelector: import("svelte").Component<Props, {}, "">;
30
+ type ThemeSelector = ReturnType<typeof ThemeSelector>;
31
+ export default ThemeSelector;
@@ -0,0 +1,76 @@
1
+ <!--
2
+ @component ThemeToggle
3
+ 主题切换按钮组件,支持明/暗主题快速切换。
4
+
5
+ @prop {string} currentTheme - 当前主题
6
+ @prop {function} onchange - 主题变更回调
7
+ @prop {string} lightTheme - 亮色主题名称
8
+ @prop {string} darkTheme - 暗色主题名称
9
+
10
+ @example
11
+ <ThemeToggle
12
+ currentTheme={$theme}
13
+ onchange={(theme) => themeStore.set(theme)}
14
+ />
15
+ -->
16
+ <script lang='ts'>
17
+ import { uiM } from '../../../messages.js'
18
+ import { isDarkTheme } from '../../../theme-config.js'
19
+ import IconButton from '../../primitives/IconButton.svelte'
20
+
21
+ interface Props {
22
+ currentTheme?: string
23
+ onchange?: (theme: string) => void
24
+ lightTheme?: string
25
+ darkTheme?: string
26
+ labels?: {
27
+ switchToLight?: string
28
+ switchToDark?: string
29
+ }
30
+ class?: string
31
+ }
32
+
33
+ const {
34
+ currentTheme = 'light',
35
+ onchange,
36
+ lightTheme = 'light',
37
+ darkTheme = 'dark',
38
+ labels = {},
39
+ class: className = '',
40
+ }: Props = $props()
41
+
42
+ // labels 优先,缺省回退到内置文案
43
+ const isDark = $derived(isDarkTheme(currentTheme))
44
+
45
+ function toggleTheme() {
46
+ const newTheme = isDark ? lightTheme : darkTheme
47
+ onchange?.(newTheme)
48
+ }
49
+ </script>
50
+
51
+ <div class='tooltip tooltip-bottom {className}' data-tip={isDark ? (labels.switchToLight ?? uiM('theme_switch_to_light')) : (labels.switchToDark ?? uiM('theme_switch_to_dark'))}>
52
+ <IconButton
53
+ variant='ghost'
54
+ size='sm'
55
+ onclick={toggleTheme}
56
+ ariaLabel={isDark ? (labels.switchToLight ?? uiM('theme_switch_to_light')) : (labels.switchToDark ?? uiM('theme_switch_to_dark'))}
57
+ >
58
+ {#if isDark}
59
+ <svg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'>
60
+ <circle cx='12' cy='12' r='5' />
61
+ <line x1='12' y1='1' x2='12' y2='3' />
62
+ <line x1='12' y1='21' x2='12' y2='23' />
63
+ <line x1='4.22' y1='4.22' x2='5.64' y2='5.64' />
64
+ <line x1='18.36' y1='18.36' x2='19.78' y2='19.78' />
65
+ <line x1='1' y1='12' x2='3' y2='12' />
66
+ <line x1='21' y1='12' x2='23' y2='12' />
67
+ <line x1='4.22' y1='19.78' x2='5.64' y2='18.36' />
68
+ <line x1='18.36' y1='5.64' x2='19.78' y2='4.22' />
69
+ </svg>
70
+ {:else}
71
+ <svg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'>
72
+ <path d='M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z' />
73
+ </svg>
74
+ {/if}
75
+ </IconButton>
76
+ </div>
@@ -0,0 +1,29 @@
1
+ interface Props {
2
+ currentTheme?: string;
3
+ onchange?: (theme: string) => void;
4
+ lightTheme?: string;
5
+ darkTheme?: string;
6
+ labels?: {
7
+ switchToLight?: string;
8
+ switchToDark?: string;
9
+ };
10
+ class?: string;
11
+ }
12
+ /**
13
+ * ThemeToggle
14
+ * 主题切换按钮组件,支持明/暗主题快速切换。
15
+ *
16
+ * @prop {string} currentTheme - 当前主题
17
+ * @prop {function} onchange - 主题变更回调
18
+ * @prop {string} lightTheme - 亮色主题名称
19
+ * @prop {string} darkTheme - 暗色主题名称
20
+ *
21
+ * @example
22
+ * <ThemeToggle
23
+ * currentTheme={$theme}
24
+ * onchange={(theme) => themeStore.set(theme)}
25
+ * />
26
+ */
27
+ declare const ThemeToggle: import("svelte").Component<Props, {}, "">;
28
+ type ThemeToggle = ReturnType<typeof ThemeToggle>;
29
+ export default ThemeToggle;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @h-ai/ui — 应用级场景组件 (App Scenes)
3
+ *
4
+ * 面向应用层的通用功能组件,包含设置、反馈、语言/主题切换等。 内置中英文翻译,自动响应全局 locale。
5
+ * @module index
6
+ */
7
+ export { default as FeedbackModal } from './FeedbackModal.svelte';
8
+ export { default as LanguageSwitch } from './LanguageSwitch.svelte';
9
+ export { default as SettingsModal } from './SettingsModal.svelte';
10
+ export { default as ThemeSelector } from './ThemeSelector.svelte';
11
+ export { default as ThemeToggle } from './ThemeToggle.svelte';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @h-ai/ui — 应用级场景组件 (App Scenes)
3
+ *
4
+ * 面向应用层的通用功能组件,包含设置、反馈、语言/主题切换等。 内置中英文翻译,自动响应全局 locale。
5
+ * @module index
6
+ */
7
+ export { default as FeedbackModal } from './FeedbackModal.svelte';
8
+ export { default as LanguageSwitch } from './LanguageSwitch.svelte';
9
+ export { default as SettingsModal } from './SettingsModal.svelte';
10
+ export { default as ThemeSelector } from './ThemeSelector.svelte';
11
+ export { default as ThemeToggle } from './ThemeToggle.svelte';
@@ -0,0 +1,39 @@
1
+ <!--
2
+ @component CrudDeleteConfirm
3
+ CRUD 删除确认对话框
4
+
5
+ 使用 Svelte 5 Runes ($props)
6
+ 使用 compounds 组件:Confirm
7
+ -->
8
+ <script lang='ts'>
9
+ import { uiM } from '../../../messages.js'
10
+ import Confirm from '../../compounds/Confirm.svelte'
11
+
12
+ let {
13
+ open = $bindable(false),
14
+ loading = false,
15
+ title = '',
16
+ message = '',
17
+ onconfirm,
18
+ oncancel,
19
+ }: {
20
+ open?: boolean
21
+ loading?: boolean
22
+ title?: string
23
+ message?: string
24
+ onconfirm?: () => Promise<void> | void
25
+ oncancel?: () => void
26
+ } = $props()
27
+ </script>
28
+
29
+ <Confirm
30
+ bind:open
31
+ title={title || uiM('crud_delete_title')}
32
+ message={message || uiM('crud_delete_message')}
33
+ confirmText={uiM('crud_delete')}
34
+ cancelText={uiM('crud_cancel')}
35
+ variant='error'
36
+ {loading}
37
+ {onconfirm}
38
+ {oncancel}
39
+ />
@@ -0,0 +1,18 @@
1
+ type $$ComponentProps = {
2
+ open?: boolean;
3
+ loading?: boolean;
4
+ title?: string;
5
+ message?: string;
6
+ onconfirm?: () => Promise<void> | void;
7
+ oncancel?: () => void;
8
+ };
9
+ /**
10
+ * CrudDeleteConfirm
11
+ * CRUD 删除确认对话框
12
+ *
13
+ * 使用 Svelte 5 Runes ($props)
14
+ * 使用 compounds 组件:Confirm
15
+ */
16
+ declare const CrudDeleteConfirm: import("svelte").Component<$$ComponentProps, {}, "open">;
17
+ type CrudDeleteConfirm = ReturnType<typeof CrudDeleteConfirm>;
18
+ export default CrudDeleteConfirm;
@@ -0,0 +1,155 @@
1
+ <!--
2
+ @component CrudDetailDrawer
3
+ CRUD 详情抽屉组件(只读展示)
4
+
5
+ 使用 Svelte 5 Runes ($props, $derived)
6
+ 使用 compounds 组件:Drawer, Badge
7
+ -->
8
+ <script lang='ts'>
9
+ import type { Snippet } from 'svelte'
10
+ import type { Size } from '../../../types.js'
11
+ import { uiM } from '../../../messages.js'
12
+ import Drawer from '../../compounds/Drawer.svelte'
13
+ import Badge from '../../primitives/Badge.svelte'
14
+ import Button from '../../primitives/Button.svelte'
15
+
16
+ type FieldDef = {
17
+ id: string
18
+ label: string | (() => string)
19
+ type: string
20
+ inDetail?: boolean
21
+ options?: Array<{ label: string, value: string | number | boolean }> | (() => Array<{ label: string, value: string | number | boolean }>)
22
+ render?: (value: unknown, item: Record<string, unknown>) => string
23
+ order?: number
24
+ }
25
+
26
+ let {
27
+ open = $bindable(false),
28
+ item = null,
29
+ fields = [],
30
+ title = '',
31
+ size = '2xl' as Size,
32
+ canEdit = false,
33
+ onedit,
34
+ onclose,
35
+ detailExtra,
36
+ }: {
37
+ open?: boolean
38
+ item?: Record<string, unknown> | null
39
+ fields?: FieldDef[]
40
+ title?: string
41
+ size?: Size
42
+ canEdit?: boolean
43
+ onedit?: () => void
44
+ onclose?: () => void
45
+ detailExtra?: Snippet<[Record<string, unknown>]>
46
+ } = $props()
47
+
48
+ function resolveText(text: string | (() => string)): string {
49
+ return typeof text === 'function' ? text() : text
50
+ }
51
+
52
+ function resolveOptions(options?: FieldDef['options']): Array<{ label: string, value: string | number | boolean }> {
53
+ if (!options)
54
+ return []
55
+ return typeof options === 'function' ? options() : options
56
+ }
57
+
58
+ function formatValue(field: FieldDef, value: unknown): string {
59
+ if (value === null || value === undefined)
60
+ return uiM('crud_detail_empty')
61
+
62
+ if (field.render && item) {
63
+ return field.render(value, item)
64
+ }
65
+
66
+ // 选项类型:将 value 映射为 label
67
+ if (field.type === 'select' || field.type === 'radio') {
68
+ const opts = resolveOptions(field.options)
69
+ const found = opts.find(o => String(o.value) === String(value))
70
+ return found?.label ?? String(value)
71
+ }
72
+
73
+ // 多选类型
74
+ if (field.type === 'multi-select' && Array.isArray(value)) {
75
+ const opts = resolveOptions(field.options)
76
+ return value.map((v) => {
77
+ const found = opts.find(o => String(o.value) === String(v))
78
+ return found?.label ?? String(v)
79
+ }).join(', ')
80
+ }
81
+
82
+ // 布尔
83
+ if (field.type === 'boolean' || field.type === 'checkbox') {
84
+ return value ? '✓' : '✗'
85
+ }
86
+
87
+ // 日期/时间
88
+ if (field.type === 'date' && (typeof value === 'string' || typeof value === 'number' || value instanceof Date)) {
89
+ return new Date(value as string | number).toLocaleDateString()
90
+ }
91
+ if (field.type === 'datetime' && (typeof value === 'string' || typeof value === 'number' || value instanceof Date)) {
92
+ return new Date(value as string | number).toLocaleString()
93
+ }
94
+
95
+ return String(value)
96
+ }
97
+
98
+ const detailFields = $derived(
99
+ [...fields]
100
+ .filter(f => f.inDetail !== false)
101
+ .sort((a, b) => (a.order ?? 0) - (b.order ?? 0)),
102
+ )
103
+
104
+ function handleClose() {
105
+ open = false
106
+ onclose?.()
107
+ }
108
+ </script>
109
+
110
+ <Drawer bind:open {title} position='right' {size} onclose={handleClose}>
111
+ {#if item}
112
+ <div class='space-y-4 pb-20'>
113
+ {#each detailFields as field}
114
+ {@const value = item[field.id]}
115
+ <div>
116
+ <div class='text-xs font-medium text-base-content/50 mb-1'>
117
+ {resolveText(field.label)}
118
+ </div>
119
+ <div class='text-sm text-base-content'>
120
+ {#if field.type === 'multi-select' && Array.isArray(value) && value.length > 0}
121
+ <div class='flex flex-wrap gap-1'>
122
+ {#each value as v}
123
+ {@const opts = resolveOptions(field.options)}
124
+ {@const found = opts.find(o => String(o.value) === String(v))}
125
+ <Badge variant='ghost' size='sm'>{found?.label ?? String(v)}</Badge>
126
+ {/each}
127
+ </div>
128
+ {:else}
129
+ {formatValue(field, value)}
130
+ {/if}
131
+ </div>
132
+ </div>
133
+ {/each}
134
+
135
+ {#if detailExtra && item}
136
+ <div class='border-t border-base-content/5 pt-4'>
137
+ {@render detailExtra(item)}
138
+ </div>
139
+ {/if}
140
+ </div>
141
+
142
+ <!-- 底部操作栏 -->
143
+ <div class='absolute bottom-0 left-0 right-0 p-4 bg-base-200 border-t border-base-content/10 flex justify-end gap-2'>
144
+ {#if canEdit}
145
+ <Button variant='primary' onclick={onedit}>
146
+ <span class='icon-[tabler--edit] size-4 mr-1'></span>
147
+ {uiM('crud_edit')}
148
+ </Button>
149
+ {/if}
150
+ <Button variant='ghost' onclick={handleClose}>
151
+ {uiM('crud_close')}
152
+ </Button>
153
+ </div>
154
+ {/if}
155
+ </Drawer>
@@ -0,0 +1,38 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { Size } from '../../../types.js';
3
+ type FieldDef = {
4
+ id: string;
5
+ label: string | (() => string);
6
+ type: string;
7
+ inDetail?: boolean;
8
+ options?: Array<{
9
+ label: string;
10
+ value: string | number | boolean;
11
+ }> | (() => Array<{
12
+ label: string;
13
+ value: string | number | boolean;
14
+ }>);
15
+ render?: (value: unknown, item: Record<string, unknown>) => string;
16
+ order?: number;
17
+ };
18
+ type $$ComponentProps = {
19
+ open?: boolean;
20
+ item?: Record<string, unknown> | null;
21
+ fields?: FieldDef[];
22
+ title?: string;
23
+ size?: Size;
24
+ canEdit?: boolean;
25
+ onedit?: () => void;
26
+ onclose?: () => void;
27
+ detailExtra?: Snippet<[Record<string, unknown>]>;
28
+ };
29
+ /**
30
+ * CrudDetailDrawer
31
+ * CRUD 详情抽屉组件(只读展示)
32
+ *
33
+ * 使用 Svelte 5 Runes ($props, $derived)
34
+ * 使用 compounds 组件:Drawer, Badge
35
+ */
36
+ declare const CrudDetailDrawer: import("svelte").Component<$$ComponentProps, {}, "open">;
37
+ type CrudDetailDrawer = ReturnType<typeof CrudDetailDrawer>;
38
+ export default CrudDetailDrawer;