@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,49 @@
1
+ import type { Size } from '../../../types.js';
2
+ interface Props {
3
+ /** 元素 ID */
4
+ id?: string;
5
+ /** 值 */
6
+ value?: string;
7
+ /** 占位符 */
8
+ placeholder?: string;
9
+ /** 尺寸 */
10
+ size?: Size;
11
+ /** 是否禁用 */
12
+ disabled?: boolean;
13
+ /** 是否只读 */
14
+ readonly?: boolean;
15
+ /** 是否必填 */
16
+ required?: boolean;
17
+ /** 错误消息 */
18
+ error?: string;
19
+ /** 是否显示切换按钮 */
20
+ showToggle?: boolean;
21
+ /** 是否显示密码强度 */
22
+ showStrength?: boolean;
23
+ /** 最小长度 */
24
+ minLength?: number;
25
+ /** 自定义类名 */
26
+ class?: string;
27
+ /** 输入框引用 */
28
+ inputRef?: HTMLInputElement;
29
+ /** 输入事件 */
30
+ oninput?: (e: Event & {
31
+ currentTarget: HTMLInputElement;
32
+ }) => void;
33
+ /** 变化事件 */
34
+ onchange?: (e: Event & {
35
+ currentTarget: HTMLInputElement;
36
+ }) => void;
37
+ /** 无效事件 */
38
+ oninvalid?: (e: Event & {
39
+ currentTarget: HTMLInputElement;
40
+ }) => void;
41
+ }
42
+ /**
43
+ * PasswordInput
44
+ * 密码输入框组件,支持显示/隐藏切换和密码强度指示。
45
+ * 内置多语言支持,自动跟随全局 locale。
46
+ */
47
+ declare const PasswordInput: import("svelte").Component<Props, {}, "value" | "inputRef">;
48
+ type PasswordInput = ReturnType<typeof PasswordInput>;
49
+ export default PasswordInput;
@@ -0,0 +1,69 @@
1
+ <!--
2
+ @h-ai/ui — PermGuard 权限守卫组件
3
+
4
+ 根据权限上下文条件渲染子内容。
5
+ 当用户拥有指定权限时渲染 children,否则渲染 fallback(可选)。
6
+
7
+ @example
8
+ ```svelte
9
+ <PermGuard permission="user:create">
10
+ <button>新建用户</button>
11
+ </PermGuard>
12
+
13
+ <PermGuard permissions={['user:update', 'user:delete']} mode="any">
14
+ <button>操作</button>
15
+ {#snippet fallback()}
16
+ <span>无权限</span>
17
+ {/snippet}
18
+ </PermGuard>
19
+ ```
20
+ -->
21
+ <script lang='ts'>
22
+ import type { Snippet } from 'svelte'
23
+ import { usePermission } from './permission-context.svelte.js'
24
+
25
+ interface Props {
26
+ /** 单个权限码 */
27
+ permission?: string
28
+ /** 多个权限码 */
29
+ permissions?: string[]
30
+ /** 多权限匹配模式:any=任一即可,all=全部需要 */
31
+ mode?: 'any' | 'all'
32
+ /** 授权通过时渲染的内容 */
33
+ children: Snippet
34
+ /** 授权未通过时渲染的内容(可选) */
35
+ fallback?: Snippet
36
+ }
37
+
38
+ const {
39
+ permission,
40
+ permissions,
41
+ mode = 'any',
42
+ children,
43
+ fallback,
44
+ }: Props = $props()
45
+
46
+ const { hasPerm, hasAnyPerm, hasAllPerms } = usePermission()
47
+
48
+ /** 计算是否有权限 */
49
+ const authorized = $derived.by(() => {
50
+ // 单权限检查
51
+ if (permission) {
52
+ return hasPerm(permission)
53
+ }
54
+ // 多权限检查
55
+ if (permissions?.length) {
56
+ return mode === 'all'
57
+ ? hasAllPerms(...permissions)
58
+ : hasAnyPerm(...permissions)
59
+ }
60
+ // 未指定权限,默认通过
61
+ return true
62
+ })
63
+ </script>
64
+
65
+ {#if authorized}
66
+ {@render children()}
67
+ {:else if fallback}
68
+ {@render fallback()}
69
+ {/if}
@@ -0,0 +1,16 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ /** 单个权限码 */
4
+ permission?: string;
5
+ /** 多个权限码 */
6
+ permissions?: string[];
7
+ /** 多权限匹配模式:any=任一即可,all=全部需要 */
8
+ mode?: 'any' | 'all';
9
+ /** 授权通过时渲染的内容 */
10
+ children: Snippet;
11
+ /** 授权未通过时渲染的内容(可选) */
12
+ fallback?: Snippet;
13
+ }
14
+ declare const PermGuard: import("svelte").Component<Props, {}, "">;
15
+ type PermGuard = ReturnType<typeof PermGuard>;
16
+ export default PermGuard;
@@ -0,0 +1,269 @@
1
+ <!--
2
+ @component RegisterForm
3
+ 用户注册表单组件,搭配 primitives/compounds 使用。
4
+ -->
5
+ <script lang='ts'>
6
+ import type { InputProps } from '../../../types.js'
7
+ import type { RegisterField, RegisterFormData, RegisterFormProps } from '../types.js'
8
+ import { uiM } from '../../../messages.js'
9
+ import { cn } from '../../../utils.js'
10
+ import Alert from '../../compounds/Alert.svelte'
11
+ import Button from '../../primitives/Button.svelte'
12
+ import Input from '../../primitives/Input.svelte'
13
+ import PasswordInput from './PasswordInput.svelte'
14
+
15
+ const {
16
+ loading = false,
17
+ disabled = false,
18
+ showTitle = false,
19
+ showPasswordStrength = true,
20
+ requireConfirmPassword = true,
21
+ minPasswordLength = 8,
22
+ fields = ['username', 'email', 'password'],
23
+ showLoginLink = false,
24
+ loginUrl = '/login',
25
+ agreements,
26
+ submitText,
27
+ class: className = '',
28
+ errors = {},
29
+ onsubmit,
30
+ header,
31
+ footer,
32
+ }: RegisterFormProps = $props()
33
+
34
+ const hasAgreements = $derived(
35
+ !!(agreements?.userAgreementUrl || agreements?.privacyPolicyUrl),
36
+ )
37
+
38
+ // 表单数据
39
+ let username = $state('')
40
+ let email = $state('')
41
+ let phone = $state('')
42
+ let password = $state('')
43
+ let confirmPassword = $state('')
44
+ let nickname = $state('')
45
+
46
+ const formClass = $derived(
47
+ cn(
48
+ 'register-form space-y-4',
49
+ className,
50
+ ),
51
+ )
52
+
53
+ // 获取字段标签
54
+ function getFieldLabel(field: RegisterField): string {
55
+ const labelMap: Record<RegisterField, () => string> = {
56
+ username: () => uiM('register_username'),
57
+ email: () => uiM('register_email'),
58
+ phone: () => uiM('register_phone'),
59
+ password: () => uiM('register_password'),
60
+ confirmPassword: () => uiM('register_confirm_password'),
61
+ nickname: () => uiM('register_nickname'),
62
+ }
63
+ return labelMap[field]?.() || field
64
+ }
65
+
66
+ // 获取字段占位符
67
+ function getFieldPlaceholder(field: RegisterField): string {
68
+ const placeholderMap: Record<RegisterField, () => string> = {
69
+ username: () => uiM('register_username_placeholder'),
70
+ email: () => uiM('register_email_placeholder'),
71
+ phone: () => uiM('register_phone_placeholder'),
72
+ password: () => uiM('register_password_placeholder'),
73
+ confirmPassword: () => uiM('register_confirm_password_placeholder'),
74
+ nickname: () => uiM('register_nickname_placeholder'),
75
+ }
76
+ return placeholderMap[field]?.() || ''
77
+ }
78
+
79
+ // 获取字段类型
80
+ function getFieldType(field: RegisterField): InputProps['type'] {
81
+ const typeMap: Record<RegisterField, InputProps['type']> = {
82
+ username: 'text',
83
+ email: 'email',
84
+ phone: 'tel',
85
+ password: 'password',
86
+ confirmPassword: 'password',
87
+ nickname: 'text',
88
+ }
89
+ return typeMap[field] ?? 'text'
90
+ }
91
+
92
+ // 检查密码是否匹配
93
+ const passwordsMatch = $derived(
94
+ !requireConfirmPassword || password === confirmPassword,
95
+ )
96
+
97
+ // 确认密码错误提示
98
+ const confirmPasswordError = $derived(
99
+ errors.confirmPassword || (!passwordsMatch && confirmPassword.length > 0
100
+ ? uiM('register_password_mismatch')
101
+ : ''),
102
+ )
103
+
104
+ async function handleSubmit(e: SubmitEvent) {
105
+ e.preventDefault()
106
+ if (loading || disabled)
107
+ return
108
+ if (!passwordsMatch)
109
+ return
110
+
111
+ const data: RegisterFormData = {
112
+ username,
113
+ email,
114
+ phone,
115
+ password,
116
+ confirmPassword: requireConfirmPassword ? confirmPassword : undefined,
117
+ nickname,
118
+ }
119
+
120
+ await onsubmit?.(data)
121
+ }
122
+
123
+ // 获取字段值
124
+ function getFieldValue(field: RegisterField): string {
125
+ switch (field) {
126
+ case 'username': return username
127
+ case 'email': return email
128
+ case 'phone': return phone
129
+ case 'nickname': return nickname
130
+ default: return ''
131
+ }
132
+ }
133
+
134
+ // 设置字段值
135
+ function setFieldValue(field: RegisterField, value: string) {
136
+ switch (field) {
137
+ case 'username':
138
+ username = value
139
+ break
140
+ case 'email':
141
+ email = value
142
+ break
143
+ case 'phone':
144
+ phone = value
145
+ break
146
+ case 'nickname':
147
+ nickname = value
148
+ break
149
+ }
150
+ }
151
+ </script>
152
+
153
+ <form class={formClass} onsubmit={handleSubmit}>
154
+ <!-- 标题 -->
155
+ {#if showTitle}
156
+ <h2 class='text-xl font-semibold text-center mb-5'>{uiM('register_title')}</h2>
157
+ {/if}
158
+
159
+ <!-- 自定义头部 -->
160
+ {#if header}
161
+ <div class='register-form-header'>
162
+ {@render header()}
163
+ </div>
164
+ {/if}
165
+
166
+ <!-- 动态字段 -->
167
+ {#each fields as field (field)}
168
+ {#if field === 'password'}
169
+ <!-- 密码字段 -->
170
+ <div class='space-y-1.5'>
171
+ <label class='text-sm font-medium text-base-content/70' for='register-password'>
172
+ {getFieldLabel('password')}
173
+ </label>
174
+ <PasswordInput
175
+ id='register-password'
176
+ bind:value={password}
177
+ placeholder={getFieldPlaceholder('password')}
178
+ {disabled}
179
+ required
180
+ showStrength={showPasswordStrength}
181
+ minLength={minPasswordLength}
182
+ error={errors.password}
183
+ />
184
+ </div>
185
+
186
+ <!-- 确认密码字段 -->
187
+ {#if requireConfirmPassword}
188
+ <div class='space-y-1.5'>
189
+ <label class='text-sm font-medium text-base-content/70' for='register-confirm-password'>
190
+ {getFieldLabel('confirmPassword')}
191
+ </label>
192
+ <PasswordInput
193
+ id='register-confirm-password'
194
+ bind:value={confirmPassword}
195
+ placeholder={getFieldPlaceholder('confirmPassword')}
196
+ {disabled}
197
+ required
198
+ showStrength={false}
199
+ error={confirmPasswordError}
200
+ />
201
+ </div>
202
+ {/if}
203
+ {:else if field !== 'confirmPassword'}
204
+ <div class='space-y-1.5'>
205
+ <label class='text-sm font-medium text-base-content/70' for='register-{field}'>
206
+ {getFieldLabel(field)}
207
+ </label>
208
+ <Input
209
+ id={`register-${field}`}
210
+ type={getFieldType(field)}
211
+ name={field}
212
+ placeholder={getFieldPlaceholder(field)}
213
+ value={getFieldValue(field)}
214
+ oninput={(e: Event & { currentTarget: HTMLInputElement }) => setFieldValue(field, e.currentTarget.value)}
215
+ {disabled}
216
+ required={field === 'username' || field === 'email'}
217
+ autocomplete={field === 'username' ? 'username' : field === 'email' ? 'email' : undefined}
218
+ error={errors[field]}
219
+ />
220
+ </div>
221
+ {/if}
222
+ {/each}
223
+
224
+ <!-- 通用错误 -->
225
+ {#if errors.general}
226
+ <Alert variant='error'>
227
+ {errors.general}
228
+ </Alert>
229
+ {/if}
230
+
231
+ <!-- 提交按钮 -->
232
+ <Button
233
+ type='submit'
234
+ variant='primary'
235
+ class='w-full'
236
+ disabled={loading || disabled || !passwordsMatch}
237
+ loading={loading}
238
+ >
239
+ {submitText || uiM('register_submit')}
240
+ </Button>
241
+
242
+ <!-- 协议提示 -->
243
+ {#if hasAgreements}
244
+ <p class='text-xs text-base-content/50 text-center'>
245
+ {uiM('agreement_prefix')}
246
+ {#if agreements?.userAgreementUrl}
247
+ <a href={agreements.userAgreementUrl} target='_blank' rel='noopener noreferrer' class='link link-primary'>{uiM('agreement_user_agreement')}</a>
248
+ {/if}
249
+ {#if agreements?.userAgreementUrl && agreements?.privacyPolicyUrl}
250
+ {uiM('agreement_and')}
251
+ {/if}
252
+ {#if agreements?.privacyPolicyUrl}
253
+ <a href={agreements.privacyPolicyUrl} target='_blank' rel='noopener noreferrer' class='link link-primary'>{uiM('agreement_privacy_policy')}</a>
254
+ {/if}
255
+ </p>
256
+ {/if}
257
+
258
+ <!-- 自定义底部 -->
259
+ {#if footer}
260
+ <div class='register-form-footer'>
261
+ {@render footer()}
262
+ </div>
263
+ {:else if showLoginLink}
264
+ <div class='text-center mt-4 text-sm text-base-content/60'>
265
+ <span>{uiM('register_has_account')}</span>
266
+ <a href={loginUrl} class='link link-primary ml-1'>{uiM('register_login_now')}</a>
267
+ </div>
268
+ {/if}
269
+ </form>
@@ -0,0 +1,8 @@
1
+ import type { RegisterFormProps } from '../types.js';
2
+ /**
3
+ * RegisterForm
4
+ * 用户注册表单组件,搭配 primitives/compounds 使用。
5
+ */
6
+ declare const RegisterForm: import("svelte").Component<RegisterFormProps, {}, "">;
7
+ type RegisterForm = ReturnType<typeof RegisterForm>;
8
+ export default RegisterForm;
@@ -0,0 +1,153 @@
1
+ <!--
2
+ @component ResetPasswordForm
3
+ 重置密码表单组件(配合验证码/链接使用)。
4
+ -->
5
+ <script lang='ts'>
6
+ import type { ResetPasswordFormData, ResetPasswordFormProps } from '../types.js'
7
+ import { uiM } from '../../../messages.js'
8
+ import { cn } from '../../../utils.js'
9
+ import Alert from '../../compounds/Alert.svelte'
10
+ import Button from '../../primitives/Button.svelte'
11
+ import Input from '../../primitives/Input.svelte'
12
+ import { arePasswordsEqual } from './password-utils.js'
13
+ import PasswordInput from './PasswordInput.svelte'
14
+
15
+ const {
16
+ loading = false,
17
+ disabled = false,
18
+ showTitle = false,
19
+ showDescription = false,
20
+ showCode = true,
21
+ showPasswordStrength = true,
22
+ minPasswordLength = 8,
23
+ showBackLink = false,
24
+ loginUrl = '/login',
25
+ submitText,
26
+ class: className = '',
27
+ errors = {},
28
+ onsubmit,
29
+ }: ResetPasswordFormProps = $props()
30
+
31
+ let code = $state('')
32
+ let newPassword = $state('')
33
+ let confirmPassword = $state('')
34
+
35
+ const formClass = $derived(
36
+ cn(
37
+ 'reset-password-form space-y-4',
38
+ className,
39
+ ),
40
+ )
41
+
42
+ const passwordsMatch = $derived(arePasswordsEqual(newPassword, confirmPassword))
43
+ const canSubmit = $derived.by(() => {
44
+ if (showCode && !code)
45
+ return false
46
+ if (!newPassword || !confirmPassword)
47
+ return false
48
+ if (!passwordsMatch)
49
+ return false
50
+ return newPassword.length >= minPasswordLength
51
+ })
52
+
53
+ async function handleSubmit(e: SubmitEvent) {
54
+ e.preventDefault()
55
+ if (loading || disabled || !canSubmit)
56
+ return
57
+
58
+ const data: ResetPasswordFormData = {
59
+ code: showCode ? code : undefined,
60
+ newPassword,
61
+ confirmPassword,
62
+ }
63
+
64
+ await onsubmit?.(data)
65
+ }
66
+ </script>
67
+
68
+ <form class={formClass} onsubmit={handleSubmit}>
69
+ <!-- 标题 -->
70
+ {#if showTitle}
71
+ <h2 class='text-xl font-semibold text-center mb-4'>{uiM('reset_password_title')}</h2>
72
+ {/if}
73
+
74
+ <!-- 描述 -->
75
+ {#if showDescription}
76
+ <p class='text-center text-base-content/45 text-sm mb-5'>
77
+ {uiM('reset_password_desc')}
78
+ </p>
79
+ {/if}
80
+
81
+ <!-- 验证码 -->
82
+ {#if showCode}
83
+ <div class='space-y-1.5'>
84
+ <label class='text-sm font-medium text-base-content/70' for='reset-code'>
85
+ {uiM('reset_password_code_label')}
86
+ </label>
87
+ <Input
88
+ id='reset-code'
89
+ name='code'
90
+ type='text'
91
+ placeholder={uiM('reset_password_code_placeholder')}
92
+ bind:value={code}
93
+ {disabled}
94
+ required
95
+ error={errors.code}
96
+ />
97
+ </div>
98
+ {/if}
99
+
100
+ <!-- 新密码 -->
101
+ <div class='space-y-1.5'>
102
+ <label class='text-sm font-medium text-base-content/70' for='reset-new-password'>
103
+ {uiM('reset_password_new_label')}
104
+ </label>
105
+ <PasswordInput
106
+ bind:value={newPassword}
107
+ placeholder={uiM('reset_password_new_placeholder')}
108
+ {disabled}
109
+ error={errors.newPassword}
110
+ showStrength={showPasswordStrength}
111
+ minLength={minPasswordLength}
112
+ />
113
+ </div>
114
+
115
+ <!-- 确认新密码 -->
116
+ <div class='space-y-1.5'>
117
+ <label class='text-sm font-medium text-base-content/70' for='reset-confirm-password'>
118
+ {uiM('reset_password_confirm_label')}
119
+ </label>
120
+ <PasswordInput
121
+ bind:value={confirmPassword}
122
+ placeholder={uiM('reset_password_confirm_placeholder')}
123
+ {disabled}
124
+ error={errors.confirmPassword || (!passwordsMatch && confirmPassword ? uiM('reset_password_mismatch') : '')}
125
+ showStrength={false}
126
+ />
127
+ </div>
128
+
129
+ <!-- 通用错误 -->
130
+ {#if errors.general}
131
+ <Alert variant='error'>
132
+ {errors.general}
133
+ </Alert>
134
+ {/if}
135
+
136
+ <!-- 提交按钮 -->
137
+ <Button
138
+ type='submit'
139
+ variant='primary'
140
+ {loading}
141
+ disabled={loading || disabled || !canSubmit}
142
+ class='w-full'
143
+ >
144
+ {submitText || uiM('reset_password_submit')}
145
+ </Button>
146
+
147
+ <!-- 返回链接 -->
148
+ {#if showBackLink}
149
+ <div class='text-center mt-4 text-sm'>
150
+ <a href={loginUrl} class='link link-primary'>← {uiM('reset_password_back')}</a>
151
+ </div>
152
+ {/if}
153
+ </form>
@@ -0,0 +1,8 @@
1
+ import type { ResetPasswordFormProps } from '../types.js';
2
+ /**
3
+ * ResetPasswordForm
4
+ * 重置密码表单组件(配合验证码/链接使用)。
5
+ */
6
+ declare const ResetPasswordForm: import("svelte").Component<ResetPasswordFormProps, {}, "">;
7
+ type ResetPasswordForm = ReturnType<typeof ResetPasswordForm>;
8
+ export default ResetPasswordForm;