@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.ja.md ADDED
@@ -0,0 +1,742 @@
1
+ # @h-ai/ui
2
+
3
+ [简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
4
+
5
+ [フレームワークのホーム](../../README.ja.md) · 関連モジュール:[@h-ai/core](../core/README.ja.md)
6
+
7
+ > Svelte 5 Runes のクロスプラットフォーム UI ライブラリーです。DaisyUI v5 + Tailwind CSS v4 のスタイル、Bits UI v2 のヘッドレス UI、内蔵 i18n(zh-CN / en-US)、15 種類の厳選テーマを提供します。
8
+
9
+ ## インストール
10
+
11
+ ```bash
12
+ npm install @h-ai/ui
13
+ ```
14
+
15
+ `@h-ai/core` に依存し、自動的にインストールされます。
16
+
17
+ ## クイックスタート
18
+
19
+ ### 1. 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. 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. 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` で TailwindCSS に `@h-ai/ui` の class を走査させます。ない場合はスタイルが欠落します。monorepo 内のアプリでは依存がルート `node_modules` に置かれる場合があるため、両方のパスを残すことを推奨します。
78
+
79
+ ### 4. コンポーネントの使用
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
+ 自動 import を有効にすると、個別の import なしでテンプレートから使えます:
93
+
94
+ ```svelte
95
+ <!-- 无需 import { Button } from '@h-ai/ui' -->
96
+ <Button variant="primary">提交</Button>
97
+ ```
98
+
99
+ > `toast`・型 import・DOM の `Range` と衝突する `Range` を除き、公開 Svelte コンポーネントは `@h-ai/ui/auto-import` で自動注入できます。
100
+
101
+ 管理画面など一部のコンポーネントを使うページは、公開コンポーネントディレクトリーの入口から直接 import できます。テーマツールは軽量な `@h-ai/ui/theme` から読み込み、テーマの利用だけでルート入口の AI・グラフコンポーネントを読み込まないようにします。インストール済みパッケージの exports を確認し、内部 `src/`・`dist/` は import しないでください。
102
+
103
+ 自動 import はセミコロンなし、複数行、別名の import を認識し、既存のローカル名を重複注入しません。型のみの import は実行時コンポーネントの代わりにはなりません。
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-\* 属性の転送
112
+
113
+ 公開 Svelte コンポーネントは `data-*` をルート・主操作要素へ転送し、テスト・計測・自動化に使えます。通常の未知属性は転送しません。
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
+ `data-testid` のためだけにボタン・入力・ポップアップをラップする必要はありません。
122
+
123
+ ## コンポーネント構成
124
+
125
+ 3 層で構成します(primitives → compounds → scenes):
126
+
127
+ ```
128
+ components/
129
+ ├── primitives/ # 分割できない基本 UI 部品
130
+ ├── compounds/ # 基本部品 + Bits UI の組み合わせ
131
+ └── scenes/ # 業務用途の完全な UI フロー
132
+ ├── ai/ # AI 文書 / 表計算 / Markdown プレビュー
133
+ ├── app/ # 設定 / フィードバック / テーマ / 言語
134
+ ├── crud/ # CRUD ページ / ドロワー / 絞り込み / 削除確認
135
+ ├── iam/ # 認証
136
+ ├── storage/ # ストレージ管理
137
+ └── crypto/ # 暗号表示
138
+ ```
139
+
140
+ ## コンポーネント一覧
141
+
142
+ ### Primitives:基本コンポーネント(20)
143
+
144
+ | コンポーネント | 説明 | 主なプロパティ |
145
+ | ---------------- | ---------------------- | ------------------------------------------------------------------------------ |
146
+ | `Button` | ボタン | `variant`, `size`, `loading`, `disabled`, `outline`, `circle` |
147
+ | `IconButton` | アイコンボタン | `icon: trusted SVG string \| Snippet`, `variant`, `size`, `tooltip`, `loading` |
148
+ | `BareButton` | スタイルなしボタン | `class`, `ariaLabel`, `role`, `tabindex` |
149
+ | `Input` | 入力欄 | `type`, `value`, `size`, `error`, `validationMessage` |
150
+ | `BareInput` | スタイルなし入力欄 | `type`, `class`, `accept`, `multiple` |
151
+ | `Textarea` | テキストエリア | `value`, `rows`, `size`, `autoResize`, `error` |
152
+ | `Select` | 選択リスト | `options`, `value`, `placeholder`, `size`, `clearable`, `filterable` |
153
+ | `Checkbox` | チェックボックス | `checked`, `label`, `size`, `indeterminate` |
154
+ | `Switch` | スイッチ | `checked`, `label`, `size` |
155
+ | `Radio` | ラジオグループ | `options`, `value`, `direction`, `size` |
156
+ | `ToggleCheckbox` | ネイティブスイッチ入力 | `checked`, `name`, `onchange` |
157
+ | `ToggleInput` | ネイティブトグル入力 | `checked`, `name` |
158
+ | `ToggleRadio` | ネイティブラジオ入力 | `checked`, `name`, `onchange` |
159
+ | `Range` | スライダー | `value`, `min`, `max`, `step` |
160
+ | `Rating` | 評価 | `value`, `max` |
161
+ | `Badge` | バッジ | `variant`, `size`, `outline` |
162
+ | `Avatar` | アバター | `src`, `name`, `size`, `shape`, `ring` |
163
+ | `Tag` | タグ | `text`, `variant`, `size`, `closable` |
164
+ | `Spinner` | 読み込み表示 | `size`, `variant` |
165
+ | `Progress` | プログレスバー | `value`, `max`, `variant`, `striped`, `animated` |
166
+
167
+ `BareInput` の `type="file"` では `onchange` の `event.currentTarget.files` を読み取り、ファイルパスの `value` はバインドしません。他の入力型では `bind:value` を使用できます。
168
+
169
+ ### Compounds:複合コンポーネント(36)
170
+
171
+ #### フォーム
172
+
173
+ | コンポーネント | 説明 | 主なプロパティ |
174
+ | -------------- | ------------------ | -------------------------------------------- |
175
+ | `Form` | フォームコンテナー | `loading`, `disabled`, `onsubmit` |
176
+ | `FormField` | フォーム項目 | `label`, `error`, `hint`, `required` |
177
+ | `TagInput` | タグ入力 | `tags`, `maxTags`, `allowDuplicates`, `size` |
178
+
179
+ #### フィードバック
180
+
181
+ | コンポーネント | 説明 | 主なプロパティ |
182
+ | ---------------- | -------------- | ---------------------------------------- |
183
+ | `Alert` | アラート | `variant`, `title`, `dismissible` |
184
+ | `ToastContainer` | 通知コンテナー | 全体に配置し、`toast` シングルトンと併用 |
185
+
186
+ #### オーバーレイ
187
+
188
+ | コンポーネント | 説明 | 主なプロパティ |
189
+ | -------------- | -------------- | ---------------------------------------------------------------------------- |
190
+ | `Modal` | モーダル(ダイアログ属性・フォーカス制御と復元) | `open`, `title`, `size`, `radius`, `bodyOverflow`, `showClose` |
191
+ | `Drawer` | ドロワー | `open`, `position`, `title`, `size`, `width`, `resizable`, `widthStorageKey` |
192
+ | `Confirm` | 確認ダイアログ | `open`, `title`, `message`, `variant`, `onconfirm` |
193
+ | `Popover` | ポップオーバー | `open`, `position`, `trigger`, `offset`, `portal` |
194
+
195
+ #### データ表示
196
+
197
+ | コンポーネント | 説明 | 主なプロパティ |
198
+ | -------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
199
+ | `Card` | カードコンテナー | `title`, `bordered`, `shadow`, `padding` |
200
+ | `DataTable` | データテーブル | `data`, `columns`, `keyField`, `loading`, `sortKey`, `sortDir`, `onsort`、`cell` スロット、`onrowclick`(列の `sortable` で並び替え) |
201
+ | `Accordion` | アコーディオン | `items: AccordionItem[]` |
202
+ | `Timeline` | タイムライン | `items: TimelineItem[]` |
203
+
204
+ #### Bits UI によるヘッドレス UI
205
+
206
+ | コンポーネント | 説明 | 主なプロパティ |
207
+ | -------------- | -------------------------- | ----------------------------------------------------------- |
208
+ | `Combobox` | 検索付き選択(単一・複数) | `options`, `value`, `multiple`, `placeholder`, `size` |
209
+ | `Calendar` | 独立カレンダー | `value: DateValue`, `minValue`, `maxValue` |
210
+ | `DatePicker` | ポップアップ付き日付入力 | `value: DateValue`, `minValue`, `maxValue`, `error`, `size` |
211
+
212
+ > 日付値には `@internationalized/date` の `DateValue` / `CalendarDate` を使います。
213
+
214
+ #### モバイル・拡張操作
215
+
216
+ | コンポーネント | 説明 | 主なプロパティ |
217
+ | ---------------- | ---------------------- | ------------------------------------------------------ |
218
+ | `ActionSheet` | 下部アクションシート | `open`, `title`, `items`, `cancelText`, `onselect` |
219
+ | `AppBar` | 上部ナビゲーション | `title`, `backHref`, `onback`, `fixed`, `safeArea` |
220
+ | `BottomNav` | 下部ナビゲーション | `items`, `active`, `centered`, `maxWidth` |
221
+ | `InfiniteScroll` | 無限スクロール | `loading`, `finished`, `threshold`, `onload` |
222
+ | `PullRefresh` | プル更新 | `refreshing`, `threshold`, `onrefresh` |
223
+ | `SafeArea` | セーフエリアコンテナー | `top`, `bottom`, `left`, `right` |
224
+ | `SwipeCell` | スワイプ操作 | `leftActions`, `rightActions`, `threshold`, `onaction` |
225
+
226
+ #### ナビゲーション
227
+
228
+ | コンポーネント | 説明 | 主なプロパティ |
229
+ | -------------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------- |
230
+ | `Tabs` | タブ | `items`, `active`, `type`, `size`, `lazy`, `onactivate` |
231
+ | `Pagination` | ページ分割(共通 table スタイル) | `page`, `total`, `pageSize`, `showTotal`, `showJumper`, `showSizeChanger`, `showPageInfo`, `showFirstLast` |
232
+ | `Breadcrumb` | パンくず | `items`, `separator` |
233
+ | `Steps` | ステップ | `items`, `current`, `direction`, `clickable` |
234
+ | `Dropdown` | ドロップダウンメニュー | `items`, `trigger`, `triggerClass`, `position` |
235
+ | `Tooltip` | ツールチップ | `content`, `position`, `delay` |
236
+
237
+ #### 状態表示
238
+
239
+ | コンポーネント | 説明 | 主なプロパティ |
240
+ | -------------- | ---------- | ------------------------------------- |
241
+ | `Skeleton` | スケルトン | `variant`, `width`, `height`, `count` |
242
+ | `Empty` | 空状態 | `title`, `description`, `icon` |
243
+ | `Result` | 結果ページ | `status`, `title`, `description` |
244
+
245
+ #### ページ単位
246
+
247
+ | コンポーネント | 説明 | 主なプロパティ |
248
+ | -------------- | -------------- | ---------------------------------------------- |
249
+ | `PageHeader` | ページヘッダー | `title`, `description`、`actions` スロット対応 |
250
+
251
+ ### Scenes:用途別コンポーネント(34)
252
+
253
+ #### App:アプリ全体(7)
254
+
255
+ | コンポーネント | 説明 | 主なプロパティ |
256
+ | ------------------ | ------------------------------------------- | -------------------------------------------------------- |
257
+ | `FeedbackModal` | フィードバックモーダル | `open`, `onsubmit` |
258
+ | `SettingsModal` | 設定モーダル | `open`, `onclose` |
259
+ | `SettingsLayout` | 設定レイアウト(区分ナビゲーション + 本文) | `title`, `description`, `sections`, `active`, `onselect` |
260
+ | `LanguageSwitch` | 言語切り替え | プロパティ不要 |
261
+ | `ThemeColorPicker` | テーマカラー選択 | `value`, `presets`, `onchange` |
262
+ | `ThemeSelector` | テーマ選択パネル | プロパティ不要 |
263
+ | `ThemeToggle` | ライト・ダーク切り替え | プロパティ不要 |
264
+
265
+ #### IAM:認証(9)
266
+
267
+ | コンポーネント | 説明 | 主なプロパティ |
268
+ | -------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------ |
269
+ | `AuthShell` | 認証ページ配置(中央カード / 左右分割) | `variant`, `title`, `subtitle`, `brandTitle`, `brandText`, `illustration`, `description`, `highlights` |
270
+ | `LoginForm` | ログインフォーム | `loading`, `errors`, `showRememberMe`, `onsubmit` |
271
+ | `RegisterForm` | 登録フォーム | `loading`, `errors`, `fields`, `onsubmit` |
272
+ | `ForgotPasswordForm` | パスワード忘れ | `mode`, `loading`, `errors`, `onsubmit` |
273
+ | `ResetPasswordForm` | パスワードリセット | `loading`, `errors`, `showCode`, `onsubmit` |
274
+ | `ChangePasswordForm` | パスワード変更 | `loading`, `errors`, `requireOldPassword`, `onsubmit` |
275
+ | `PasswordInput` | パスワード入力 | `value`, `showToggle`, `showStrength`, `minLength` |
276
+ | `PermGuard` | 権限ガード | `permissions`, `mode`, `fallback` |
277
+ | `UserProfile` | ユーザープロフィール | `user`, `editable`, `fields`, `avatarAccept`, `avatarHint`, `onsubmit` |
278
+
279
+ #### Storage:ストレージ(4)
280
+
281
+ | コンポーネント | 説明 | 主なプロパティ |
282
+ | -------------- | -------------------- | ------------------------------------------------------------ |
283
+ | `FileUpload` | ファイルアップロード | `accept`, `maxSize`, `maxFiles`, `multiple`, `uploadHandler` |
284
+ | `ImageUpload` | 画像アップロード | `value`, `accept`, `maxSize`, `aspectRatio`, `uploadHandler` |
285
+ | `AvatarUpload` | アバターアップロード | `value`, `size`, `maxSize`, `fallback`, `uploadHandler` |
286
+ | `FileList` | ファイル一覧 | `files`, `layout`, `showDelete`, `showDownload` |
287
+
288
+ アップロード部品は presign・ヘッダー・PUT/POST を内蔵しません。アプリは `uploadHandler(file, { signal, onProgress })` からサービスを呼び、`{ url?, response? }` を返します。画像・アバターには表示可能な `url` が必要です。handler がなければ `FileUpload` は選択のみ、画像・アバターはローカルのプレビューのみを作成します。
289
+
290
+ #### Crypto:暗号表示(3)
291
+
292
+ 入力変更で `encryptedValue` を即座に消去し、現在の入力の成功結果のみを反映します。消去・コールバック置換・破棄で旧リクエストを無効化します。失敗時は組み込みエラーを表示し暗号文は空です。暗号化対象の送信は現在の暗号文が空でない場合に限ってください。
293
+
294
+ `EncryptedInput` は `onencrypt` で実際の暗号サービスに接続する必要があり、`algorithm` はその方式の説明のみです。未設定なら利用不可を示し、暗号文を生成せず、Base64 などへの代替もありません。
295
+
296
+ | コンポーネント | 説明 | 主なプロパティ |
297
+ | ------------------ | ------------ | ------------------------------------------------------- |
298
+ | `EncryptedInput` | 暗号化入力 | `value`, `encryptedValue`, `algorithm`, `showEncrypted` |
299
+ | `HashDisplay` | ハッシュ表示 | `value`, `algorithm`, `copyable`, `truncate` |
300
+ | `SignatureDisplay` | 署名表示 | `signature`, `publicKey`, `algorithm`, `verified` |
301
+
302
+ 既定は `@h-ai/crypto` と共通で、暗号化入力は `SM4`、ハッシュは `SM3`、署名は `SM2` です。
303
+
304
+ #### AI:文書・表計算(4)
305
+
306
+ | コンポーネント | 説明 | 主なプロパティ・機能 |
307
+ | ------------------------ | ------------------------ | ---------------------------------------------------------------------------------------------------------- |
308
+ | `MarkdownRenderer` | Markdown レンダラー | `content`, `fontSize`, `allowHtmlTags`, `showCopyButton`, `enableHighlight` |
309
+ | `AiDocumentDownloadMenu` | 文書ダウンロードメニュー | `actions`, `ondownload` |
310
+ | `AiDocumentEditor` | AI 文書エディター | `content`, `fontSize`, `allowHtmlTags`, `showRunButton`, `showCodePreviewToggle`, `allowUnsafeCodePreview` |
311
+ | `AiTableEditor` | AI 表計算エディター | `columns`, `rows`, `editable`, `ondownload` |
312
+
313
+ > `AiDocumentEditor` は既定で Markdown の内蔵プレビューのみを許可します。HTML / JS / CSS などには明示的な `allowUnsafeCodePreview`、または `oncoderun` による制御済み結果が必要です。
314
+ >
315
+ > Mermaid は追加設定不要です。文書(`sourceKind='document'`)の ` ```mermaid ` を閲覧時に自動描画し、code モード(`sourceKind='code'` + `showCodePreviewToggle`)ではコード・プレビューを切り替えます。`securityLevel: 'strict'` と消毒済み SVG を使い、`allowUnsafeCodePreview` は不要です。
316
+ >
317
+ > `MarkdownRenderer` と `AiDocumentEditor` には表示設定もあります:
318
+ >
319
+ > - `fontSize` は px の `number`、または `1.125rem` などの CSS 長さ。
320
+ > - `allowHtmlTags` は既定で無効です。有効化すると `<b>` / `<i>` / `<u>` / `<mark>` などを安全な許可リストで解析し、危険なタグ・属性は除去します。
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'` は既定で LayerChart の折れ線を使います。監視画面で分割線・点・最寄りの点への hover・縦基準線が必要な場合は `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:一覧操作(5)
355
+
356
+ | コンポーネント | 説明 | 主なプロパティ・機能 |
357
+ | ------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
358
+ | `CrudPage` | CRUD ページ | `crud`, `data`, `permissions`, `form`, `pagination`, `density`, `detailColumns`, `showHeader`, `toolbarStyle`, `toolbarLeading`, `toolbarActions`, `card`, `tableCell`, `onrowclick`, `sortableColumns`, `nav`, `loading`, `loaded`, `error`, `onRetry`, `keepPreviousData` |
359
+ | `CrudFilterBar` | 絞り込みツールバー(検索 + 各種フィルター + リセット) | `filterFields`, `searchValue`, `onsearch`, `onfilterchange`, `onreset` |
360
+ | `CrudDetailPanel` | 詳細パネル(ドロワー・モーダル) | `open`, `item`, `fields`, `variant`, `drawerWidth`, `modalSize` |
361
+ | `CrudEditPanel` | 編集パネル(ドロワー・モーダル) | `open`, `fields`, `variant`, `drawerWidth`, `modalSize`, `onsubmit` |
362
+ | `CrudDeleteConfirm` | 削除確認 | `open`, `loading`, `onconfirm` |
363
+
364
+ > `CrudPage` は既定で `TableToolbar` のアイコン・ポップアップ形式を使い、`toolbarStyle='filter-bar'` で平置きにできます。`detailColumns={['title']}` は指定列を詳細入口にします。`toolbarLeading` は左のタイトル・説明、`showHeader={false}` はタイトル重複の防止、`toolbarActions` は右の操作に使います。`card` は行をカード化、`tableCell` は行・列 key で Tooltip などの操作要素を描画、`sortableColumns` はサーバーのソート項目を制御します。フィルターは `select`、`boolean`、`number`、`date`、`date-range`、`text` に対応します。
365
+ >
366
+ > ページは親の利用可能な高さを満たします。データの有無にかかわらずテーブルが残りの領域を使い、データ部分のみがスクロールします。見出しはデータ領域の上、ページ分割は一覧カードの下に固定します。
367
+ >
368
+ > 列は先に 7rem まで縮小し長文を切り詰め、総幅が容器を超える場合のみ横スクロールを出します。操作列は右に固定し、横スクロール中も表示します。
369
+
370
+ #### エラーページ(1)
371
+
372
+ | コンポーネント | 説明 | 主なプロパティ・機能 |
373
+ | -------------- | -------------------------------------------------- | ----------------------------------------------------------------------- |
374
+ | `ErrorPage` | 401/403/404/500/503 の既定表示付き汎用エラーページ | `status`, `code`, `title`, `description`, `homeUrl`, `onhome`, `onback` |
375
+
376
+ > `CrudPage` の作成・編集の表示は `form` で設定します:
377
+ >
378
+ > - `form.variant`:`'drawer'`(既定)または `'modal'`。
379
+ > - ドロワー:`form.drawerSize` は既定サイズ、`form.drawerWidth` は任意の CSS 幅で優先されます。既定で左端をドラッグでき、リソースごとに幅を記憶します。`form.drawerResizable=false` で無効化。
380
+ > - モーダル:`form.modalSize` は既定サイズ、`form.modalWidth` / `form.modalHeight` は任意の CSS サイズ。
381
+ >
382
+ > `pagination` は常時表示するページ分割欄を設定します。`showSizeChanger`、`pageSizeOptions`、`showJumper`、`showTotal` は既定で有効です。コンパクト表示では `showPageInfo` でページ文言、`showFirstLast` で先頭・末尾ボタンを非表示にできます。
383
+ >
384
+ > `density` は `'normal'`(既定)または `'compact'`。一覧の行・操作・ページ分割・作成/編集/詳細フォームに適用し、ヘッダー・フィルターには影響しません。
385
+ >
386
+ > 読み込み状態は `loading` / `loaded` / `error` / `onRetry` / `keepPreviousData` で制御します。空状態は `loaded && total === 0` の場合のみで、初回応答前の誤表示を防ぎます。初回は検索中、利用可能なデータがない失敗はエラー・再試行(`onRetry`)を表示します。`keepPreviousData`(既定 true)は更新・失敗時に旧データを保持します。
387
+ >
388
+ > SvelteKit SSR を使わないクライアントは、`defineCrud` + `createCrudController` で `data` と `nav` を管理できます。最新リクエスト優先(seq + AbortSignal)、失敗時の旧データ保持、遅延読み込み、並行呼び出しの重複排除を備えます:
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` は遅延表示に対応し、`lazy` では未使用タブの内容を描画しません(`children` の `isActivated(key)` で判定)。`onactivate(key)` は初回のみ実行し、`createCrudController({ autoLoad: false })` と組み合わせて最初に表示されたときに読み込めます。
400
+
401
+ ## 使用例
402
+
403
+ ### Toast 通知
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
+ > `notifySuccess` / `notifyError` / `notifyInfo` / `notifyWarning` を内蔵し、アプリ独自の toast ツールは不要です。`notifyError(err, fallback?)` は `Error`・文字列と代替文言を受け付け、既定は成功 3 秒・失敗 5 秒です。
422
+
423
+ ### Message 表示
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
+ ### 標準的な CRUD ページ
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` は `title` / `header` がなくても右上の閉じるボタンを表示し、ヘッダー全体は追加しません。角丸は `radius="1.4rem"`、内部ドロップダウンなどを外に表示する場合は `bodyOverflow="visible"` を指定できます。
510
+
511
+ ### 宣言的な CRUD ページ(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
+ > ドロワーは `form={{ variant: 'drawer', drawerWidth: '40rem' }}` に変更できます。ユーザーが変更した幅が初期幅より優先され、リソースごとに記憶されます。
536
+
537
+ ### ログインページ
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
+ ### 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
+ ## スタイルの依存関係
581
+
582
+ コンポーネントは TailwindCSS v4 + DaisyUI を使います。アプリの `app.css` に次を設定します:
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`:HTML リセット、スクロールバー、フォームのフォーカスリング。
603
+ - `theme.css`:ブランドカラー・影・アニメーション曲線・フォント機能を含む Tailwind v4 `@theme`。
604
+ - `design-tokens.css`:余白・角丸・z-index・遷移の CSS 変数。モバイル推奨。
605
+ - `mobile.css`:セーフエリア、慣性スクロール、仮想キーボード対応、モバイルシェル(`hai-mobile-viewport` / `hai-mobile-shell` / `hai-mobile-main`)。
606
+
607
+ モバイルページは `BottomNav` の幅をページシェルに合わせ、デスクトッププレビューで画面全幅になるのを防ぎます:
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
+ ### アイコン
620
+
621
+ 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
+ ### テーマ切り替え
635
+
636
+ 内蔵ツールで 15 種類の厳選 DaisyUI テーマを管理します:
637
+
638
+ ```ts
639
+ import {
640
+ applyTheme, // 应用主题(自动持久化到 localStorage)
641
+ createThemeBootstrapScript, // 生成可注入 app.html 的首屏主题恢复脚本
642
+ getCurrentTheme, // 获取当前主题
643
+ getSavedTheme, // 保存済みテーマ。アプリ既定値を指定可能
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
+ 単純な場合は `getThemeInitScript()` を使えます。独自ストレージ key・テーマカラーの CSS 変数・言語設定がある場合、サーバーの `createThemeBootstrapScript()` で生成して `app.html` に注入することを推奨します。
654
+
655
+ SvelteKit の `app.html` にプレースホルダーを置きます:
656
+
657
+ ```html
658
+ <head>
659
+ <script>%theme_bootstrap%</script>
660
+ </head>
661
+ ```
662
+
663
+ 次に `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
+ ## 国際化(i18n)
679
+
680
+ @h-ai/ui は**コンポーネント内蔵翻訳**を使います:
681
+
682
+ - 用途別コンポーネント(`scenes/`)は中国語・英語(zh-CN / en-US)を内蔵し、そのまま使えます。
683
+ - `@h-ai/core` と同期した全体 locale の変更へ自動応答します。
684
+ - アプリは**ページ単位の文言**のみを扱い、内部テキストは @h-ai/ui が管理します。
685
+ - 個別文言の変更は `submitText`・`labels` などの props で指定。
686
+
687
+ ### createLocaleStore
688
+
689
+ クライアントの locale 状態を管理し、`@h-ai/core` の全体管理器へ自動同期します:
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
+ ### エクスポートされる i18n ツール
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
+ ## その他のエクスポート
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
+ ```