@h-ai/ui 0.1.0-alpha5

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 (234) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +488 -0
  3. package/auto-import.d.ts +96 -0
  4. package/auto-import.js +259 -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 +102 -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 +2259 -0
  130. package/dist/components/scenes/ai/AiDocumentEditor.svelte.d.ts +4 -0
  131. package/dist/components/scenes/ai/MarkdownRenderer.svelte +460 -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/index.d.ts +17 -0
  140. package/dist/components/scenes/ai/index.js +13 -0
  141. package/dist/components/scenes/ai/markdown-parse.d.ts +32 -0
  142. package/dist/components/scenes/ai/markdown-parse.js +161 -0
  143. package/dist/components/scenes/app/FeedbackModal.svelte +160 -0
  144. package/dist/components/scenes/app/FeedbackModal.svelte.d.ts +47 -0
  145. package/dist/components/scenes/app/LanguageSwitch.svelte +136 -0
  146. package/dist/components/scenes/app/LanguageSwitch.svelte.d.ts +50 -0
  147. package/dist/components/scenes/app/SettingsModal.svelte +174 -0
  148. package/dist/components/scenes/app/SettingsModal.svelte.d.ts +31 -0
  149. package/dist/components/scenes/app/ThemeSelector.svelte +176 -0
  150. package/dist/components/scenes/app/ThemeSelector.svelte.d.ts +31 -0
  151. package/dist/components/scenes/app/ThemeToggle.svelte +76 -0
  152. package/dist/components/scenes/app/ThemeToggle.svelte.d.ts +29 -0
  153. package/dist/components/scenes/app/index.d.ts +11 -0
  154. package/dist/components/scenes/app/index.js +11 -0
  155. package/dist/components/scenes/crud/CrudDeleteConfirm.svelte +39 -0
  156. package/dist/components/scenes/crud/CrudDeleteConfirm.svelte.d.ts +18 -0
  157. package/dist/components/scenes/crud/CrudDetailDrawer.svelte +155 -0
  158. package/dist/components/scenes/crud/CrudDetailDrawer.svelte.d.ts +38 -0
  159. package/dist/components/scenes/crud/CrudEditDrawer.svelte +222 -0
  160. package/dist/components/scenes/crud/CrudEditDrawer.svelte.d.ts +48 -0
  161. package/dist/components/scenes/crud/CrudFilterBar.svelte +96 -0
  162. package/dist/components/scenes/crud/CrudFilterBar.svelte.d.ts +31 -0
  163. package/dist/components/scenes/crud/CrudPage.svelte +499 -0
  164. package/dist/components/scenes/crud/CrudPage.svelte.d.ts +114 -0
  165. package/dist/components/scenes/crud/crud-types.d.ts +135 -0
  166. package/dist/components/scenes/crud/crud-types.js +7 -0
  167. package/dist/components/scenes/crud/index.d.ts +12 -0
  168. package/dist/components/scenes/crud/index.js +11 -0
  169. package/dist/components/scenes/crypto/EncryptedInput.svelte +156 -0
  170. package/dist/components/scenes/crypto/EncryptedInput.svelte.d.ts +32 -0
  171. package/dist/components/scenes/crypto/HashDisplay.svelte +97 -0
  172. package/dist/components/scenes/crypto/HashDisplay.svelte.d.ts +4 -0
  173. package/dist/components/scenes/crypto/SignatureDisplay.svelte +161 -0
  174. package/dist/components/scenes/crypto/SignatureDisplay.svelte.d.ts +4 -0
  175. package/dist/components/scenes/crypto/index.d.ts +9 -0
  176. package/dist/components/scenes/crypto/index.js +11 -0
  177. package/dist/components/scenes/iam/ChangePasswordForm.svelte +134 -0
  178. package/dist/components/scenes/iam/ChangePasswordForm.svelte.d.ts +8 -0
  179. package/dist/components/scenes/iam/ForgotPasswordForm.svelte +147 -0
  180. package/dist/components/scenes/iam/ForgotPasswordForm.svelte.d.ts +4 -0
  181. package/dist/components/scenes/iam/LoginForm.svelte +191 -0
  182. package/dist/components/scenes/iam/LoginForm.svelte.d.ts +8 -0
  183. package/dist/components/scenes/iam/PasswordInput.svelte +203 -0
  184. package/dist/components/scenes/iam/PasswordInput.svelte.d.ts +49 -0
  185. package/dist/components/scenes/iam/PermGuard.svelte +69 -0
  186. package/dist/components/scenes/iam/PermGuard.svelte.d.ts +16 -0
  187. package/dist/components/scenes/iam/RegisterForm.svelte +269 -0
  188. package/dist/components/scenes/iam/RegisterForm.svelte.d.ts +8 -0
  189. package/dist/components/scenes/iam/ResetPasswordForm.svelte +153 -0
  190. package/dist/components/scenes/iam/ResetPasswordForm.svelte.d.ts +8 -0
  191. package/dist/components/scenes/iam/UserProfile.svelte +251 -0
  192. package/dist/components/scenes/iam/UserProfile.svelte.d.ts +4 -0
  193. package/dist/components/scenes/iam/index.d.ts +16 -0
  194. package/dist/components/scenes/iam/index.js +18 -0
  195. package/dist/components/scenes/iam/password-utils.d.ts +15 -0
  196. package/dist/components/scenes/iam/password-utils.js +19 -0
  197. package/dist/components/scenes/iam/permission-context.svelte.d.ts +90 -0
  198. package/dist/components/scenes/iam/permission-context.svelte.js +114 -0
  199. package/dist/components/scenes/index.d.ts +13 -0
  200. package/dist/components/scenes/index.js +20 -0
  201. package/dist/components/scenes/storage/AvatarUpload.svelte +218 -0
  202. package/dist/components/scenes/storage/AvatarUpload.svelte.d.ts +4 -0
  203. package/dist/components/scenes/storage/FileList.svelte +260 -0
  204. package/dist/components/scenes/storage/FileList.svelte.d.ts +4 -0
  205. package/dist/components/scenes/storage/FileUpload.svelte +400 -0
  206. package/dist/components/scenes/storage/FileUpload.svelte.d.ts +7 -0
  207. package/dist/components/scenes/storage/ImageUpload.svelte +285 -0
  208. package/dist/components/scenes/storage/ImageUpload.svelte.d.ts +4 -0
  209. package/dist/components/scenes/storage/index.d.ts +10 -0
  210. package/dist/components/scenes/storage/index.js +12 -0
  211. package/dist/components/scenes/types.d.ts +583 -0
  212. package/dist/components/scenes/types.js +7 -0
  213. package/dist/i18n.svelte.d.ts +44 -0
  214. package/dist/i18n.svelte.js +108 -0
  215. package/dist/index.d.ts +13 -0
  216. package/dist/index.js +19 -0
  217. package/dist/messages/en-US.json +242 -0
  218. package/dist/messages/zh-CN.json +242 -0
  219. package/dist/messages.d.ts +29 -0
  220. package/dist/messages.js +42 -0
  221. package/dist/styles/design-tokens.css +58 -0
  222. package/dist/styles/global.css +218 -0
  223. package/dist/styles/mobile.css +105 -0
  224. package/dist/theme-config.d.ts +92 -0
  225. package/dist/theme-config.js +122 -0
  226. package/dist/toast.svelte.d.ts +45 -0
  227. package/dist/toast.svelte.js +77 -0
  228. package/dist/types.d.ts +944 -0
  229. package/dist/types.js +7 -0
  230. package/dist/utils/platform.svelte.d.ts +54 -0
  231. package/dist/utils/platform.svelte.js +87 -0
  232. package/dist/utils.d.ts +46 -0
  233. package/dist/utils.js +100 -0
  234. package/package.json +69 -0
@@ -0,0 +1,327 @@
1
+ <!--
2
+ @component Combobox
3
+ 可搜索下拉选择组件,基于 Bits UI headless + DaisyUI 样式。
4
+ 支持单选和多选模式,带搜索过滤、键盘导航、无障碍支持。
5
+ 多选模式下以标签形式展示已选项。
6
+
7
+ @prop {Option[]} options - 选项列表
8
+ @prop {string | string[]} value - 选中值(双向绑定,单选为 string,多选为 string[])
9
+ @prop {boolean} multiple - 是否多选模式
10
+ @prop {string} placeholder - 输入框占位符
11
+ @prop {boolean} disabled - 是否禁用
12
+ @prop {string} error - 错误消息
13
+ @prop {string} label - 表单标签
14
+ @prop {string} class - 自定义类名
15
+ @prop {function} onchange - 值变更回调
16
+
17
+ @example 单选
18
+ <Combobox
19
+ options={[
20
+ { value: 'svelte', label: 'Svelte' },
21
+ { value: 'react', label: 'React' },
22
+ ]}
23
+ bind:value={framework}
24
+ placeholder="搜索框架..."
25
+ />
26
+
27
+ @example 多选
28
+ <Combobox
29
+ options={skills}
30
+ bind:value={selectedSkills}
31
+ multiple
32
+ placeholder="搜索技能..."
33
+ />
34
+ -->
35
+ <script lang='ts'>
36
+ import { Combobox } from 'bits-ui'
37
+ import { uiM } from '../../messages.js'
38
+ import BareButton from '../primitives/BareButton.svelte'
39
+
40
+ /** 选项定义 */
41
+ interface Option {
42
+ /** 选项值 */
43
+ value: string
44
+ /** 显示标签 */
45
+ label: string
46
+ /** 是否禁用 */
47
+ disabled?: boolean
48
+ /** 描述文本(仅在多选模式下展示) */
49
+ description?: string
50
+ }
51
+
52
+ interface Props {
53
+ /** 选项列表 */
54
+ options: Option[]
55
+ /** 选中值(双向绑定,单选为 string,多选为 string[]) */
56
+ value?: string | string[]
57
+ /** 是否多选模式 */
58
+ multiple?: boolean
59
+ /** 输入框占位符 */
60
+ placeholder?: string
61
+ /** 是否禁用 */
62
+ disabled?: boolean
63
+ /** 错误消息 */
64
+ error?: string
65
+ /** 表单标签 */
66
+ label?: string
67
+ /** 自定义类名 */
68
+ class?: string
69
+ /** 值变更回调 */
70
+ onchange?: (value: string | string[]) => void
71
+ }
72
+
73
+ let {
74
+ options,
75
+ value = $bindable(),
76
+ multiple = false,
77
+ placeholder = '',
78
+ disabled = false,
79
+ error,
80
+ label: fieldLabel,
81
+ class: className = '',
82
+ onchange,
83
+ }: Props = $props()
84
+
85
+ // 为未提供 value 时设置初始默认值(仅在初始化时执行一次)
86
+ // 统一以空字符串初始化,避免在初始化阶段捕获 multiple 的初始值。
87
+ // 多选模式会通过 multiVal 派生为 [] 传给 Combobox.Root。
88
+ if (value === undefined) {
89
+ value = ''
90
+ }
91
+
92
+ let searchValue = $state('')
93
+ let open = $state(false)
94
+
95
+ /** 根据搜索关键词过滤选项 */
96
+ const filteredOptions = $derived(
97
+ searchValue === ''
98
+ ? options
99
+ : options.filter((opt) => {
100
+ const keyword = searchValue.toLowerCase()
101
+ const inLabel = opt.label.toLowerCase().includes(keyword)
102
+ const inDescription = opt.description?.toLowerCase().includes(keyword) ?? false
103
+ return inLabel || inDescription
104
+ }),
105
+ )
106
+
107
+ /** 单选模式:输入框显示值(打开时显示搜索词,关闭时显示选中项标签) */
108
+ const singleInputValue = $derived.by(() => {
109
+ if (open)
110
+ return searchValue
111
+ return options.find(o => o.value === value)?.label ?? ''
112
+ })
113
+
114
+ /** 多选模式:输入框始终显示搜索词 */
115
+ const multiInputValue = $derived(searchValue)
116
+
117
+ /** 多选模式下已选中的选项列表 */
118
+ const selectedOptions = $derived(
119
+ Array.isArray(value)
120
+ ? options.filter(opt => (value as string[]).includes(opt.value))
121
+ : [],
122
+ )
123
+
124
+ /** 多选模式:提供给 Combobox.Root 的类型安全值 */
125
+ const multiVal = $derived(Array.isArray(value) ? value as string[] : [])
126
+
127
+ /** 单选模式:提供给 Combobox.Root 的类型安全值 */
128
+ const singleVal = $derived(typeof value === 'string' ? value : '')
129
+
130
+ function handleInput(e: Event & { currentTarget: HTMLInputElement }) {
131
+ searchValue = e.currentTarget.value
132
+ if (!open)
133
+ open = true
134
+ }
135
+
136
+ function handleOpenChange(newOpen: boolean) {
137
+ if (!newOpen)
138
+ searchValue = ''
139
+ }
140
+
141
+ function handleMultiValueChange(v: string[]) {
142
+ value = v
143
+ onchange?.(v)
144
+ }
145
+
146
+ function handleSingleValueChange(v: string) {
147
+ value = v
148
+ onchange?.(v)
149
+ }
150
+
151
+ /** 多选模式下移除某个已选项 */
152
+ function removeItem(val: string, event: MouseEvent) {
153
+ event.stopPropagation()
154
+ if (!Array.isArray(value))
155
+ return
156
+ const newValue = (value as string[]).filter(v => v !== val)
157
+ value = newValue
158
+ onchange?.(newValue)
159
+ }
160
+ </script>
161
+
162
+ <div class='fieldset w-full {className}'>
163
+ {#if fieldLabel}
164
+ <legend class='fieldset-legend font-medium'>{fieldLabel}</legend>
165
+ {/if}
166
+
167
+ {#if multiple}
168
+ <!-- 多选模式 -->
169
+ <Combobox.Root
170
+ type='multiple'
171
+ items={filteredOptions}
172
+ inputValue={multiInputValue}
173
+ value={multiVal}
174
+ bind:open
175
+ {disabled}
176
+ onOpenChange={handleOpenChange}
177
+ onValueChange={handleMultiValueChange}
178
+ >
179
+ <div class='relative'>
180
+ <div
181
+ class="min-h-10 px-2 py-1.5 pr-8 border rounded-lg bg-base-100 flex flex-wrap gap-1.5 items-center transition-colors
182
+ {disabled ? 'opacity-50 cursor-not-allowed bg-base-200' : 'cursor-text'}
183
+ {error ? 'border-error focus-within:border-error' : 'border-base-content/20 focus-within:border-primary focus-within:ring-1 focus-within:ring-primary/20'}"
184
+ >
185
+ {#each selectedOptions as opt (opt.value)}
186
+ <span class='inline-flex items-center gap-1 px-2 py-0.5 bg-primary/10 text-primary text-sm rounded-md'>
187
+ <span class='truncate max-w-32'>{opt.label}</span>
188
+ {#if !disabled}
189
+ <BareButton
190
+ type='button'
191
+ class='hover:bg-primary/20 rounded p-0.5 transition-colors'
192
+ onclick={e => removeItem(opt.value, e)}
193
+ ariaLabel="{uiM('combobox_remove')} {opt.label}"
194
+ >
195
+ <span class='icon-[tabler--x] size-3'></span>
196
+ </BareButton>
197
+ {/if}
198
+ </span>
199
+ {/each}
200
+
201
+ <Combobox.Input
202
+ class='flex-1 min-w-20 bg-transparent outline-none text-sm py-0.5'
203
+ placeholder={selectedOptions.length === 0 ? placeholder : ''}
204
+ {disabled}
205
+ oninput={handleInput}
206
+ onfocus={() => {
207
+ if (!disabled)
208
+ open = true
209
+ }}
210
+ />
211
+ </div>
212
+
213
+ <Combobox.Trigger
214
+ class='absolute right-2 top-1/2 -translate-y-1/2 btn btn-ghost btn-xs btn-circle'
215
+ >
216
+ <span class="icon-[tabler--chevron-down] size-4 transition-transform {open ? 'rotate-180' : ''}"></span>
217
+ </Combobox.Trigger>
218
+ </div>
219
+
220
+ <Combobox.Portal>
221
+ <Combobox.Content
222
+ class='z-50 mt-1 max-h-60 w-[var(--bits-combobox-anchor-width)] overflow-y-auto rounded-lg border border-base-content/10 bg-base-100 p-1 shadow-lg'
223
+ sideOffset={4}
224
+ >
225
+ {#if filteredOptions.length === 0}
226
+ <div class='px-3 py-2 text-sm text-base-content/50'>
227
+ {uiM('combobox_no_match')}
228
+ </div>
229
+ {:else}
230
+ {#each filteredOptions as opt (opt.value)}
231
+ <Combobox.Item
232
+ value={opt.value}
233
+ label={opt.label}
234
+ disabled={opt.disabled}
235
+ class='flex cursor-pointer items-center gap-2 rounded-md px-3 py-2 text-sm transition-colors hover:bg-base-200 data-[highlighted]:bg-base-200 data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50'
236
+ >
237
+ {#snippet children({ selected })}
238
+ <span class="size-4 shrink-0 rounded border border-base-content/30 flex items-center justify-center {selected ? 'bg-primary border-primary' : ''}">
239
+ {#if selected}
240
+ <span class='icon-[tabler--check] size-3 text-primary-content'></span>
241
+ {/if}
242
+ </span>
243
+ <div class='flex-1 min-w-0'>
244
+ <span>{opt.label}</span>
245
+ {#if opt.description}
246
+ <div class='text-xs text-base-content/50 truncate'>{opt.description}</div>
247
+ {/if}
248
+ </div>
249
+ {/snippet}
250
+ </Combobox.Item>
251
+ {/each}
252
+ {/if}
253
+ </Combobox.Content>
254
+ </Combobox.Portal>
255
+ </Combobox.Root>
256
+ {:else}
257
+ <!-- 单选模式 -->
258
+ <Combobox.Root
259
+ type='single'
260
+ items={filteredOptions}
261
+ inputValue={singleInputValue}
262
+ value={singleVal}
263
+ bind:open
264
+ {disabled}
265
+ onOpenChange={handleOpenChange}
266
+ onValueChange={handleSingleValueChange}
267
+ >
268
+ <div class='relative'>
269
+ <Combobox.Input
270
+ class="input w-full pr-8 {error ? 'input-error' : ''}"
271
+ {placeholder}
272
+ {disabled}
273
+ oninput={handleInput}
274
+ onfocus={() => {
275
+ if (!disabled)
276
+ open = true
277
+ }}
278
+ />
279
+ <Combobox.Trigger
280
+ class='absolute right-2 top-1/2 -translate-y-1/2 btn btn-ghost btn-xs btn-circle'
281
+ >
282
+ <span class="icon-[tabler--chevron-down] size-4 transition-transform {open ? 'rotate-180' : ''}"></span>
283
+ </Combobox.Trigger>
284
+ </div>
285
+
286
+ <Combobox.Portal>
287
+ <Combobox.Content
288
+ class='z-50 mt-1 max-h-60 w-[var(--bits-combobox-anchor-width)] overflow-y-auto rounded-lg border border-base-content/10 bg-base-100 p-1 shadow-lg'
289
+ sideOffset={4}
290
+ >
291
+ {#if filteredOptions.length === 0}
292
+ <div class='px-3 py-2 text-sm text-base-content/50'>
293
+ {uiM('combobox_no_match')}
294
+ </div>
295
+ {:else}
296
+ {#each filteredOptions as opt (opt.value)}
297
+ <Combobox.Item
298
+ value={opt.value}
299
+ label={opt.label}
300
+ disabled={opt.disabled}
301
+ class='flex cursor-pointer items-center gap-2 rounded-md px-3 py-2 text-sm transition-colors hover:bg-base-200 data-[highlighted]:bg-base-200 data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50'
302
+ >
303
+ {#snippet children({ selected })}
304
+ <span class='size-4 shrink-0'>
305
+ {#if selected}
306
+ <span class='icon-[tabler--check] size-4 text-primary'></span>
307
+ {/if}
308
+ </span>
309
+ <div class='flex-1 min-w-0'>
310
+ <span>{opt.label}</span>
311
+ {#if opt.description}
312
+ <div class='text-xs text-base-content/50 truncate'>{opt.description}</div>
313
+ {/if}
314
+ </div>
315
+ {/snippet}
316
+ </Combobox.Item>
317
+ {/each}
318
+ {/if}
319
+ </Combobox.Content>
320
+ </Combobox.Portal>
321
+ </Combobox.Root>
322
+ {/if}
323
+
324
+ {#if error}
325
+ <span class='fieldset-label text-error'>{error}</span>
326
+ {/if}
327
+ </div>
@@ -0,0 +1,69 @@
1
+ import { Combobox } from 'bits-ui';
2
+ /** 选项定义 */
3
+ interface Option {
4
+ /** 选项值 */
5
+ value: string;
6
+ /** 显示标签 */
7
+ label: string;
8
+ /** 是否禁用 */
9
+ disabled?: boolean;
10
+ /** 描述文本(仅在多选模式下展示) */
11
+ description?: string;
12
+ }
13
+ interface Props {
14
+ /** 选项列表 */
15
+ options: Option[];
16
+ /** 选中值(双向绑定,单选为 string,多选为 string[]) */
17
+ value?: string | string[];
18
+ /** 是否多选模式 */
19
+ multiple?: boolean;
20
+ /** 输入框占位符 */
21
+ placeholder?: string;
22
+ /** 是否禁用 */
23
+ disabled?: boolean;
24
+ /** 错误消息 */
25
+ error?: string;
26
+ /** 表单标签 */
27
+ label?: string;
28
+ /** 自定义类名 */
29
+ class?: string;
30
+ /** 值变更回调 */
31
+ onchange?: (value: string | string[]) => void;
32
+ }
33
+ /**
34
+ * Combobox
35
+ * 可搜索下拉选择组件,基于 Bits UI headless + DaisyUI 样式。
36
+ * 支持单选和多选模式,带搜索过滤、键盘导航、无障碍支持。
37
+ * 多选模式下以标签形式展示已选项。
38
+ *
39
+ * @prop {Option[]} options - 选项列表
40
+ * @prop {string | string[]} value - 选中值(双向绑定,单选为 string,多选为 string[])
41
+ * @prop {boolean} multiple - 是否多选模式
42
+ * @prop {string} placeholder - 输入框占位符
43
+ * @prop {boolean} disabled - 是否禁用
44
+ * @prop {string} error - 错误消息
45
+ * @prop {string} label - 表单标签
46
+ * @prop {string} class - 自定义类名
47
+ * @prop {function} onchange - 值变更回调
48
+ *
49
+ * @example 单选
50
+ * <Combobox
51
+ * options={[
52
+ * { value: 'svelte', label: 'Svelte' },
53
+ * { value: 'react', label: 'React' },
54
+ * ]}
55
+ * bind:value={framework}
56
+ * placeholder="搜索框架..."
57
+ * />
58
+ *
59
+ * @example 多选
60
+ * <Combobox
61
+ * options={skills}
62
+ * bind:value={selectedSkills}
63
+ * multiple
64
+ * placeholder="搜索技能..."
65
+ * />
66
+ */
67
+ declare const Combobox: import("svelte").Component<Props, {}, "value">;
68
+ type Combobox = ReturnType<typeof Combobox>;
69
+ export default Combobox;
@@ -0,0 +1,90 @@
1
+ <!--
2
+ =============================================================================
3
+ @h-ai/ui - Confirm 组件
4
+ =============================================================================
5
+ 确认对话框组件
6
+
7
+ 使用 Svelte 5 Runes ($props, $state)
8
+ 使用 primitives 组件:Button
9
+ =============================================================================
10
+ -->
11
+ <script lang='ts'>
12
+ import type { ConfirmProps } from '../../types.js'
13
+ import { uiM } from '../../messages.js'
14
+ import { cn } from '../../utils.js'
15
+ import Button from '../primitives/Button.svelte'
16
+
17
+ let {
18
+ open = $bindable(false),
19
+ title,
20
+ message,
21
+ confirmText,
22
+ cancelText,
23
+ variant = 'warning',
24
+ loading = false,
25
+ class: className = '',
26
+ onconfirm,
27
+ oncancel,
28
+ }: ConfirmProps = $props()
29
+
30
+ /** 响应式 i18n 显示值 */
31
+ const displayTitle = $derived(title ?? uiM('confirm_title'))
32
+ const displayMessage = $derived(message ?? uiM('confirm_message'))
33
+ const displayConfirmText = $derived(confirmText ?? uiM('confirm_ok'))
34
+ const displayCancelText = $derived(cancelText ?? uiM('confirm_cancel'))
35
+
36
+ let modalElement: HTMLDialogElement
37
+
38
+ $effect(() => {
39
+ if (open) {
40
+ modalElement?.showModal()
41
+ }
42
+ else {
43
+ modalElement?.close()
44
+ }
45
+ })
46
+
47
+ async function handleConfirm() {
48
+ await onconfirm?.()
49
+ open = false
50
+ }
51
+
52
+ function handleCancel() {
53
+ oncancel?.()
54
+ open = false
55
+ }
56
+
57
+ function handleBackdropClick(e: MouseEvent) {
58
+ if (e.target === modalElement) {
59
+ handleCancel()
60
+ }
61
+ }
62
+ </script>
63
+
64
+ <dialog
65
+ bind:this={modalElement}
66
+ class={cn('modal', className)}
67
+ onclick={handleBackdropClick}
68
+ >
69
+ <div class='modal-box'>
70
+ <h3 class='font-bold text-lg'>{displayTitle}</h3>
71
+ <p class='py-4'>{displayMessage}</p>
72
+ <div class='modal-action'>
73
+ <Button
74
+ variant='ghost'
75
+ onclick={handleCancel}
76
+ disabled={loading}
77
+ >
78
+ {displayCancelText}
79
+ </Button>
80
+ <Button
81
+ variant={variant}
82
+ onclick={handleConfirm}
83
+ {loading}
84
+ disabled={loading}
85
+ >
86
+ {displayConfirmText}
87
+ </Button>
88
+ </div>
89
+ </div>
90
+ </dialog>
@@ -0,0 +1,4 @@
1
+ import type { ConfirmProps } from '../../types.js';
2
+ declare const Confirm: import("svelte").Component<ConfirmProps, {}, "open">;
3
+ type Confirm = ReturnType<typeof Confirm>;
4
+ export default Confirm;
@@ -0,0 +1,133 @@
1
+ <!--
2
+ @component DataTable
3
+ 通用数据表格组件,支持自定义列、操作按钮和加载状态。
4
+
5
+ @prop {T[]} data - 表格数据
6
+ @prop {Column<T>[]} columns - 列配置
7
+ @prop {keyof T} keyField - 用于唯一标识行的字段
8
+ @prop {Snippet<[T]>} actions - 操作按钮插槽
9
+ @prop {Snippet} empty - 空状态插槽
10
+ @prop {boolean} loading - 是否加载中
11
+ @prop {boolean} striped - 是否斑马纹
12
+ @prop {boolean} hoverable - 是否悬停高亮
13
+
14
+ @example
15
+ <DataTable
16
+ data={users}
17
+ columns={[
18
+ { key: 'name', label: '姓名' },
19
+ { key: 'email', label: '邮箱' },
20
+ { key: 'createdAt', label: '创建时间', render: (item) => formatDate(item.createdAt) }
21
+ ]}
22
+ keyField="id"
23
+ >
24
+ {#snippet actions(item)}
25
+ <Button size="xs" onclick={() => edit(item)}>编辑</Button>
26
+ {/snippet}
27
+ </DataTable>
28
+ -->
29
+ <script lang='ts' generics='T'>
30
+ import type { Snippet } from 'svelte'
31
+ import { uiM } from '../../messages.js'
32
+
33
+ const {
34
+ data,
35
+ columns,
36
+ keyField,
37
+ actions,
38
+ empty,
39
+ loading = false,
40
+ striped = true,
41
+ hoverable = true,
42
+ class: className = '',
43
+ }: {
44
+ data: T[]
45
+ columns: { key: keyof T | string, label: string, width?: string, align?: 'left' | 'center' | 'right', render?: (item: T) => string }[]
46
+ keyField: keyof T
47
+ actions?: Snippet<[T]>
48
+ empty?: Snippet
49
+ loading?: boolean
50
+ striped?: boolean
51
+ hoverable?: boolean
52
+ class?: string
53
+ } = $props()
54
+
55
+ type Column = { key: keyof T | string, label: string, width?: string, align?: 'left' | 'center' | 'right', render?: (item: T) => string }
56
+
57
+ function getValue(item: T, col: Column): string {
58
+ if (col.render) {
59
+ return col.render(item)
60
+ }
61
+ const value = item[col.key as keyof T]
62
+ if (value === null || value === undefined) {
63
+ return '-'
64
+ }
65
+ // 自动格式化时间戳
66
+ if (typeof value === 'number' && col.key.toString().toLowerCase().includes('at')) {
67
+ return new Date(value).toLocaleString()
68
+ }
69
+ return String(value)
70
+ }
71
+
72
+ function getAlignClass(align?: string): string {
73
+ switch (align) {
74
+ case 'center': return 'text-center'
75
+ case 'right': return 'text-right'
76
+ default: return 'text-left'
77
+ }
78
+ }
79
+ </script>
80
+
81
+ <div class='overflow-x-auto {className}'>
82
+ <table class='table w-full text-[13px]' class:table-zebra={striped}>
83
+ <thead>
84
+ <tr class='text-xs text-base-content/50 border-b border-base-content/6'>
85
+ {#each columns as col}
86
+ <th
87
+ style={col.width ? `width: ${col.width}` : ''}
88
+ class='{getAlignClass(col.align)} font-medium'
89
+ >
90
+ {col.label}
91
+ </th>
92
+ {/each}
93
+ {#if actions}
94
+ <th class='w-24 text-center font-medium'>{uiM('data_table_actions')}</th>
95
+ {/if}
96
+ </tr>
97
+ </thead>
98
+ <tbody>
99
+ {#if loading}
100
+ <tr>
101
+ <td colspan={columns.length + (actions ? 1 : 0)} class='text-center py-8'>
102
+ <span class='loading loading-spinner loading-sm'></span>
103
+ </td>
104
+ </tr>
105
+ {:else if data.length === 0}
106
+ <tr>
107
+ <td colspan={columns.length + (actions ? 1 : 0)} class='text-center py-8 text-base-content/40'>
108
+ {#if empty}
109
+ {@render empty()}
110
+ {:else}
111
+ {uiM('data_table_empty')}
112
+ {/if}
113
+ </td>
114
+ </tr>
115
+ {:else}
116
+ {#each data as item (item[keyField])}
117
+ <tr class={hoverable ? 'hover:bg-base-content/3 transition-colors' : ''}>
118
+ {#each columns as col}
119
+ <td class={getAlignClass(col.align)}>{getValue(item, col)}</td>
120
+ {/each}
121
+ {#if actions}
122
+ <td>
123
+ <div class='flex items-center justify-center gap-1'>
124
+ {@render actions(item)}
125
+ </div>
126
+ </td>
127
+ {/if}
128
+ </tr>
129
+ {/each}
130
+ {/if}
131
+ </tbody>
132
+ </table>
133
+ </div>
@@ -0,0 +1,69 @@
1
+ import type { Snippet } from 'svelte';
2
+ declare function $$render<T>(): {
3
+ props: {
4
+ data: T[];
5
+ columns: {
6
+ key: keyof T | string;
7
+ label: string;
8
+ width?: string;
9
+ align?: "left" | "center" | "right";
10
+ render?: (item: T) => string;
11
+ }[];
12
+ keyField: keyof T;
13
+ actions?: Snippet<[T]>;
14
+ empty?: Snippet;
15
+ loading?: boolean;
16
+ striped?: boolean;
17
+ hoverable?: boolean;
18
+ class?: string;
19
+ };
20
+ exports: {};
21
+ bindings: "";
22
+ slots: {};
23
+ events: {};
24
+ };
25
+ declare class __sveltets_Render<T> {
26
+ props(): ReturnType<typeof $$render<T>>['props'];
27
+ events(): ReturnType<typeof $$render<T>>['events'];
28
+ slots(): ReturnType<typeof $$render<T>>['slots'];
29
+ bindings(): "";
30
+ exports(): {};
31
+ }
32
+ interface $$IsomorphicComponent {
33
+ new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
34
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
35
+ } & ReturnType<__sveltets_Render<T>['exports']>;
36
+ <T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
37
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
38
+ }
39
+ /**
40
+ * DataTable
41
+ * 通用数据表格组件,支持自定义列、操作按钮和加载状态。
42
+ *
43
+ * @prop {T[]} data - 表格数据
44
+ * @prop {Column<T>[]} columns - 列配置
45
+ * @prop {keyof T} keyField - 用于唯一标识行的字段
46
+ * @prop {Snippet<[T]>} actions - 操作按钮插槽
47
+ * @prop {Snippet} empty - 空状态插槽
48
+ * @prop {boolean} loading - 是否加载中
49
+ * @prop {boolean} striped - 是否斑马纹
50
+ * @prop {boolean} hoverable - 是否悬停高亮
51
+ *
52
+ * @example
53
+ * <DataTable
54
+ * data={users}
55
+ * columns={[
56
+ * { key: 'name', label: '姓名' },
57
+ * { key: 'email', label: '邮箱' },
58
+ * { key: 'createdAt', label: '创建时间', render: (item) => formatDate(item.createdAt) }
59
+ * ]}
60
+ * keyField="id"
61
+ * >
62
+ * {#snippet actions(item)}
63
+ * <Button size="xs" onclick={() => edit(item)}>编辑</Button>
64
+ * {/snippet}
65
+ * </DataTable>
66
+ */
67
+ declare const DataTable: $$IsomorphicComponent;
68
+ type DataTable<T> = InstanceType<typeof DataTable<T>>;
69
+ export default DataTable;