@h-ai/ui 0.1.0-alpha.56 → 0.1.0-alpha.58
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.
- package/README.en.md +742 -0
- package/README.ja.md +742 -0
- package/README.md +21 -4
- package/auto-import.js +9 -66
- package/dist/components/compounds/Modal.svelte +53 -6
- package/dist/components/primitives/BareInput.svelte +4 -2
- package/dist/components/scenes/app/LanguageSwitch.svelte +1 -1
- package/dist/components/scenes/app/LanguageSwitch.svelte.d.ts +1 -1
- package/dist/components/scenes/iam/UserProfile.svelte +9 -5
- package/dist/components/scenes/types.d.ts +4 -0
- package/dist/messages/en-US.json +2 -0
- package/dist/messages/zh-CN.json +2 -0
- package/dist/theme-config.d.ts +4 -2
- package/dist/theme-config.js +5 -3
- package/dist/types.d.ts +1 -1
- package/package.json +25 -21
package/README.en.md
ADDED
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
# @h-ai/ui
|
|
2
|
+
|
|
3
|
+
[简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
|
|
4
|
+
|
|
5
|
+
[Framework home](../../README.en.md) · Related modules: [@h-ai/core](../core/README.en.md)
|
|
6
|
+
|
|
7
|
+
> A cross-platform Svelte 5 Runes component library with DaisyUI v5 + Tailwind CSS v4 styling and Bits UI v2 headless interactions. Includes built-in i18n (zh-CN / en-US) and 15 curated DaisyUI themes.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @h-ai/ui
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Depends on `@h-ai/core`, installed automatically.
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
### 1. Configure svelte.config.js
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
import { autoImportHaiUi } from '@h-ai/ui/auto-import'
|
|
23
|
+
import adapter from '@sveltejs/adapter-node'
|
|
24
|
+
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
|
25
|
+
|
|
26
|
+
/** @type {import('@sveltejs/kit').Config} */
|
|
27
|
+
const config = {
|
|
28
|
+
preprocess: [autoImportHaiUi(), vitePreprocess()],
|
|
29
|
+
compilerOptions: { runes: true },
|
|
30
|
+
kit: { adapter: adapter() },
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default config
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 2. Configure vite.config.ts
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { haiOptimizeExclude, haiPrebundledDeps } from '@h-ai/ui/vite'
|
|
40
|
+
import { sveltekit } from '@sveltejs/kit/vite'
|
|
41
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
42
|
+
import { defineConfig } from 'vite'
|
|
43
|
+
|
|
44
|
+
export default defineConfig({
|
|
45
|
+
plugins: [sveltekit(), tailwindcss()],
|
|
46
|
+
optimizeDeps: {
|
|
47
|
+
// bits-ui 含 .svelte 源码须 exclude;@internationalized/date 须与 bits-ui 同为原始副本,
|
|
48
|
+
// 否则日期组件会因双实例 instanceof 失败抛 "Unknown date type"
|
|
49
|
+
exclude: [...haiOptimizeExclude],
|
|
50
|
+
// 预打包 @h-ai/ui 的重型纯 JS 依赖(语法高亮 / Mermaid / PDF 等),
|
|
51
|
+
// 避免首次进入业务路由时触发依赖再优化与整页刷新
|
|
52
|
+
include: [...haiPrebundledDeps],
|
|
53
|
+
},
|
|
54
|
+
ssr: { noExternal: [/@h-ai\//] },
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. Configure src/app.css
|
|
59
|
+
|
|
60
|
+
```css
|
|
61
|
+
@import 'tailwindcss';
|
|
62
|
+
@import '@h-ai/ui/styles/global.css';
|
|
63
|
+
@import '@h-ai/ui/styles/theme.css';
|
|
64
|
+
@source "../node_modules/@h-ai/ui/dist/**/*.{svelte,js,ts}";
|
|
65
|
+
@source "../../../node_modules/@h-ai/ui/dist/**/*.{svelte,js,ts}";
|
|
66
|
+
|
|
67
|
+
@plugin "daisyui" {
|
|
68
|
+
themes: light --default, dark --prefersdark, cupcake, emerald, corporate, nord, dracula, night;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* 图标(可选) */
|
|
72
|
+
@plugin "@iconify/tailwind4" {
|
|
73
|
+
prefixes: tabler;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
> `@source` makes TailwindCSS scan classes in `@h-ai/ui`; without it, component styles are missing. In nested monorepo apps, dependencies may be hoisted to root `node_modules`, so retain both paths.
|
|
78
|
+
|
|
79
|
+
### 4. Use components
|
|
80
|
+
|
|
81
|
+
```svelte
|
|
82
|
+
<script>
|
|
83
|
+
import { Button, Input, Card } from '@h-ai/ui'
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
<Card title="示例表单">
|
|
87
|
+
<Input placeholder="请输入用户名" />
|
|
88
|
+
<Button variant="primary" onclick={handleSubmit}>提交</Button>
|
|
89
|
+
</Card>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
With auto-import enabled, use components directly in templates without individual imports:
|
|
93
|
+
|
|
94
|
+
```svelte
|
|
95
|
+
<!-- 无需 import { Button } from '@h-ai/ui' -->
|
|
96
|
+
<Button variant="primary">提交</Button>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
> `@h-ai/ui/auto-import` injects all public Svelte components except `toast`, type imports, and `Range` (which conflicts with the DOM `Range` constructor).
|
|
100
|
+
|
|
101
|
+
Pages using a subset of components, such as administration screens, can import public component-directory entry points directly. Import theme utilities from the lightweight `@h-ai/ui/theme` entry so theme consumers do not load the root entry's AI and chart components. Check the installed package's exports; do not import its internal `src/` or `dist/` paths.
|
|
102
|
+
|
|
103
|
+
The preprocessor recognizes semicolon-free, multiline and aliased imports without injecting components already bound locally. Type-only imports do not replace runtime components.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { Card } from '@h-ai/ui/components/compounds/index'
|
|
107
|
+
import { Button } from '@h-ai/ui/components/primitives/index'
|
|
108
|
+
import { getThemeInfo, THEMES } from '@h-ai/ui/theme'
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### data-\* attribute forwarding
|
|
112
|
+
|
|
113
|
+
Public Svelte components forward caller-supplied `data-*` attributes to their root or primary interactive element for tests, analytics, and automation. Other unknown attributes are not forwarded.
|
|
114
|
+
|
|
115
|
+
```svelte
|
|
116
|
+
<Button data-testid='save-button' data-analytics-id='settings.save'>保存</Button>
|
|
117
|
+
<Input data-testid='username-input' bind:value={username} />
|
|
118
|
+
<Modal data-testid='confirm-modal' bind:open={open}>...</Modal>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
No extra button, input, or overlay wrappers are needed just for `data-testid`.
|
|
122
|
+
|
|
123
|
+
## Component architecture
|
|
124
|
+
|
|
125
|
+
Three layers (primitives → compounds → scenes):
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
components/
|
|
129
|
+
├── primitives/ # Indivisible base UI elements
|
|
130
|
+
├── compounds/ # Primitives + Bits UI headless composition
|
|
131
|
+
└── scenes/ # Complete UI workflows for business scenarios
|
|
132
|
+
├── ai/ # AI documents / spreadsheets / Markdown preview
|
|
133
|
+
├── app/ # Settings / feedback / themes / language
|
|
134
|
+
├── crud/ # CRUD pages / drawers / filters / delete confirmation
|
|
135
|
+
├── iam/ # Authentication
|
|
136
|
+
├── storage/ # Storage management
|
|
137
|
+
└── crypto/ # Cryptographic display
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Component catalog
|
|
141
|
+
|
|
142
|
+
### Primitives (20)
|
|
143
|
+
|
|
144
|
+
| Component | Description | Key props |
|
|
145
|
+
| ---------------- | ------------------- | ------------------------------------------------------------------------------ |
|
|
146
|
+
| `Button` | Button | `variant`, `size`, `loading`, `disabled`, `outline`, `circle` |
|
|
147
|
+
| `IconButton` | Icon button | `icon: trusted SVG string \| Snippet`, `variant`, `size`, `tooltip`, `loading` |
|
|
148
|
+
| `BareButton` | Unstyled button | `class`, `ariaLabel`, `role`, `tabindex` |
|
|
149
|
+
| `Input` | Input | `type`, `value`, `size`, `error`, `validationMessage` |
|
|
150
|
+
| `BareInput` | Unstyled input | `type`, `class`, `accept`, `multiple` |
|
|
151
|
+
| `Textarea` | Text area | `value`, `rows`, `size`, `autoResize`, `error` |
|
|
152
|
+
| `Select` | Select | `options`, `value`, `placeholder`, `size`, `clearable`, `filterable` |
|
|
153
|
+
| `Checkbox` | Checkbox | `checked`, `label`, `size`, `indeterminate` |
|
|
154
|
+
| `Switch` | Switch | `checked`, `label`, `size` |
|
|
155
|
+
| `Radio` | Radio group | `options`, `value`, `direction`, `size` |
|
|
156
|
+
| `ToggleCheckbox` | Native switch input | `checked`, `name`, `onchange` |
|
|
157
|
+
| `ToggleInput` | Native toggle input | `checked`, `name` |
|
|
158
|
+
| `ToggleRadio` | Native radio input | `checked`, `name`, `onchange` |
|
|
159
|
+
| `Range` | Slider | `value`, `min`, `max`, `step` |
|
|
160
|
+
| `Rating` | Rating | `value`, `max` |
|
|
161
|
+
| `Badge` | Badge | `variant`, `size`, `outline` |
|
|
162
|
+
| `Avatar` | Avatar | `src`, `name`, `size`, `shape`, `ring` |
|
|
163
|
+
| `Tag` | Tag | `text`, `variant`, `size`, `closable` |
|
|
164
|
+
| `Spinner` | Loading animation | `size`, `variant` |
|
|
165
|
+
| `Progress` | Progress bar | `value`, `max`, `variant`, `striped`, `animated` |
|
|
166
|
+
|
|
167
|
+
For `BareInput` with `type="file"`, read `event.currentTarget.files` in `onchange` and leave the file path `value` unbound. Other input types support `bind:value`.
|
|
168
|
+
|
|
169
|
+
### Compounds (36)
|
|
170
|
+
|
|
171
|
+
#### Forms
|
|
172
|
+
|
|
173
|
+
| Component | Description | Key props |
|
|
174
|
+
| ----------- | -------------- | -------------------------------------------- |
|
|
175
|
+
| `Form` | Form container | `loading`, `disabled`, `onsubmit` |
|
|
176
|
+
| `FormField` | Form field | `label`, `error`, `hint`, `required` |
|
|
177
|
+
| `TagInput` | Tag input | `tags`, `maxTags`, `allowDuplicates`, `size` |
|
|
178
|
+
|
|
179
|
+
#### Feedback
|
|
180
|
+
|
|
181
|
+
| Component | Description | Key props |
|
|
182
|
+
| ---------------- | --------------- | ------------------------------------------------- |
|
|
183
|
+
| `Alert` | Alert | `variant`, `title`, `dismissible` |
|
|
184
|
+
| `ToastContainer` | Toast container | Place globally and use with the `toast` singleton |
|
|
185
|
+
|
|
186
|
+
#### Overlays
|
|
187
|
+
|
|
188
|
+
| Component | Description | Key props |
|
|
189
|
+
| --------- | ------------------- | ---------------------------------------------------------------------------- |
|
|
190
|
+
| `Modal` | Modal with dialog semantics, focus trap and restoration | `open`, `title`, `size`, `radius`, `bodyOverflow`, `showClose` |
|
|
191
|
+
| `Drawer` | Drawer | `open`, `position`, `title`, `size`, `width`, `resizable`, `widthStorageKey` |
|
|
192
|
+
| `Confirm` | Confirmation dialog | `open`, `title`, `message`, `variant`, `onconfirm` |
|
|
193
|
+
| `Popover` | Popover | `open`, `position`, `trigger`, `offset`, `portal` |
|
|
194
|
+
|
|
195
|
+
#### Data display
|
|
196
|
+
|
|
197
|
+
| Component | Description | Key props |
|
|
198
|
+
| ----------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
199
|
+
| `Card` | Card container | `title`, `bordered`, `shadow`, `padding` |
|
|
200
|
+
| `DataTable` | Data table | `data`, `columns`, `keyField`, `loading`, `sortKey`, `sortDir`, `onsort`; `cell` slot and `onrowclick` (`sortable` columns support sorting) |
|
|
201
|
+
| `Accordion` | Accordion | `items: AccordionItem[]` |
|
|
202
|
+
| `Timeline` | Timeline | `items: TimelineItem[]` |
|
|
203
|
+
|
|
204
|
+
#### Bits UI headless interactions
|
|
205
|
+
|
|
206
|
+
| Component | Description | Key props |
|
|
207
|
+
| ------------ | ----------------------------------- | ----------------------------------------------------------- |
|
|
208
|
+
| `Combobox` | Searchable select (single/multiple) | `options`, `value`, `multiple`, `placeholder`, `size` |
|
|
209
|
+
| `Calendar` | Standalone calendar | `value: DateValue`, `minValue`, `maxValue` |
|
|
210
|
+
| `DatePicker` | Date input with popover | `value: DateValue`, `minValue`, `maxValue`, `error`, `size` |
|
|
211
|
+
|
|
212
|
+
> Dates use `DateValue` / `CalendarDate` from `@internationalized/date`.
|
|
213
|
+
|
|
214
|
+
#### Mobile / enhanced interactions
|
|
215
|
+
|
|
216
|
+
| Component | Description | Key props |
|
|
217
|
+
| ---------------- | ------------------- | ------------------------------------------------------ |
|
|
218
|
+
| `ActionSheet` | Bottom action sheet | `open`, `title`, `items`, `cancelText`, `onselect` |
|
|
219
|
+
| `AppBar` | Top navigation | `title`, `backHref`, `onback`, `fixed`, `safeArea` |
|
|
220
|
+
| `BottomNav` | Bottom navigation | `items`, `active`, `centered`, `maxWidth` |
|
|
221
|
+
| `InfiniteScroll` | Infinite scrolling | `loading`, `finished`, `threshold`, `onload` |
|
|
222
|
+
| `PullRefresh` | Pull to refresh | `refreshing`, `threshold`, `onrefresh` |
|
|
223
|
+
| `SafeArea` | Safe area container | `top`, `bottom`, `left`, `right` |
|
|
224
|
+
| `SwipeCell` | Swipe actions | `leftActions`, `rightActions`, `threshold`, `onaction` |
|
|
225
|
+
|
|
226
|
+
#### Navigation
|
|
227
|
+
|
|
228
|
+
| Component | Description | Key props |
|
|
229
|
+
| ------------ | ------------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
230
|
+
| `Tabs` | Tabs | `items`, `active`, `type`, `size`, `lazy`, `onactivate` |
|
|
231
|
+
| `Pagination` | Pagination (shared table style) | `page`, `total`, `pageSize`, `showTotal`, `showJumper`, `showSizeChanger`, `showPageInfo`, `showFirstLast` |
|
|
232
|
+
| `Breadcrumb` | Breadcrumbs | `items`, `separator` |
|
|
233
|
+
| `Steps` | Steps | `items`, `current`, `direction`, `clickable` |
|
|
234
|
+
| `Dropdown` | Dropdown menu | `items`, `trigger`, `triggerClass`, `position` |
|
|
235
|
+
| `Tooltip` | Tooltip | `content`, `position`, `delay` |
|
|
236
|
+
|
|
237
|
+
#### State placeholders
|
|
238
|
+
|
|
239
|
+
| Component | Description | Key props |
|
|
240
|
+
| ---------- | ----------- | ------------------------------------- |
|
|
241
|
+
| `Skeleton` | Skeleton | `variant`, `width`, `height`, `count` |
|
|
242
|
+
| `Empty` | Empty state | `title`, `description`, `icon` |
|
|
243
|
+
| `Result` | Result page | `status`, `title`, `description` |
|
|
244
|
+
|
|
245
|
+
#### Page-level
|
|
246
|
+
|
|
247
|
+
| Component | Description | Key props |
|
|
248
|
+
| ------------ | ----------- | ----------------------------------------------- |
|
|
249
|
+
| `PageHeader` | Page header | `title`, `description`; supports `actions` slot |
|
|
250
|
+
|
|
251
|
+
### Scenes (34)
|
|
252
|
+
|
|
253
|
+
#### App (7)
|
|
254
|
+
|
|
255
|
+
| Component | Description | Key props |
|
|
256
|
+
| ------------------ | ---------------------------------------------- | -------------------------------------------------------- |
|
|
257
|
+
| `FeedbackModal` | Feedback modal | `open`, `onsubmit` |
|
|
258
|
+
| `SettingsModal` | Settings modal | `open`, `onclose` |
|
|
259
|
+
| `SettingsLayout` | Settings layout (section navigation + content) | `title`, `description`, `sections`, `active`, `onselect` |
|
|
260
|
+
| `LanguageSwitch` | Language switcher | No props required |
|
|
261
|
+
| `ThemeColorPicker` | Theme color picker | `value`, `presets`, `onchange` |
|
|
262
|
+
| `ThemeSelector` | Full theme selector | No props required |
|
|
263
|
+
| `ThemeToggle` | Light/dark toggle | No props required |
|
|
264
|
+
|
|
265
|
+
#### IAM authentication (9)
|
|
266
|
+
|
|
267
|
+
| Component | Description | Key props |
|
|
268
|
+
| -------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
|
|
269
|
+
| `AuthShell` | Auth page layout (centered card / split columns) | `variant`, `title`, `subtitle`, `brandTitle`, `brandText`, `illustration`, `description`, `highlights` |
|
|
270
|
+
| `LoginForm` | Login form | `loading`, `errors`, `showRememberMe`, `onsubmit` |
|
|
271
|
+
| `RegisterForm` | Registration form | `loading`, `errors`, `fields`, `onsubmit` |
|
|
272
|
+
| `ForgotPasswordForm` | Forgot password | `mode`, `loading`, `errors`, `onsubmit` |
|
|
273
|
+
| `ResetPasswordForm` | Reset password | `loading`, `errors`, `showCode`, `onsubmit` |
|
|
274
|
+
| `ChangePasswordForm` | Change password | `loading`, `errors`, `requireOldPassword`, `onsubmit` |
|
|
275
|
+
| `PasswordInput` | Password input | `value`, `showToggle`, `showStrength`, `minLength` |
|
|
276
|
+
| `PermGuard` | Permission guard | `permissions`, `mode`, `fallback` |
|
|
277
|
+
| `UserProfile` | User profile | `user`, `editable`, `fields`, `avatarAccept`, `avatarHint`, `onsubmit` |
|
|
278
|
+
|
|
279
|
+
#### Storage (4)
|
|
280
|
+
|
|
281
|
+
| Component | Description | Key props |
|
|
282
|
+
| -------------- | ------------- | ------------------------------------------------------------ |
|
|
283
|
+
| `FileUpload` | File upload | `accept`, `maxSize`, `maxFiles`, `multiple`, `uploadHandler` |
|
|
284
|
+
| `ImageUpload` | Image upload | `value`, `accept`, `maxSize`, `aspectRatio`, `uploadHandler` |
|
|
285
|
+
| `AvatarUpload` | Avatar upload | `value`, `size`, `maxSize`, `fallback`, `uploadHandler` |
|
|
286
|
+
| `FileList` | File list | `files`, `layout`, `showDelete`, `showDownload` |
|
|
287
|
+
|
|
288
|
+
Upload components do not embed presigning, headers, or PUT/POST protocols. Applications call their service through `uploadHandler(file, { signal, onProgress })` and return `{ url?, response? }`. Image/avatar uploads require a displayable `url`. Without a handler, `FileUpload` only selects files, and image/avatar components generate local previews only.
|
|
289
|
+
|
|
290
|
+
#### Crypto display (3)
|
|
291
|
+
|
|
292
|
+
Input changes immediately clear `encryptedValue`; only successful results for current input are restored. Clearing, callback replacement, and unmounting invalidate old requests. Failures show built-in errors and leave ciphertext empty. Submit encrypted inputs only when current ciphertext is nonempty.
|
|
293
|
+
|
|
294
|
+
Callers must connect `EncryptedInput` to a real encryption service through `onencrypt`; `algorithm` describes that service's algorithm only. Without a callback, the component shows unavailability and generates no ciphertext, with no Base64 fallback.
|
|
295
|
+
|
|
296
|
+
| Component | Description | Key props |
|
|
297
|
+
| ------------------ | ----------------- | ------------------------------------------------------- |
|
|
298
|
+
| `EncryptedInput` | Encrypted input | `value`, `encryptedValue`, `algorithm`, `showEncrypted` |
|
|
299
|
+
| `HashDisplay` | Hash display | `value`, `algorithm`, `copyable`, `truncate` |
|
|
300
|
+
| `SignatureDisplay` | Signature display | `signature`, `publicKey`, `algorithm`, `verified` |
|
|
301
|
+
|
|
302
|
+
Defaults match `@h-ai/crypto`: `SM4` for encrypted inputs, `SM3` for hashes, and `SM2` for signatures.
|
|
303
|
+
|
|
304
|
+
#### AI documents and spreadsheets (4)
|
|
305
|
+
|
|
306
|
+
| Component | Description | Key props / capabilities |
|
|
307
|
+
| ------------------------ | ---------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
308
|
+
| `MarkdownRenderer` | Markdown renderer | `content`, `fontSize`, `allowHtmlTags`, `showCopyButton`, `enableHighlight` |
|
|
309
|
+
| `AiDocumentDownloadMenu` | Document download menu | `actions`, `ondownload` |
|
|
310
|
+
| `AiDocumentEditor` | AI document editor | `content`, `fontSize`, `allowHtmlTags`, `showRunButton`, `showCodePreviewToggle`, `allowUnsafeCodePreview` |
|
|
311
|
+
| `AiTableEditor` | AI spreadsheet editor | `columns`, `rows`, `editable`, `ondownload` |
|
|
312
|
+
|
|
313
|
+
> `AiDocumentEditor` permits only built-in Markdown preview by default. High-risk HTML / JS / CSS previews require explicit `allowUnsafeCodePreview` or controlled results from `oncoderun`.
|
|
314
|
+
>
|
|
315
|
+
> Mermaid works out of the box: ` ```mermaid ` blocks in documents (`sourceKind='document'`) render automatically in reading mode. Code mode (`sourceKind='code'` + `showCodePreviewToggle`) supports code/preview switching. Mermaid uses `securityLevel: 'strict'` and sanitized SVG without `allowUnsafeCodePreview`.
|
|
316
|
+
>
|
|
317
|
+
> `MarkdownRenderer` and `AiDocumentEditor` also support presentation options:
|
|
318
|
+
>
|
|
319
|
+
> - `fontSize` accepts a `number` in px or any CSS length such as `1.125rem`.
|
|
320
|
+
> - `allowHtmlTags` is off by default; when enabled, tags such as `<b>` / `<i>` / `<u>` / `<mark>` use a safe allowlist, with dangerous tags/attributes still sanitized.
|
|
321
|
+
|
|
322
|
+
```svelte
|
|
323
|
+
<MarkdownRenderer content={markdown} fontSize='1.125rem' allowHtmlTags />
|
|
324
|
+
|
|
325
|
+
<AiDocumentEditor
|
|
326
|
+
title='方案文档'
|
|
327
|
+
content={documentMarkdown}
|
|
328
|
+
fontSize={18}
|
|
329
|
+
allowHtmlTags
|
|
330
|
+
showToolbar
|
|
331
|
+
showOutline={false}
|
|
332
|
+
/>
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
#### Charts
|
|
336
|
+
|
|
337
|
+
`Chart type='line'` uses LayerChart's native line chart by default. For monitoring dashboards with segmented lines, points, nearest-point hover, and vertical reference lines, use `lineVariant='segmented-point'`:
|
|
338
|
+
|
|
339
|
+
```svelte
|
|
340
|
+
<Chart
|
|
341
|
+
type='line'
|
|
342
|
+
data={trendData}
|
|
343
|
+
x='label'
|
|
344
|
+
y={['rateLimit', 'circuitBreak']}
|
|
345
|
+
series={trendSeries}
|
|
346
|
+
lineVariant='segmented-point'
|
|
347
|
+
tooltipMode='nearest'
|
|
348
|
+
showCrosshair
|
|
349
|
+
legend
|
|
350
|
+
grid
|
|
351
|
+
/>
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
#### CRUD scenes (5)
|
|
355
|
+
|
|
356
|
+
| Component | Description | Key props / capabilities |
|
|
357
|
+
| ------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
358
|
+
| `CrudPage` | CRUD page | `crud`, `data`, `permissions`, `form`, `pagination`, `density`, `detailColumns`, `showHeader`, `toolbarStyle`, `toolbarLeading`, `toolbarActions`, `card`, `tableCell`, `onrowclick`, `sortableColumns`, `nav`, `loading`, `loaded`, `error`, `onRetry`, `keepPreviousData` |
|
|
359
|
+
| `CrudFilterBar` | Filter toolbar (search + typed filters + reset) | `filterFields`, `searchValue`, `onsearch`, `onfilterchange`, `onreset` |
|
|
360
|
+
| `CrudDetailPanel` | Details panel (drawer/modal) | `open`, `item`, `fields`, `variant`, `drawerWidth`, `modalSize` |
|
|
361
|
+
| `CrudEditPanel` | Edit panel (drawer/modal) | `open`, `fields`, `variant`, `drawerWidth`, `modalSize`, `onsubmit` |
|
|
362
|
+
| `CrudDeleteConfirm` | Delete confirmation | `open`, `loading`, `onconfirm` |
|
|
363
|
+
|
|
364
|
+
> `CrudPage` defaults to the `TableToolbar` icon/popover toolbar; use `toolbarStyle='filter-bar'` for a flat filter bar. `detailColumns={['title']}` makes specified columns open details. `toolbarLeading` injects left-side titles/descriptions; `showHeader={false}` avoids duplicate titles. `toolbarActions` adds right-side actions, `card` replaces rows with cards, `tableCell` renders interactive cells by row/column key (such as Tooltip), and `sortableColumns` controls server sorting fields. Filters support `select`, `boolean`, `number`, `date`, `date-range`, and `text`.
|
|
365
|
+
>
|
|
366
|
+
> The page fills available parent height. Tables fill remaining space even without data; only the data area scrolls, with headers fixed at its top and pagination fixed at the list card bottom.
|
|
367
|
+
>
|
|
368
|
+
> Columns first shrink to 7rem and truncate long text; horizontal scrolling appears only if total width still exceeds the container. The action column stays fixed on the right.
|
|
369
|
+
|
|
370
|
+
#### Error pages (1)
|
|
371
|
+
|
|
372
|
+
| Component | Description | Key props / capabilities |
|
|
373
|
+
| ----------- | --------------------------------------------------- | ----------------------------------------------------------------------- |
|
|
374
|
+
| `ErrorPage` | Generic error page with 401/403/404/500/503 presets | `status`, `code`, `title`, `description`, `homeUrl`, `onhome`, `onback` |
|
|
375
|
+
|
|
376
|
+
> `CrudPage` uses `form` to configure create/edit presentation:
|
|
377
|
+
>
|
|
378
|
+
> - `form.variant`: `'drawer'` (default) or `'modal'`.
|
|
379
|
+
> - Drawer: `form.drawerSize` selects a preset; `form.drawerWidth` accepts any CSS width and takes precedence. The left edge is resizable by default and remembered per resource; disable with `form.drawerResizable=false`.
|
|
380
|
+
> - Modal: `form.modalSize` selects a preset; `form.modalWidth` / `form.modalHeight` accept CSS sizes.
|
|
381
|
+
>
|
|
382
|
+
> `pagination` configures the always-visible pagination bar. `showSizeChanger`, `pageSizeOptions`, `showJumper`, and `showTotal` are on by default. For compact views, disable page text with `showPageInfo` and first/last buttons with `showFirstLast`.
|
|
383
|
+
>
|
|
384
|
+
> `density` is `'normal'` (default) or `'compact'`. It affects list rows/actions, pagination, and create/edit/detail forms, not headers or filters.
|
|
385
|
+
>
|
|
386
|
+
> Controlled loading: `loading` / `loaded` / `error` / `onRetry` / `keepPreviousData`. Empty state appears only when `loaded && total === 0`, avoiding false empty results before the first response. Initial queries show loading; failures without usable data show errors/retry (`onRetry`). `keepPreviousData` (default true) retains old data during refresh/failure.
|
|
387
|
+
>
|
|
388
|
+
> Pure clients without SvelteKit SSR can use framework `defineCrud` + `createCrudController` to manage `data` and `nav`, with latest-wins (seq + AbortSignal), old-data retention on failure, lazy loading, and concurrent deduplication:
|
|
389
|
+
>
|
|
390
|
+
> ```svelte
|
|
391
|
+
> <script>
|
|
392
|
+
> import { CrudPage, createCrudController, defineCrud } from '@h-ai/ui'
|
|
393
|
+
> const crud = defineCrud({ name: 'user', label: '用户', fields: [/* ... */], api: { list: params => listUsers(params) } })
|
|
394
|
+
> const c = createCrudController(crud) // 惰性页签传 { autoLoad: false },首次激活时 c.load()
|
|
395
|
+
> </script>
|
|
396
|
+
> <CrudPage {crud} data={c.data} nav={c.nav} loading={c.loading} loaded={c.loaded} error={c.error} onRetry={c.reload} />
|
|
397
|
+
> ```
|
|
398
|
+
>
|
|
399
|
+
> `Tabs` supports lazy content. With `lazy`, never-activated panes are not rendered (`children` receives `isActivated(key)`); `onactivate(key)` runs once on first activation. Combine with `createCrudController({ autoLoad: false })` to load only when first visible.
|
|
400
|
+
|
|
401
|
+
## Examples
|
|
402
|
+
|
|
403
|
+
### Toast notifications
|
|
404
|
+
|
|
405
|
+
```svelte
|
|
406
|
+
<script>
|
|
407
|
+
import { toast, ToastContainer } from '@h-ai/ui'
|
|
408
|
+
|
|
409
|
+
function notify() {
|
|
410
|
+
toast.success('操作成功')
|
|
411
|
+
toast.error('操作失败')
|
|
412
|
+
toast.warning('请注意')
|
|
413
|
+
toast.info('提示信息', 5000) // 自定义持续时间
|
|
414
|
+
}
|
|
415
|
+
</script>
|
|
416
|
+
|
|
417
|
+
<button onclick={notify}>通知</button>
|
|
418
|
+
<ToastContainer />
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
> Built-in `notifySuccess` / `notifyError` / `notifyInfo` / `notifyWarning` remove the need for application toast wrappers. `notifyError(err, fallback?)` accepts `Error` or strings with a fallback message. Success lasts 3s and errors 5s by default.
|
|
422
|
+
|
|
423
|
+
### Message feedback
|
|
424
|
+
|
|
425
|
+
```svelte
|
|
426
|
+
<script>
|
|
427
|
+
import { message, MessageContainer } from '@h-ai/ui'
|
|
428
|
+
|
|
429
|
+
function showMessage() {
|
|
430
|
+
message.success('操作成功')
|
|
431
|
+
message.error('操作失败')
|
|
432
|
+
message.warning('请注意')
|
|
433
|
+
message.info('提示信息', 5000)
|
|
434
|
+
}
|
|
435
|
+
</script>
|
|
436
|
+
|
|
437
|
+
<button onclick={showMessage}>消息</button>
|
|
438
|
+
<MessageContainer />
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Typical CRUD page
|
|
442
|
+
|
|
443
|
+
```svelte
|
|
444
|
+
<script>
|
|
445
|
+
import {
|
|
446
|
+
PageHeader, Card, DataTable, Button,
|
|
447
|
+
Modal, Input, Select, toast, ToastContainer,
|
|
448
|
+
} from '@h-ai/ui'
|
|
449
|
+
|
|
450
|
+
let items = $state([])
|
|
451
|
+
let showModal = $state(false)
|
|
452
|
+
let loading = $state(false)
|
|
453
|
+
let formData = $state({ name: '', type: '' })
|
|
454
|
+
|
|
455
|
+
const columns = [
|
|
456
|
+
{ key: 'name', label: '名称' },
|
|
457
|
+
{ key: 'type', label: '类型' },
|
|
458
|
+
{ key: 'createdAt', label: '创建时间' },
|
|
459
|
+
]
|
|
460
|
+
|
|
461
|
+
async function handleCreate() {
|
|
462
|
+
loading = true
|
|
463
|
+
try {
|
|
464
|
+
await fetch('/api/items', { method: 'POST', body: JSON.stringify(formData) })
|
|
465
|
+
showModal = false
|
|
466
|
+
toast.success('创建成功')
|
|
467
|
+
} finally {
|
|
468
|
+
loading = false
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
</script>
|
|
472
|
+
|
|
473
|
+
<PageHeader title="项目管理" description="管理所有项目">
|
|
474
|
+
{#snippet actions()}
|
|
475
|
+
<Button onclick={() => showModal = true}>新建</Button>
|
|
476
|
+
{/snippet}
|
|
477
|
+
</PageHeader>
|
|
478
|
+
|
|
479
|
+
<Card>
|
|
480
|
+
<DataTable data={items} {columns} keyField="id" {loading}>
|
|
481
|
+
{#snippet actions(item)}
|
|
482
|
+
<Button size="xs">编辑</Button>
|
|
483
|
+
<Button size="xs" variant="error">删除</Button>
|
|
484
|
+
{/snippet}
|
|
485
|
+
</DataTable>
|
|
486
|
+
</Card>
|
|
487
|
+
|
|
488
|
+
<Modal bind:open={showModal} title="新建项目">
|
|
489
|
+
<form onsubmit={(e) => { e.preventDefault(); handleCreate() }} class="space-y-4">
|
|
490
|
+
<Input placeholder="名称" bind:value={formData.name} required />
|
|
491
|
+
<Select
|
|
492
|
+
placeholder="选择类型"
|
|
493
|
+
bind:value={formData.type}
|
|
494
|
+
options={[
|
|
495
|
+
{ value: 'a', label: '类型 A' },
|
|
496
|
+
{ value: 'b', label: '类型 B' },
|
|
497
|
+
]}
|
|
498
|
+
/>
|
|
499
|
+
</form>
|
|
500
|
+
{#snippet footer()}
|
|
501
|
+
<Button variant="ghost" onclick={() => showModal = false}>取消</Button>
|
|
502
|
+
<Button {loading} onclick={handleCreate}>创建</Button>
|
|
503
|
+
{/snippet}
|
|
504
|
+
</Modal>
|
|
505
|
+
|
|
506
|
+
<ToastContainer />
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
`Modal` retains the top-right close button without a `title` / `header`, but does not render a full header row. Use `radius="1.4rem"` for rounded corners and `bodyOverflow="visible"` when internal dropdowns/overlays need to extend outside.
|
|
510
|
+
|
|
511
|
+
### Declarative CRUD page (CrudPage)
|
|
512
|
+
|
|
513
|
+
```svelte
|
|
514
|
+
<script lang='ts'>
|
|
515
|
+
import { createSvelteKitNavAdapter } from '@h-ai/kit/client'
|
|
516
|
+
import { CrudPage } from '@h-ai/ui'
|
|
517
|
+
|
|
518
|
+
let { data } = $props()
|
|
519
|
+
const nav = createSvelteKitNavAdapter()
|
|
520
|
+
</script>
|
|
521
|
+
|
|
522
|
+
<CrudPage
|
|
523
|
+
crud={roleCrud}
|
|
524
|
+
{data}
|
|
525
|
+
permissions={{ create: true, update: true, delete: true }}
|
|
526
|
+
form={{ variant: 'modal', modalSize: 'lg' }}
|
|
527
|
+
pagination={{ showSizeChanger: true, showJumper: true, pageSizeOptions: [10, 20, 50] }}
|
|
528
|
+
density='compact'
|
|
529
|
+
detailColumns={['name']}
|
|
530
|
+
sortableColumns={[{ key: 'createdAt', label: '创建时间' }]}
|
|
531
|
+
{nav}
|
|
532
|
+
/>
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
> Use `form={{ variant: 'drawer', drawerWidth: '40rem' }}` for drawers. User resizing overrides the initial width and is remembered per resource.
|
|
536
|
+
|
|
537
|
+
### Login page
|
|
538
|
+
|
|
539
|
+
```svelte
|
|
540
|
+
<script>
|
|
541
|
+
import { LoginForm } from '@h-ai/ui'
|
|
542
|
+
|
|
543
|
+
let loading = $state(false)
|
|
544
|
+
let errors = $state({})
|
|
545
|
+
|
|
546
|
+
async function handleLogin(data) {
|
|
547
|
+
loading = true
|
|
548
|
+
errors = {}
|
|
549
|
+
try {
|
|
550
|
+
await fetch('/api/login', { method: 'POST', body: JSON.stringify(data) })
|
|
551
|
+
} catch {
|
|
552
|
+
errors = { general: '登录失败,请检查用户名和密码' }
|
|
553
|
+
} finally {
|
|
554
|
+
loading = false
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
</script>
|
|
558
|
+
|
|
559
|
+
<!-- 场景组件内置 i18n,无需传入翻译 props -->
|
|
560
|
+
<LoginForm {loading} {errors} onsubmit={handleLogin} showRegisterLink />
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### Controlled PasswordInput
|
|
564
|
+
|
|
565
|
+
```svelte
|
|
566
|
+
<script>
|
|
567
|
+
import { PasswordInput } from '@h-ai/ui'
|
|
568
|
+
|
|
569
|
+
let password = $state('')
|
|
570
|
+
</script>
|
|
571
|
+
|
|
572
|
+
<PasswordInput
|
|
573
|
+
value={password}
|
|
574
|
+
oninput={(e) => { password = e.currentTarget.value }}
|
|
575
|
+
placeholder="请输入密码"
|
|
576
|
+
showStrength
|
|
577
|
+
/>
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
## Style dependencies
|
|
581
|
+
|
|
582
|
+
Components use TailwindCSS v4 + DaisyUI. Configure application `app.css` as follows:
|
|
583
|
+
|
|
584
|
+
```css
|
|
585
|
+
/* 必须 */
|
|
586
|
+
@import 'tailwindcss';
|
|
587
|
+
@import '@h-ai/ui/styles/global.css'; /* 基础重置、滚动条、焦点样式 */
|
|
588
|
+
@import '@h-ai/ui/styles/theme.css'; /* Tailwind v4 @theme Token(品牌色/阴影/动效) */
|
|
589
|
+
@source "../node_modules/@h-ai/ui/dist/**/*.{svelte,js,ts}";
|
|
590
|
+
@source "../../../node_modules/@h-ai/ui/dist/**/*.{svelte,js,ts}";
|
|
591
|
+
|
|
592
|
+
/* 移动端项目追加(可选) */
|
|
593
|
+
@import '@h-ai/ui/styles/design-tokens.css'; /* CSS 自定义属性 */
|
|
594
|
+
@import '@h-ai/ui/styles/mobile.css'; /* 安全区域/触摸优化 */
|
|
595
|
+
|
|
596
|
+
/* DaisyUI 主题 */
|
|
597
|
+
@plugin "daisyui" {
|
|
598
|
+
themes: light --default, dark --prefersdark, cupcake, emerald, corporate, nord, dracula, night;
|
|
599
|
+
}
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
- `global.css`: base HTML reset, scrollbars, and form focus rings.
|
|
603
|
+
- `theme.css`: Tailwind v4 `@theme` with brand colors, shadows, animation curves, and font features.
|
|
604
|
+
- `design-tokens.css`: spacing/radius/z-index/transition custom properties; recommended for mobile.
|
|
605
|
+
- `mobile.css`: safe-area padding, momentum scrolling, keyboard adaptation, and mobile shells (`hai-mobile-viewport` / `hai-mobile-shell` / `hai-mobile-main`).
|
|
606
|
+
|
|
607
|
+
On mobile pages, match `BottomNav` width to the page shell to avoid spanning the entire desktop preview viewport:
|
|
608
|
+
|
|
609
|
+
```svelte
|
|
610
|
+
<div class="hai-mobile-viewport">
|
|
611
|
+
<div class="hai-mobile-shell max-w-lg">
|
|
612
|
+
<AppBar title="Home" fixed={false} safeArea />
|
|
613
|
+
<main class="hai-mobile-main">...</main>
|
|
614
|
+
<BottomNav items={items} active="home" centered maxWidth="lg" safeArea />
|
|
615
|
+
</div>
|
|
616
|
+
</div>
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
### Icons
|
|
620
|
+
|
|
621
|
+
Components use Iconify (Tabler Icons):
|
|
622
|
+
|
|
623
|
+
```bash
|
|
624
|
+
npm install -D @iconify/tailwind4 @iconify-json/tabler
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
```css
|
|
628
|
+
/* app.css 中追加 */
|
|
629
|
+
@plugin "@iconify/tailwind4" {
|
|
630
|
+
prefixes: tabler;
|
|
631
|
+
}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### Theme switching
|
|
635
|
+
|
|
636
|
+
Manage 15 curated DaisyUI themes through built-in utilities:
|
|
637
|
+
|
|
638
|
+
```ts
|
|
639
|
+
import {
|
|
640
|
+
applyTheme, // 应用主题(自动持久化到 localStorage)
|
|
641
|
+
createThemeBootstrapScript, // 生成可注入 app.html 的首屏主题恢复脚本
|
|
642
|
+
getCurrentTheme, // 获取当前主题
|
|
643
|
+
getSavedTheme, // Read saved appearance, with an optional application default
|
|
644
|
+
getThemeInitScript, // 返回可注入到 HTML shell 的防闪烁脚本文本
|
|
645
|
+
isDarkTheme, // 检查是否暗色主题
|
|
646
|
+
normalizeHexColor, // 归一化 #RGB / #RRGGBB 主题色
|
|
647
|
+
normalizeThemeId, // 校验主题 ID 并自动回退
|
|
648
|
+
THEME_GROUPS, // 按亮色/暗色分组
|
|
649
|
+
THEMES, // ThemeInfo[] — 15 个精选主题元数据
|
|
650
|
+
} from '@h-ai/ui/theme'
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
Use `getThemeInitScript()` for simple cases. For custom storage keys, theme color CSS variables, or language preferences, generate `createThemeBootstrapScript()` on the server and inject it into `app.html`.
|
|
654
|
+
|
|
655
|
+
Place a placeholder in SvelteKit `app.html`:
|
|
656
|
+
|
|
657
|
+
```html
|
|
658
|
+
<head>
|
|
659
|
+
<script>%theme_bootstrap%</script>
|
|
660
|
+
</head>
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
Then inject it from `hooks.server.ts`:
|
|
664
|
+
|
|
665
|
+
```ts
|
|
666
|
+
import { createThemeBootstrapScript, DEFAULT_THEME_COLOR_CSS_VAR } from '@h-ai/ui/theme'
|
|
667
|
+
|
|
668
|
+
const themeBootstrapScript = createThemeBootstrapScript({
|
|
669
|
+
storageKey: 'hai-demo-preferences',
|
|
670
|
+
legacyThemeStorageKey: 'hai-demo-theme',
|
|
671
|
+
defaultThemeColor: '#5765f0',
|
|
672
|
+
colorCssVar: DEFAULT_THEME_COLOR_CSS_VAR,
|
|
673
|
+
})
|
|
674
|
+
|
|
675
|
+
transformPageChunk: ({ html }) => html.replace('%theme_bootstrap%', themeBootstrapScript)
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
## Internationalization (i18n)
|
|
679
|
+
|
|
680
|
+
@h-ai/ui uses **built-in component translations**:
|
|
681
|
+
|
|
682
|
+
- Scene components (`scenes/`) include Chinese/English translations (zh-CN / en-US) out of the box.
|
|
683
|
+
- Components respond automatically to global locale changes, synchronized through `@h-ai/core`.
|
|
684
|
+
- Applications handle **page text** only; @h-ai/ui manages internal component text.
|
|
685
|
+
- Override specific text with props such as `submitText` or `labels`.
|
|
686
|
+
|
|
687
|
+
### createLocaleStore
|
|
688
|
+
|
|
689
|
+
Client locale state management, automatically synchronized with the `@h-ai/core` global locale manager:
|
|
690
|
+
|
|
691
|
+
```svelte
|
|
692
|
+
<script>
|
|
693
|
+
import { createLocaleStore, setGlobalLocale } from '@h-ai/ui'
|
|
694
|
+
import { setLocale } from '$lib/paraglide/runtime'
|
|
695
|
+
|
|
696
|
+
const localeStore = createLocaleStore()
|
|
697
|
+
|
|
698
|
+
function changeLocale(code) {
|
|
699
|
+
localeStore.set(code) // 更新 UI store + 同步到 @h-ai/core
|
|
700
|
+
setLocale(code) // 同步到 Paraglide(应用层)
|
|
701
|
+
}
|
|
702
|
+
</script>
|
|
703
|
+
|
|
704
|
+
<select value={localeStore.current} onchange={(e) => changeLocale(e.currentTarget.value)}>
|
|
705
|
+
{#each localeStore.supported as l}
|
|
706
|
+
<option value={l.code}>{l.label}</option>
|
|
707
|
+
{/each}
|
|
708
|
+
</select>
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
### Exported i18n utilities
|
|
712
|
+
|
|
713
|
+
```ts
|
|
714
|
+
import {
|
|
715
|
+
createLocaleStore, // Svelte 响应式 locale store
|
|
716
|
+
DEFAULT_LOCALE, // 默认 locale: 'zh-CN'
|
|
717
|
+
DEFAULT_LOCALES, // 支持的 locale 列表
|
|
718
|
+
detectBrowserLocale, // 检测浏览器语言
|
|
719
|
+
getGlobalLocale, // 获取当前全局 locale
|
|
720
|
+
interpolate, // 字符串插值(如 "Hello {name}")
|
|
721
|
+
isLocaleSupported, // 检查 locale 是否支持
|
|
722
|
+
resolveLocale, // 解析 locale(支持回退)
|
|
723
|
+
setGlobalLocale, // 设置全局 locale(同步 @h-ai/core)
|
|
724
|
+
} from '@h-ai/ui'
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
## Other exports
|
|
728
|
+
|
|
729
|
+
```ts
|
|
730
|
+
// 类型
|
|
731
|
+
import type { ButtonProps, InputProps, ModalProps } from '@h-ai/ui'
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
```ts
|
|
735
|
+
// 样式工具
|
|
736
|
+
import { cn, generateId, getSizeClass, getVariantClass } from '@h-ai/ui'
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
```ts
|
|
740
|
+
// Toast 单例
|
|
741
|
+
import { toast } from '@h-ai/ui'
|
|
742
|
+
```
|