@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,134 @@
1
+ <!--
2
+ @component ChangePasswordForm
3
+ 修改密码表单组件。
4
+ -->
5
+ <script lang='ts'>
6
+ import type { ChangePasswordFormData, ChangePasswordFormProps } 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 { arePasswordsEqual } from './password-utils.js'
12
+ import PasswordInput from './PasswordInput.svelte'
13
+
14
+ const {
15
+ loading = false,
16
+ disabled = false,
17
+ requireOldPassword = true,
18
+ showPasswordStrength = true,
19
+ minPasswordLength = 8,
20
+ submitText,
21
+ class: className = '',
22
+ errors = {},
23
+ onsubmit,
24
+ }: ChangePasswordFormProps = $props()
25
+
26
+ let oldPassword = $state('')
27
+ let newPassword = $state('')
28
+ let confirmPassword = $state('')
29
+
30
+ const formClass = $derived(
31
+ cn(
32
+ 'change-password-form space-y-5',
33
+ className,
34
+ ),
35
+ )
36
+
37
+ const passwordsMatch = $derived(arePasswordsEqual(newPassword, confirmPassword))
38
+ const canSubmit = $derived.by(() => {
39
+ if (requireOldPassword && !oldPassword)
40
+ return false
41
+ if (!newPassword || !confirmPassword)
42
+ return false
43
+ if (!passwordsMatch)
44
+ return false
45
+ return newPassword.length >= minPasswordLength
46
+ })
47
+
48
+ /**
49
+ * 提交修改密码数据。
50
+ *
51
+ * @param e 表单提交事件
52
+ * @returns 无返回值
53
+ */
54
+ async function handleSubmit(e: SubmitEvent) {
55
+ e.preventDefault()
56
+ if (loading || disabled || !canSubmit)
57
+ return
58
+
59
+ const data: ChangePasswordFormData = {
60
+ oldPassword: requireOldPassword ? oldPassword : undefined,
61
+ newPassword,
62
+ confirmPassword,
63
+ }
64
+
65
+ await onsubmit?.(data)
66
+ }
67
+ </script>
68
+
69
+ <form class={formClass} onsubmit={handleSubmit}>
70
+ {#if requireOldPassword}
71
+ <div class='space-y-1.5'>
72
+ <label class='text-sm font-medium text-base-content/70' for='old-password'>
73
+ {uiM('change_password_old')}
74
+ </label>
75
+ <PasswordInput
76
+ value={oldPassword}
77
+ oninput={(e) => { oldPassword = e.currentTarget.value }}
78
+ placeholder={uiM('change_password_old_placeholder')}
79
+ {disabled}
80
+ error={errors.oldPassword}
81
+ showStrength={false}
82
+ />
83
+ </div>
84
+ {/if}
85
+
86
+ <div class='space-y-1.5'>
87
+ <label class='text-sm font-medium text-base-content/70' for='new-password'>
88
+ {uiM('change_password_new')}
89
+ </label>
90
+ <PasswordInput
91
+ value={newPassword}
92
+ oninput={(e) => { newPassword = e.currentTarget.value }}
93
+ placeholder={uiM('change_password_new_placeholder')}
94
+ {disabled}
95
+ error={errors.newPassword}
96
+ showStrength={showPasswordStrength}
97
+ minLength={minPasswordLength}
98
+ />
99
+ </div>
100
+
101
+ <div class='space-y-1.5'>
102
+ <label class='text-sm font-medium text-base-content/70' for='confirm-password'>
103
+ {uiM('change_password_confirm')}
104
+ </label>
105
+ <PasswordInput
106
+ value={confirmPassword}
107
+ oninput={(e) => { confirmPassword = e.currentTarget.value }}
108
+ placeholder={uiM('change_password_confirm_placeholder')}
109
+ {disabled}
110
+ error={errors.confirmPassword || (!passwordsMatch && confirmPassword ? uiM('change_password_mismatch') : '')}
111
+ showStrength={false}
112
+ />
113
+ </div>
114
+
115
+ <div class='rounded-lg bg-base-content/3 px-3.5 py-2.5 text-xs text-base-content/50'>
116
+ {uiM('change_password_relogin_hint')}
117
+ </div>
118
+
119
+ {#if errors.general}
120
+ <Alert variant='error'>
121
+ {errors.general}
122
+ </Alert>
123
+ {/if}
124
+
125
+ <Button
126
+ type='submit'
127
+ variant='primary'
128
+ class='w-full sm:w-auto sm:min-w-40'
129
+ disabled={loading || disabled || !canSubmit}
130
+ {loading}
131
+ >
132
+ {submitText || uiM('change_password_submit')}
133
+ </Button>
134
+ </form>
@@ -0,0 +1,8 @@
1
+ import type { ChangePasswordFormProps } from '../types.js';
2
+ /**
3
+ * ChangePasswordForm
4
+ * 修改密码表单组件。
5
+ */
6
+ declare const ChangePasswordForm: import("svelte").Component<ChangePasswordFormProps, {}, "">;
7
+ type ChangePasswordForm = ReturnType<typeof ChangePasswordForm>;
8
+ export default ChangePasswordForm;
@@ -0,0 +1,147 @@
1
+ <!--
2
+ =============================================================================
3
+ @h-ai/ui - ForgotPasswordForm 组件
4
+ =============================================================================
5
+ 找回密码表单组件
6
+
7
+ 使用 Svelte 5 Runes ($props, $state, $derived)
8
+ 使用 primitives/compounds 组件:Input, Button, Alert
9
+ =============================================================================
10
+ -->
11
+ <script lang='ts'>
12
+ import type { ForgotPasswordFormData, ForgotPasswordFormProps } from '../types.js'
13
+ import { uiM } from '../../../messages.js'
14
+ import { cn } from '../../../utils.js'
15
+ import Alert from '../../compounds/Alert.svelte'
16
+ import Button from '../../primitives/Button.svelte'
17
+ import Input from '../../primitives/Input.svelte'
18
+
19
+ const {
20
+ loading = false,
21
+ disabled = false,
22
+ showTitle = false,
23
+ showDescription = false,
24
+ mode = 'email',
25
+ showBackLink = false,
26
+ loginUrl = '/login',
27
+ submitText,
28
+ class: className = '',
29
+ errors = {},
30
+ onsubmit,
31
+ header,
32
+ footer,
33
+ }: ForgotPasswordFormProps = $props()
34
+
35
+ let email = $state('')
36
+ let phone = $state('')
37
+
38
+ const formClass = $derived(
39
+ cn(
40
+ 'forgot-password-form space-y-4',
41
+ className,
42
+ ),
43
+ )
44
+
45
+ async function handleSubmit(e: SubmitEvent) {
46
+ e.preventDefault()
47
+ if (loading || disabled)
48
+ return
49
+
50
+ const data: ForgotPasswordFormData = {
51
+ email: mode === 'email' ? email : undefined,
52
+ phone: mode === 'phone' ? phone : undefined,
53
+ }
54
+
55
+ await onsubmit?.(data)
56
+ }
57
+ </script>
58
+
59
+ <form class={formClass} onsubmit={handleSubmit}>
60
+ <!-- 标题 -->
61
+ {#if showTitle}
62
+ <h2 class='text-2xl font-semibold text-center mb-4'>{uiM('forgot_password_title')}</h2>
63
+ {/if}
64
+
65
+ <!-- 描述 -->
66
+ {#if showDescription}
67
+ <p class='text-center text-base-content/60 text-sm mb-6'>
68
+ {uiM('forgot_password_desc')}
69
+ </p>
70
+ {/if}
71
+
72
+ <!-- 自定义头部 -->
73
+ {#if header}
74
+ <div class='forgot-password-form-header'>
75
+ {@render header()}
76
+ </div>
77
+ {/if}
78
+
79
+ <!-- 邮箱输入 -->
80
+ {#if mode === 'email'}
81
+ <div class='fieldset'>
82
+ <legend class='fieldset-legend'>{uiM('forgot_password_email_label')}</legend>
83
+ <Input
84
+ id='forgot-email'
85
+ name='email'
86
+ type='email'
87
+ placeholder={uiM('forgot_password_email_placeholder')}
88
+ bind:value={email}
89
+ {disabled}
90
+ required
91
+ error={errors.email}
92
+ />
93
+ </div>
94
+ {:else}
95
+ <div class='fieldset'>
96
+ <legend class='fieldset-legend'>{uiM('forgot_password_phone_label')}</legend>
97
+ <Input
98
+ id='forgot-phone'
99
+ name='phone'
100
+ type='tel'
101
+ placeholder={uiM('forgot_password_phone_placeholder')}
102
+ bind:value={phone}
103
+ {disabled}
104
+ required
105
+ error={errors.phone}
106
+ />
107
+ </div>
108
+ {/if}
109
+
110
+ <!-- 提示信息 -->
111
+ <p class='text-sm text-base-content/60'>
112
+ {#if mode === 'email'}
113
+ {uiM('forgot_password_email_hint')}
114
+ {:else}
115
+ {uiM('forgot_password_phone_hint')}
116
+ {/if}
117
+ </p>
118
+
119
+ <!-- 通用错误 -->
120
+ {#if errors.general}
121
+ <Alert variant='error'>
122
+ {errors.general}
123
+ </Alert>
124
+ {/if}
125
+
126
+ <!-- 提交按钮 -->
127
+ <Button
128
+ type='submit'
129
+ variant='primary'
130
+ {loading}
131
+ disabled={loading || disabled}
132
+ class='w-full'
133
+ >
134
+ {submitText || uiM('forgot_password_submit')}
135
+ </Button>
136
+
137
+ <!-- 自定义底部 -->
138
+ {#if footer}
139
+ <div class='forgot-password-form-footer'>
140
+ {@render footer()}
141
+ </div>
142
+ {:else if showBackLink}
143
+ <div class='text-center mt-4 text-sm'>
144
+ <a href={loginUrl} class='link link-primary'>← {uiM('forgot_password_back')}</a>
145
+ </div>
146
+ {/if}
147
+ </form>
@@ -0,0 +1,4 @@
1
+ import type { ForgotPasswordFormProps } from '../types.js';
2
+ declare const ForgotPasswordForm: import("svelte").Component<ForgotPasswordFormProps, {}, "">;
3
+ type ForgotPasswordForm = ReturnType<typeof ForgotPasswordForm>;
4
+ export default ForgotPasswordForm;
@@ -0,0 +1,191 @@
1
+ <!--
2
+ @component LoginForm
3
+ 用户登录表单组件,搭配 primitives/compounds 使用。
4
+ -->
5
+ <script lang='ts'>
6
+ import type { LoginFormData, LoginFormProps } from '../types.js'
7
+ import { uiM } from '../../../messages.js'
8
+ import { cn } from '../../../utils.js'
9
+ import Alert from '../../compounds/Alert.svelte'
10
+ import BareButton from '../../primitives/BareButton.svelte'
11
+ import Button from '../../primitives/Button.svelte'
12
+ import Checkbox from '../../primitives/Checkbox.svelte'
13
+ import Input from '../../primitives/Input.svelte'
14
+ import PasswordInput from './PasswordInput.svelte'
15
+
16
+ const {
17
+ loading = false,
18
+ disabled = false,
19
+ showTitle = false,
20
+ showRememberMe = true,
21
+ showForgotPassword = true,
22
+ forgotPasswordUrl = '/forgot-password',
23
+ showRegisterLink = false,
24
+ registerUrl = '/register',
25
+ agreements,
26
+ submitText,
27
+ class: className = '',
28
+ errors = {},
29
+ onsubmit,
30
+ onforgotpassword,
31
+ header,
32
+ footer,
33
+ }: LoginFormProps = $props()
34
+
35
+ const hasAgreements = $derived(
36
+ !!(agreements?.userAgreementUrl || agreements?.privacyPolicyUrl),
37
+ )
38
+
39
+ let username = $state('')
40
+ let password = $state('')
41
+ let rememberMe = $state(false)
42
+
43
+ const formClass = $derived(
44
+ cn(
45
+ 'login-form space-y-4',
46
+ className,
47
+ ),
48
+ )
49
+
50
+ async function handleSubmit(e: SubmitEvent) {
51
+ e.preventDefault()
52
+ if (loading || disabled)
53
+ return
54
+
55
+ const data: LoginFormData = {
56
+ username,
57
+ password,
58
+ rememberMe,
59
+ }
60
+
61
+ await onsubmit?.(data)
62
+ }
63
+
64
+ function handleForgotPassword() {
65
+ onforgotpassword?.()
66
+ }
67
+ </script>
68
+
69
+ <form class={formClass} onsubmit={handleSubmit}>
70
+ <!-- 标题 -->
71
+ {#if showTitle}
72
+ <h2 class='text-xl font-semibold text-center mb-5'>{uiM('login_title')}</h2>
73
+ {/if}
74
+
75
+ <!-- 自定义头部 -->
76
+ {#if header}
77
+ <div class='login-form-header'>
78
+ {@render header()}
79
+ </div>
80
+ {/if}
81
+
82
+ <!-- 用户名 -->
83
+ <div class='space-y-1.5'>
84
+ <label class='text-sm font-medium text-base-content/70' for='login-username'>
85
+ {uiM('login_username')}
86
+ </label>
87
+ <Input
88
+ id='login-username'
89
+ name='username'
90
+ type='text'
91
+ placeholder={uiM('login_username_placeholder')}
92
+ bind:value={username}
93
+ {disabled}
94
+ required
95
+ error={errors.username}
96
+ />
97
+ </div>
98
+
99
+ <!-- 密码 -->
100
+ <div class='space-y-1.5'>
101
+ <label class='text-sm font-medium text-base-content/70' for='login-password'>
102
+ {uiM('login_password')}
103
+ </label>
104
+ <PasswordInput
105
+ bind:value={password}
106
+ placeholder={uiM('login_password_placeholder')}
107
+ {disabled}
108
+ required
109
+ error={errors.password}
110
+ showStrength={false}
111
+ />
112
+ </div>
113
+
114
+ <!-- 记住我 & 忘记密码 -->
115
+ {#if showRememberMe || showForgotPassword}
116
+ <div class='flex items-center justify-between'>
117
+ {#if showRememberMe}
118
+ <Checkbox
119
+ bind:checked={rememberMe}
120
+ label={uiM('login_remember_me')}
121
+ {disabled}
122
+ size='sm'
123
+ />
124
+ {:else}
125
+ <div></div>
126
+ {/if}
127
+
128
+ {#if showForgotPassword}
129
+ {#if onforgotpassword}
130
+ <BareButton
131
+ type='button'
132
+ class='link link-primary text-sm'
133
+ onclick={handleForgotPassword}
134
+ >
135
+ {uiM('login_forgot_password')}
136
+ </BareButton>
137
+ {:else}
138
+ <a href={forgotPasswordUrl} class='link link-primary text-sm'>
139
+ {uiM('login_forgot_password')}
140
+ </a>
141
+ {/if}
142
+ {/if}
143
+ </div>
144
+ {/if}
145
+
146
+ <!-- 通用错误 -->
147
+ {#if errors.general}
148
+ <Alert variant='error'>
149
+ {errors.general}
150
+ </Alert>
151
+ {/if}
152
+
153
+ <!-- 提交按钮 -->
154
+ <Button
155
+ type='submit'
156
+ variant='primary'
157
+ {loading}
158
+ disabled={loading || disabled}
159
+ class='w-full'
160
+ >
161
+ {submitText || uiM('login_submit')}
162
+ </Button>
163
+
164
+ <!-- 协议提示 -->
165
+ {#if hasAgreements}
166
+ <p class='text-xs text-base-content/50 text-center'>
167
+ {uiM('agreement_prefix')}
168
+ {#if agreements?.userAgreementUrl}
169
+ <a href={agreements.userAgreementUrl} target='_blank' rel='noopener noreferrer' class='link link-primary'>{uiM('agreement_user_agreement')}</a>
170
+ {/if}
171
+ {#if agreements?.userAgreementUrl && agreements?.privacyPolicyUrl}
172
+ {uiM('agreement_and')}
173
+ {/if}
174
+ {#if agreements?.privacyPolicyUrl}
175
+ <a href={agreements.privacyPolicyUrl} target='_blank' rel='noopener noreferrer' class='link link-primary'>{uiM('agreement_privacy_policy')}</a>
176
+ {/if}
177
+ </p>
178
+ {/if}
179
+
180
+ <!-- 自定义底部 -->
181
+ {#if footer}
182
+ <div class='login-form-footer'>
183
+ {@render footer()}
184
+ </div>
185
+ {:else if showRegisterLink}
186
+ <div class='text-center mt-4 text-sm text-base-content/60'>
187
+ <span>{uiM('login_no_account')}</span>
188
+ <a href={registerUrl} class='link link-primary ml-1'>{uiM('login_register_now')}</a>
189
+ </div>
190
+ {/if}
191
+ </form>
@@ -0,0 +1,8 @@
1
+ import type { LoginFormProps } from '../types.js';
2
+ /**
3
+ * LoginForm
4
+ * 用户登录表单组件,搭配 primitives/compounds 使用。
5
+ */
6
+ declare const LoginForm: import("svelte").Component<LoginFormProps, {}, "">;
7
+ type LoginForm = ReturnType<typeof LoginForm>;
8
+ export default LoginForm;
@@ -0,0 +1,208 @@
1
+ <!--
2
+ @component PasswordInput
3
+ 密码输入框组件,支持显示/隐藏切换和密码强度指示。
4
+ 内置多语言支持,自动跟随全局 locale。
5
+ -->
6
+ <script lang='ts'>
7
+ import type { Size } from '../../../types.js'
8
+ import { uiM } from '../../../messages.js'
9
+ import { cn } from '../../../utils.js'
10
+ import BareButton from '../../primitives/BareButton.svelte'
11
+ import BareInput from '../../primitives/BareInput.svelte'
12
+ import Progress from '../../primitives/Progress.svelte'
13
+
14
+ const PASSWORD_LOWERCASE_REGEX = /[a-z]/
15
+ const PASSWORD_UPPERCASE_REGEX = /[A-Z]/
16
+ const PASSWORD_DIGIT_REGEX = /\d/
17
+ const PASSWORD_SPECIAL_CHAR_REGEX = /[^a-z0-9]/i
18
+
19
+ interface Props {
20
+ /** 元素 ID */
21
+ id?: string
22
+ /** 值 */
23
+ value?: string
24
+ /** 占位符 */
25
+ placeholder?: string
26
+ /** 尺寸 */
27
+ size?: Size
28
+ /** 是否禁用 */
29
+ disabled?: boolean
30
+ /** 是否只读 */
31
+ readonly?: boolean
32
+ /** 是否必填 */
33
+ required?: boolean
34
+ /** 错误消息 */
35
+ error?: string
36
+ /** 是否显示切换按钮 */
37
+ showToggle?: boolean
38
+ /** 是否显示密码强度 */
39
+ showStrength?: boolean
40
+ /** 最小长度 */
41
+ minLength?: number
42
+ /** 自定义类名 */
43
+ class?: string
44
+ /** 输入框引用 */
45
+ inputRef?: HTMLInputElement
46
+ /** 输入事件 */
47
+ oninput?: (e: Event & { currentTarget: HTMLInputElement }) => void
48
+ /** 变化事件 */
49
+ onchange?: (e: Event & { currentTarget: HTMLInputElement }) => void
50
+ /** 无效事件 */
51
+ oninvalid?: (e: Event & { currentTarget: HTMLInputElement }) => void
52
+ }
53
+
54
+ let {
55
+ id,
56
+ value = $bindable(''),
57
+ placeholder = '',
58
+ size = 'md',
59
+ disabled = false,
60
+ readonly = false,
61
+ required = false,
62
+ error = '',
63
+ showToggle = true,
64
+ showStrength = false,
65
+ minLength = 8,
66
+ class: className = '',
67
+ inputRef = $bindable<HTMLInputElement | undefined>(),
68
+ oninput,
69
+ onchange,
70
+ oninvalid,
71
+ }: Props = $props()
72
+
73
+ let showPassword = $state(false)
74
+
75
+ // 容器高度(与 Input 组件一致)
76
+ const containerHeight = $derived(
77
+ size === 'xs'
78
+ ? 'h-8'
79
+ : size === 'sm'
80
+ ? 'h-9'
81
+ : size === 'lg'
82
+ ? 'h-12'
83
+ : size === 'xl'
84
+ ? 'h-14'
85
+ : 'h-10',
86
+ )
87
+
88
+ // 图标大小
89
+ const iconSize = $derived(
90
+ size === 'xs'
91
+ ? 'size-3.5'
92
+ : size === 'sm'
93
+ ? 'size-3.5'
94
+ : size === 'lg'
95
+ ? 'size-5'
96
+ : 'size-4',
97
+ )
98
+
99
+ // 计算密码强度
100
+ const strength = $derived.by(() => {
101
+ if (!value)
102
+ return { score: 0, label: '', color: '' }
103
+
104
+ let score = 0
105
+
106
+ // 长度检查
107
+ if (value.length >= minLength)
108
+ score += 1
109
+ if (value.length >= 12)
110
+ score += 1
111
+
112
+ // 复杂度检查
113
+ if (PASSWORD_LOWERCASE_REGEX.test(value))
114
+ score += 1
115
+ if (PASSWORD_UPPERCASE_REGEX.test(value))
116
+ score += 1
117
+ if (PASSWORD_DIGIT_REGEX.test(value))
118
+ score += 1
119
+ if (PASSWORD_SPECIAL_CHAR_REGEX.test(value))
120
+ score += 1
121
+
122
+ // 返回强度等级
123
+ if (score <= 2)
124
+ return { score: 1, label: uiM('password_strength_weak'), color: 'bg-error' }
125
+ if (score <= 4)
126
+ return { score: 2, label: uiM('password_strength_fair'), color: 'bg-warning' }
127
+ if (score <= 5)
128
+ return { score: 3, label: uiM('password_strength_good'), color: 'bg-success' }
129
+ return { score: 4, label: uiM('password_strength_strong'), color: 'bg-primary' }
130
+ })
131
+
132
+ function handleInput(e: Event & { currentTarget: HTMLInputElement }) {
133
+ value = e.currentTarget.value
134
+ oninput?.(e)
135
+ }
136
+
137
+ function handleChange(e: Event & { currentTarget: HTMLInputElement }) {
138
+ onchange?.(e)
139
+ }
140
+
141
+ function togglePassword() {
142
+ showPassword = !showPassword
143
+ }
144
+ </script>
145
+
146
+ <div class='fieldset w-full'>
147
+ <!-- 输入框容器 -->
148
+ <div class={cn(
149
+ 'flex items-center w-full rounded-lg border bg-base-100',
150
+ containerHeight,
151
+ error
152
+ ? 'border-error/60 focus-within:ring-2 focus-within:ring-error/15'
153
+ : 'border-base-content/15 focus-within:border-primary/50 focus-within:ring-2 focus-within:ring-primary/10',
154
+ 'transition-[border-color,box-shadow] duration-150',
155
+ disabled && 'opacity-50 cursor-not-allowed',
156
+ className,
157
+ )}>
158
+ <BareInput
159
+ type={showPassword ? 'text' : 'password'}
160
+ {id}
161
+ class='flex-1 h-full px-3 bg-transparent border-none outline-none text-sm placeholder:text-base-content/35'
162
+ bind:value
163
+ bind:inputRef={inputRef}
164
+ {placeholder}
165
+ {disabled}
166
+ {readonly}
167
+ {required}
168
+ oninput={handleInput}
169
+ onchange={handleChange}
170
+ oninvalid={oninvalid}
171
+ />
172
+
173
+ {#if showToggle}
174
+ <!-- 切换按钮 -->
175
+ <BareButton
176
+ type='button'
177
+ class='flex items-center justify-center w-9 h-full text-base-content/35 hover:text-base-content/60 transition-colors'
178
+ onclick={togglePassword}
179
+ tabindex={-1}
180
+ {disabled}
181
+ ariaLabel={showPassword ? uiM('password_hide') : uiM('password_show')}
182
+ >
183
+ <span class={cn(
184
+ showPassword ? 'icon-[tabler--eye-off]' : 'icon-[tabler--eye]',
185
+ iconSize,
186
+ )}></span>
187
+ </BareButton>
188
+ {/if}
189
+ </div>
190
+
191
+ {#if showStrength && value}
192
+ <div class='mt-2'>
193
+ <Progress
194
+ value={strength.score}
195
+ max={4}
196
+ size='xs'
197
+ variant={strength.score <= 1 ? 'error' : strength.score <= 2 ? 'warning' : strength.score <= 3 ? 'success' : 'primary'}
198
+ />
199
+ <span class='text-xs text-base-content/40 mt-1 block'>
200
+ {uiM('password_strength_label')} {strength.label}
201
+ </span>
202
+ </div>
203
+ {/if}
204
+
205
+ {#if error}
206
+ <p class='mt-1.5 text-xs text-error/80 leading-tight'>{error}</p>
207
+ {/if}
208
+ </div>