@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,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,203 @@
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
+ interface Props {
15
+ /** 元素 ID */
16
+ id?: string
17
+ /** 值 */
18
+ value?: string
19
+ /** 占位符 */
20
+ placeholder?: string
21
+ /** 尺寸 */
22
+ size?: Size
23
+ /** 是否禁用 */
24
+ disabled?: boolean
25
+ /** 是否只读 */
26
+ readonly?: boolean
27
+ /** 是否必填 */
28
+ required?: boolean
29
+ /** 错误消息 */
30
+ error?: string
31
+ /** 是否显示切换按钮 */
32
+ showToggle?: boolean
33
+ /** 是否显示密码强度 */
34
+ showStrength?: boolean
35
+ /** 最小长度 */
36
+ minLength?: number
37
+ /** 自定义类名 */
38
+ class?: string
39
+ /** 输入框引用 */
40
+ inputRef?: HTMLInputElement
41
+ /** 输入事件 */
42
+ oninput?: (e: Event & { currentTarget: HTMLInputElement }) => void
43
+ /** 变化事件 */
44
+ onchange?: (e: Event & { currentTarget: HTMLInputElement }) => void
45
+ /** 无效事件 */
46
+ oninvalid?: (e: Event & { currentTarget: HTMLInputElement }) => void
47
+ }
48
+
49
+ let {
50
+ id,
51
+ value = $bindable(''),
52
+ placeholder = '',
53
+ size = 'md',
54
+ disabled = false,
55
+ readonly = false,
56
+ required = false,
57
+ error = '',
58
+ showToggle = true,
59
+ showStrength = false,
60
+ minLength = 8,
61
+ class: className = '',
62
+ inputRef = $bindable<HTMLInputElement | undefined>(),
63
+ oninput,
64
+ onchange,
65
+ oninvalid,
66
+ }: Props = $props()
67
+
68
+ let showPassword = $state(false)
69
+
70
+ // 容器高度(与 Input 组件一致)
71
+ const containerHeight = $derived(
72
+ size === 'xs'
73
+ ? 'h-8'
74
+ : size === 'sm'
75
+ ? 'h-9'
76
+ : size === 'lg'
77
+ ? 'h-12'
78
+ : size === 'xl'
79
+ ? 'h-14'
80
+ : 'h-10',
81
+ )
82
+
83
+ // 图标大小
84
+ const iconSize = $derived(
85
+ size === 'xs'
86
+ ? 'size-3.5'
87
+ : size === 'sm'
88
+ ? 'size-3.5'
89
+ : size === 'lg'
90
+ ? 'size-5'
91
+ : 'size-4',
92
+ )
93
+
94
+ // 计算密码强度
95
+ const strength = $derived.by(() => {
96
+ if (!value)
97
+ return { score: 0, label: '', color: '' }
98
+
99
+ let score = 0
100
+
101
+ // 长度检查
102
+ if (value.length >= minLength)
103
+ score += 1
104
+ if (value.length >= 12)
105
+ score += 1
106
+
107
+ // 复杂度检查
108
+ if (/[a-z]/.test(value))
109
+ score += 1
110
+ if (/[A-Z]/.test(value))
111
+ score += 1
112
+ if (/\d/.test(value))
113
+ score += 1
114
+ if (/[^a-z0-9]/i.test(value))
115
+ score += 1
116
+
117
+ // 返回强度等级
118
+ if (score <= 2)
119
+ return { score: 1, label: uiM('password_strength_weak'), color: 'bg-error' }
120
+ if (score <= 4)
121
+ return { score: 2, label: uiM('password_strength_fair'), color: 'bg-warning' }
122
+ if (score <= 5)
123
+ return { score: 3, label: uiM('password_strength_good'), color: 'bg-success' }
124
+ return { score: 4, label: uiM('password_strength_strong'), color: 'bg-primary' }
125
+ })
126
+
127
+ function handleInput(e: Event & { currentTarget: HTMLInputElement }) {
128
+ value = e.currentTarget.value
129
+ oninput?.(e)
130
+ }
131
+
132
+ function handleChange(e: Event & { currentTarget: HTMLInputElement }) {
133
+ onchange?.(e)
134
+ }
135
+
136
+ function togglePassword() {
137
+ showPassword = !showPassword
138
+ }
139
+ </script>
140
+
141
+ <div class='fieldset w-full'>
142
+ <!-- 输入框容器 -->
143
+ <div class={cn(
144
+ 'flex items-center w-full rounded-lg border bg-base-100',
145
+ containerHeight,
146
+ error
147
+ ? 'border-error/60 focus-within:ring-2 focus-within:ring-error/15'
148
+ : 'border-base-content/15 focus-within:border-primary/50 focus-within:ring-2 focus-within:ring-primary/10',
149
+ 'transition-[border-color,box-shadow] duration-150',
150
+ disabled && 'opacity-50 cursor-not-allowed',
151
+ className,
152
+ )}>
153
+ <BareInput
154
+ type={showPassword ? 'text' : 'password'}
155
+ {id}
156
+ class='flex-1 h-full px-3 bg-transparent border-none outline-none text-sm placeholder:text-base-content/35'
157
+ bind:value
158
+ bind:inputRef={inputRef}
159
+ {placeholder}
160
+ {disabled}
161
+ {readonly}
162
+ {required}
163
+ oninput={handleInput}
164
+ onchange={handleChange}
165
+ oninvalid={oninvalid}
166
+ />
167
+
168
+ {#if showToggle}
169
+ <!-- 切换按钮 -->
170
+ <BareButton
171
+ type='button'
172
+ class='flex items-center justify-center w-9 h-full text-base-content/35 hover:text-base-content/60 transition-colors'
173
+ onclick={togglePassword}
174
+ tabindex={-1}
175
+ {disabled}
176
+ ariaLabel={showPassword ? uiM('password_hide') : uiM('password_show')}
177
+ >
178
+ <span class={cn(
179
+ showPassword ? 'icon-[tabler--eye-off]' : 'icon-[tabler--eye]',
180
+ iconSize,
181
+ )}></span>
182
+ </BareButton>
183
+ {/if}
184
+ </div>
185
+
186
+ {#if showStrength && value}
187
+ <div class='mt-2'>
188
+ <Progress
189
+ value={strength.score}
190
+ max={4}
191
+ size='xs'
192
+ variant={strength.score <= 1 ? 'error' : strength.score <= 2 ? 'warning' : strength.score <= 3 ? 'success' : 'primary'}
193
+ />
194
+ <span class='text-xs text-base-content/40 mt-1 block'>
195
+ {uiM('password_strength_label')} {strength.label}
196
+ </span>
197
+ </div>
198
+ {/if}
199
+
200
+ {#if error}
201
+ <p class='mt-1.5 text-xs text-error/80 leading-tight'>{error}</p>
202
+ {/if}
203
+ </div>