@k8ordo/ui 1.0.0 → 2.0.0

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 (85) hide show
  1. package/README.md +37 -12
  2. package/dist/components/ai/conversation/conversation.d.mts +8 -10
  3. package/dist/components/ai/conversation/conversation.mjs +1 -6
  4. package/dist/components/ai/conversation/index.d.mts +15 -1
  5. package/dist/components/ai/conversation/index.mjs +8 -1
  6. package/dist/components/ai/index.d.mts +5 -9
  7. package/dist/components/ai/index.mjs +4 -4
  8. package/dist/components/ai/message/index.d.mts +12 -1
  9. package/dist/components/ai/message/index.mjs +7 -1
  10. package/dist/components/ai/message/message.d.mts +3 -5
  11. package/dist/components/ai/message/message.mjs +1 -5
  12. package/dist/components/ai/prompt-input/index.d.mts +23 -1
  13. package/dist/components/ai/prompt-input/index.mjs +8 -1
  14. package/dist/components/ai/prompt-input/prompt-input.d.mts +4 -6
  15. package/dist/components/ai/prompt-input/prompt-input.mjs +1 -6
  16. package/dist/components/ai/suggestion/index.d.mts +13 -1
  17. package/dist/components/ai/suggestion/index.mjs +7 -1
  18. package/dist/components/ai/suggestion/suggestion.d.mts +3 -5
  19. package/dist/components/ai/suggestion/suggestion.mjs +1 -5
  20. package/dist/components/buttons/icon-button/icon-button.mjs +1 -1
  21. package/dist/components/form/checkbox/checkbox.d.mts +1 -0
  22. package/dist/components/form/checkbox/checkbox.mjs +3 -2
  23. package/dist/components/form/checkbox-group/index.d.mts +1 -0
  24. package/dist/components/form/file-field/file-field.d.mts +12 -15
  25. package/dist/components/form/file-field/file-field.mjs +1 -6
  26. package/dist/components/form/file-field/index.d.mts +17 -1
  27. package/dist/components/form/file-field/index.mjs +8 -1
  28. package/dist/components/form/radio/radio.d.mts +1 -0
  29. package/dist/components/form/radio/radio.mjs +3 -2
  30. package/dist/components/index.d.mts +15 -21
  31. package/dist/components/index.mjs +7 -7
  32. package/dist/components/navigation/anchor/anchor.mjs +1 -1
  33. package/dist/components/navigation/tabs/index.d.mts +19 -1
  34. package/dist/components/navigation/tabs/index.mjs +9 -1
  35. package/dist/components/navigation/tabs/tabs.d.mts +16 -18
  36. package/dist/components/navigation/tabs/tabs.mjs +1 -7
  37. package/dist/components/overlays/dialog/dialog.d.mts +12 -16
  38. package/dist/components/overlays/dialog/dialog.mjs +1 -6
  39. package/dist/components/overlays/dialog/index.d.mts +17 -1
  40. package/dist/components/overlays/dialog/index.mjs +8 -1
  41. package/dist/components/overlays/dropdown-menu/dropdown-menu.d.mts +20 -24
  42. package/dist/components/overlays/dropdown-menu/dropdown-menu.mjs +2 -11
  43. package/dist/components/overlays/dropdown-menu/index.d.mts +31 -1
  44. package/dist/components/overlays/dropdown-menu/index.mjs +12 -1
  45. package/dist/components/overlays/dropdown-menu/sub-menu.mjs +1 -1
  46. package/dist/components/overlays/list-box/index.d.mts +27 -1
  47. package/dist/components/overlays/list-box/index.mjs +9 -1
  48. package/dist/components/overlays/list-box/list-box.d.mts +20 -22
  49. package/dist/components/overlays/list-box/list-box.mjs +2 -8
  50. package/dist/components/overlays/popover/index.d.mts +24 -1
  51. package/dist/components/overlays/popover/index.mjs +8 -1
  52. package/dist/components/overlays/popover/popover.d.mts +18 -20
  53. package/dist/components/overlays/popover/popover.mjs +2 -7
  54. package/dist/components/overlays/tooltip/index.d.mts +21 -1
  55. package/dist/components/overlays/tooltip/index.mjs +8 -1
  56. package/dist/components/overlays/tooltip/tooltip.d.mts +18 -22
  57. package/dist/components/overlays/tooltip/tooltip.mjs +2 -7
  58. package/dist/hooks/index.d.mts +1 -4
  59. package/dist/hooks/index.mjs +2 -5
  60. package/dist/index.d.mts +13 -15
  61. package/dist/index.mjs +9 -12
  62. package/dist/integrations/_shared/renderers.mjs +7 -7
  63. package/dist/integrations/_shared/schemas.d.mts +1 -1
  64. package/dist/{hooks/focus-trap/index.d.mts → internal/focus-trap.d.mts} +1 -1
  65. package/dist/{hooks/focus-trap/index.mjs → internal/focus-trap.mjs} +1 -1
  66. package/dist/styles/index.css +0 -8
  67. package/docs/GUIDE.md +148 -133
  68. package/docs/llms.txt +11 -7
  69. package/docs/props.generated.json +18 -0
  70. package/docs/references/ai-chat.md +67 -67
  71. package/docs/references/color.md +108 -107
  72. package/docs/references/components.md +318 -310
  73. package/docs/references/generative-ui.md +51 -30
  74. package/docs/references/helpers.md +155 -52
  75. package/docs/references/hooks.md +61 -74
  76. package/docs/references/interaction-design.md +47 -47
  77. package/docs/references/spatial-design.md +83 -81
  78. package/docs/references/typography.md +68 -67
  79. package/package.json +32 -33
  80. package/dist/hooks/hash/index.d.mts +0 -4
  81. package/dist/hooks/hash/index.mjs +0 -25
  82. package/dist/hooks/local-storage/index.d.mts +0 -4
  83. package/dist/hooks/local-storage/index.mjs +0 -46
  84. package/dist/hooks/session-storage/index.d.mts +0 -4
  85. package/dist/hooks/session-storage/index.mjs +0 -46
@@ -1,11 +1,11 @@
1
- # k8ordo UI AI チャットコンポーネント
1
+ # @k8ordo/ui AI chat components
2
2
 
3
- AI チャット UI(会話ログ・メッセージ・入力欄・推論表示・ツール実行表示)を構築するコンポーネント群。ルートではなく専用サブパスからインポートする。
3
+ Components for building an AI chat UI: the conversation log, messages, the input box, reasoning display, and tool-invocation display. Import them from their dedicated subpaths rather than from the root.
4
4
 
5
- ## インポート方法
5
+ ## Importing
6
6
 
7
7
  ```tsx
8
- // 基本コンポーネント(追加の依存なし)
8
+ // Core components (no extra dependencies)
9
9
  import {
10
10
  Conversation,
11
11
  Message,
@@ -15,47 +15,47 @@ import {
15
15
  ToolInvocation,
16
16
  } from '@k8ordo/ui/ai';
17
17
 
18
- // ストリーミング対応 Markdown 描画(optional peer: streamdown)
18
+ // Streaming-aware Markdown rendering (optional peer: streamdown)
19
19
  import { Response } from '@k8ordo/ui/ai/response';
20
20
 
21
- // AI SDK 連携(optional peer: ai)
21
+ // AI SDK integration (optional peer: ai)
22
22
  import { mapMessageParts } from '@k8ordo/ui/ai-sdk';
23
23
  ```
24
24
 
25
- 型も `@k8ordo/ui/ai` から export される:
25
+ The types are exported from `@k8ordo/ui/ai` too:
26
26
 
27
- - `ChatStatus`: `'ready' | 'submitted' | 'streaming' | 'error'`(AI SDK の `status` と互換)
28
- - `ToolState`: `'input-streaming' | 'input-available' | 'approval-requested' | 'approval-responded' | 'output-available' | 'output-error' | 'output-denied'`(AI SDK v7 のツール状態と 1:1)
27
+ - `ChatStatus`: `'ready' | 'submitted' | 'streaming' | 'error'` (compatible with the AI SDK's `status`)
28
+ - `ToolState`: `'input-streaming' | 'input-available' | 'approval-requested' | 'approval-responded' | 'output-available' | 'output-error' | 'output-denied'` (1:1 with the AI SDK v7 tool states)
29
29
 
30
- ### optional peer のセットアップ
30
+ ### Setting up the optional peers
31
31
 
32
- 使うサブパスの分だけインストールする。`@k8ordo/ui/ai` 本体はどちらも不要。
32
+ Install only what the subpaths you use require. `@k8ordo/ui/ai` itself needs neither.
33
33
 
34
34
  ```bash
35
- # @k8ordo/ui/ai/response(Response)を使う場合
35
+ # If you use @k8ordo/ui/ai/response (Response)
36
36
  pnpm add streamdown
37
- # @k8ordo/ui/ai-sdk(mapMessageParts)を使う場合
37
+ # If you use @k8ordo/ui/ai-sdk (mapMessageParts)
38
38
  pnpm add ai
39
39
  ```
40
40
 
41
- `Response` を使う場合は streamdown のスタイルシートの読み込みと、Tailwind の `@source` 設定も必要。streamdown のスタイルはビルド済み `styles.css` には含まれないため、`Response` だけは Tailwind CSS 4 のビルド(`@k8ordo/ui/tailwind.css` エントリ)が前提になる:
41
+ Using `Response` also means loading streamdown's stylesheet and adding a Tailwind `@source` entry. streamdown's styles are not part of the prebuilt `styles.css`, so `Response` alone requires a Tailwind CSS 4 build (the `@k8ordo/ui/tailwind.css` entry):
42
42
 
43
43
  ```tsx
44
44
  import 'streamdown/styles.css';
45
45
  ```
46
46
 
47
47
  ```css
48
- /* アプリの CSS エントリに追加(パスは CSS ファイルから node_modules への相対) */
48
+ /* Add to your app's CSS entry (the path is relative from the CSS file to node_modules) */
49
49
  @source '../node_modules/streamdown/dist/*.js';
50
50
  ```
51
51
 
52
- なお本リファレンスの例で使う `useChat` は AI SDK の React バインディング(`pnpm add @ai-sdk/react`)から import する。
52
+ The `useChat` used in the examples below comes from the AI SDK's React bindings (`pnpm add @ai-sdk/react`).
53
53
 
54
- 以下に出てくる `label` / `sendLabel` などの「デフォルト」は文言辞書の既定値(日本語)。`<UIProvider messages={en}>` で辞書ごと切り替えられる([i18n](components.md))。個別の prop を渡した場合はそちらが辞書より優先される。
54
+ The "default" shown for `label`, `sendLabel`, and similar props below is the message dictionary's default (Japanese). Swap the whole dictionary with `<UIProvider messages={en}>` (see [i18n](components.md)). Passing the prop directly wins over the dictionary.
55
55
 
56
- ## 全体像
56
+ ## The whole picture
57
57
 
58
- `Conversation`(会話ログ)+ `Message`(吹き出し)+ `PromptInput`(入力欄)が骨組み。`Response` / `Reasoning` / `ToolInvocation` は後から足せる。
58
+ `Conversation` (the log), `Message` (a bubble), and `PromptInput` (the input box) form the skeleton. `Response`, `Reasoning`, and `ToolInvocation` can be added later.
59
59
 
60
60
  ```tsx
61
61
  'use client';
@@ -99,7 +99,7 @@ export function Chat() {
99
99
  onSubmit={(text) => sendMessage({ text })}
100
100
  status={status}
101
101
  >
102
- <PromptInput.Textarea placeholder="メッセージを入力" />
102
+ <PromptInput.Textarea placeholder="Type a message" />
103
103
  <PromptInput.Submit />
104
104
  </PromptInput.Root>
105
105
  </div>
@@ -109,7 +109,7 @@ export function Chat() {
109
109
 
110
110
  ## Conversation
111
111
 
112
- 会話ログのスクロール領域。Compound component パターン。最下部に張り付き、新しいメッセージで自動スクロールする。最下部から離れると `ScrollButton` が現れる。
112
+ The scrolling region for the conversation log, as a compound component. It sticks to the bottom and auto-scrolls on a new message. Once you scroll away from the bottom, a `ScrollButton` appears.
113
113
 
114
114
  ```tsx
115
115
  import { Conversation } from '@k8ordo/ui/ai';
@@ -124,40 +124,40 @@ import { Conversation } from '@k8ordo/ui/ai';
124
124
 
125
125
  Props (Conversation.Messages):
126
126
 
127
- - `label`: string(デフォルト: `'チャット'`、aria-label として使用)
128
- - `isStreaming`: boolean(`aria-busy` に反映)
127
+ - `label`: string (default: `'チャット'`, used as the aria-label)
128
+ - `isStreaming`: boolean (reflected in `aria-busy`)
129
129
 
130
130
  Props (Conversation.ScrollButton):
131
131
 
132
- - `label`: string(デフォルト: `'最新のメッセージへ移動'`)
132
+ - `label`: string (default: `'最新のメッセージへ移動'`)
133
133
 
134
134
  ## Message
135
135
 
136
- メッセージ 1 件。`from="user"` は右寄せの吹き出し、`from="assistant"` は地の文として描画される。アバターなどは `Message.Root` の子として自由に並べられる。
136
+ A single message. `from="user"` renders as a right-aligned bubble; `from="assistant"` renders as running text. An avatar or anything else can sit freely as a child of `Message.Root`.
137
137
 
138
138
  ```tsx
139
139
  import { Message } from '@k8ordo/ui/ai';
140
140
 
141
141
  <Message.Root from="user">
142
- <Message.Content>こんにちは</Message.Content>
142
+ <Message.Content>Hello</Message.Content>
143
143
  </Message.Root>
144
144
 
145
145
  <Message.Root from="assistant">
146
- <Message.Content isStreaming>生成中の本文…</Message.Content>
146
+ <Message.Content isStreaming>Text still streaming…</Message.Content>
147
147
  </Message.Root>
148
148
  ```
149
149
 
150
150
  Props (Message.Root):
151
151
 
152
- - `from`: `'user'` | `'assistant'`(必須)
152
+ - `from`: `'user'` | `'assistant'` (required)
153
153
 
154
154
  Props (Message.Content):
155
155
 
156
- - `isStreaming`: boolean(true でストリーミングカーソルを表示)
156
+ - `isStreaming`: boolean (shows the streaming cursor when true)
157
157
 
158
158
  ## PromptInput
159
159
 
160
- 送信フォーム。Enter で送信、Shift+Enter で改行。IME 変換確定の Enter では送信しない。`status` が `'submitted'` / `'streaming'` の間は `Submit` が停止ボタンに切り替わる。本文は trim され、空文字は送信されない。
160
+ The submit form. Enter sends; Shift+Enter inserts a newline; the Enter that commits an IME conversion does not send. While `status` is `'submitted'` or `'streaming'`, `Submit` turns into a stop button. The body is trimmed, and an empty string is never sent.
161
161
 
162
162
  ```tsx
163
163
  import { PromptInput } from '@k8ordo/ui/ai';
@@ -167,30 +167,30 @@ import { PromptInput } from '@k8ordo/ui/ai';
167
167
  onSubmit={(message) => send(message)}
168
168
  onStop={stop}
169
169
  >
170
- <PromptInput.Textarea placeholder="メッセージを入力" />
170
+ <PromptInput.Textarea placeholder="Type a message" />
171
171
  <PromptInput.Submit />
172
172
  </PromptInput.Root>;
173
173
  ```
174
174
 
175
175
  Props (PromptInput.Root):
176
176
 
177
- - `status`: ChatStatus(デフォルト: `'ready'`)
178
- - `value` / `defaultValue` / `onChange`: controlled / uncontrolled 両対応(`onChange` は `(value: string) => void`)
179
- - `onSubmit`: `(message: string) => void`(trim 済みの本文)
180
- - `onStop`: `() => void`(送信中に停止ボタンを押したとき)
177
+ - `status`: ChatStatus (default: `'ready'`)
178
+ - `value` / `defaultValue` / `onChange`: supports both controlled and uncontrolled use (`onChange` is `(value: string) => void`)
179
+ - `onSubmit`: `(message: string) => void` (the trimmed body)
180
+ - `onStop`: `() => void` (when the stop button is pressed mid-send)
181
181
 
182
182
  Props (PromptInput.Textarea):
183
183
 
184
- - `placeholder`: string ほか textarea 属性
184
+ - `placeholder`: string, plus the other textarea attributes
185
185
 
186
186
  Props (PromptInput.Submit):
187
187
 
188
- - `sendLabel`: string(デフォルト: `'送信'`)
189
- - `stopLabel`: string(デフォルト: `'停止'`)
188
+ - `sendLabel`: string (default: `'送信'`)
189
+ - `stopLabel`: string (default: `'停止'`)
190
190
 
191
191
  ## Reasoning
192
192
 
193
- 推論(思考過程)の折りたたみ表示。ストリーミング中はラベルが「思考中…」になる。
193
+ A collapsible view of the model's reasoning. While streaming, the label reads 「思考中…」.
194
194
 
195
195
  ```tsx
196
196
  import { Reasoning } from '@k8ordo/ui/ai';
@@ -201,11 +201,11 @@ import { Reasoning } from '@k8ordo/ui/ai';
201
201
  Props:
202
202
 
203
203
  - `isStreaming`: boolean
204
- - `isOpen` / `defaultOpen` / `onChange`: 開閉の controlled / uncontrolled 両対応(`onChange` は `(isOpen: boolean) => void`)
204
+ - `isOpen` / `defaultOpen` / `onChange`: open state, controlled or uncontrolled (`onChange` is `(isOpen: boolean) => void`)
205
205
 
206
206
  ## Response
207
207
 
208
- ストリーミング対応の Markdown レンダラ。未クローズのコードブロックなど途中の Markdown も破綻なく描画する。optional peer の `streamdown` が必要([セットアップ](#optional-peer-のセットアップ)参照)。
208
+ A streaming-aware Markdown renderer. It renders half-finished Markdown — an unclosed code block, say — without breaking. Requires the optional peer `streamdown` (see [setup](#setting-up-the-optional-peers)).
209
209
 
210
210
  ```tsx
211
211
  import { Response } from '@k8ordo/ui/ai/response';
@@ -218,47 +218,47 @@ import 'streamdown/styles.css';
218
218
 
219
219
  Props:
220
220
 
221
- - `children`: string(Markdown 文字列、必須)
221
+ - `children`: string (the Markdown, required)
222
222
  - `isStreaming`: boolean
223
- - 上記以外の streamdown の props(`translations` / `controls` / `linkSafety` / `plugins` / `components` / `urlTransform` / `dir` など)はそのまま透過する。`className` と `mode` はライブラリが握る(`mode` は `isStreaming` から決まる)
223
+ - Every other streamdown prop (`translations`, `controls`, `linkSafety`, `plugins`, `components`, `urlTransform`, `dir`, …) passes straight through. The library owns `className` and `mode` (`mode` is derived from `isStreaming`)
224
224
 
225
- 文言は i18n 辞書から引くので、既定では「コードをコピー」「表をダウンロード」などが日本語で出る。個別に変えたいときは `translations` を渡す(prop > 辞書 > streamdown 既定)。
225
+ The wording comes from the i18n dictionary, so by default strings such as 「コードをコピー」 and 「表をダウンロード」 appear in Japanese. Pass `translations` to change individual strings (prop > dictionary > streamdown's own default).
226
226
 
227
- `linkSafety` はライブラリ側で**無効を既定にしている**。streamdown の既定(有効)ではリンクが `<a>` ではなく `<button>` で描画され、⌘クリック・中クリック・リンクアドレスのコピー・支援技術の link ロールが失われるため。確認ダイアログを挟みたい場合は明示的に有効化する:
227
+ The library **defaults `linkSafety` to off**. With streamdown's own default (on), links render as `<button>` rather than `<a>`, which loses ⌘-click, middle-click, copying the link address, and the link role for assistive technology. Turn it on explicitly if you want the confirmation dialog:
228
228
 
229
229
  ```tsx
230
230
  <Response linkSafety={{ enabled: true }}>{markdown}</Response>
231
231
  ```
232
232
 
233
- なお `javascript:` のような危険なスキームは `linkSafety` とは無関係に rehype-harden が無効化するので、既定のままでも生きたリンクにはならない。
233
+ Dangerous schemes such as `javascript:` are neutralized by rehype-harden regardless of `linkSafety`, so they never become live links even with the default.
234
234
 
235
235
  ## Suggestion
236
236
 
237
- 候補プロンプトのチップ表示。クリックで `onSelect` に `value` が渡る。
237
+ Suggested prompts shown as chips. Clicking one passes its `value` to `onSelect`.
238
238
 
239
239
  ```tsx
240
240
  import { Suggestion } from '@k8ordo/ui/ai';
241
241
 
242
242
  <Suggestion.List>
243
- <Suggestion.Item onSelect={send} value="IME 対応について教えて" />
243
+ <Suggestion.Item onSelect={send} value="Tell me about IME support" />
244
244
  <Suggestion.Item onSelect={send} value="streaming">
245
- ストリーミング表示は?
245
+ How does streaming display work?
246
246
  </Suggestion.Item>
247
247
  </Suggestion.List>;
248
248
  ```
249
249
 
250
250
  Props (Suggestion.List):
251
251
 
252
- - `label`: string(デフォルト: `'候補'`、aria-label として使用)
252
+ - `label`: string (default: `'候補'`, used as the aria-label)
253
253
 
254
254
  Props (Suggestion.Item):
255
255
 
256
- - `value`: string(必須。children 省略時は表示テキストも兼ねる)
256
+ - `value`: string (required; also serves as the visible text when children is omitted)
257
257
  - `onSelect`: `(value: string) => void`
258
258
 
259
259
  ## ToolInvocation
260
260
 
261
- ツール呼び出しの折りたたみ表示。`state` に応じてスピナー / 成功 / エラー / 拒否のアイコンが切り替わる(承認待ち・承認応答・入力中は進行中としてスピナー)。
261
+ A collapsible view of a tool call. The icon switches between spinner, success, error, and denied according to `state` (awaiting approval, approval responded, and input streaming all count as in-progress and show the spinner).
262
262
 
263
263
  ```tsx
264
264
  import { ToolInvocation } from '@k8ordo/ui/ai';
@@ -267,23 +267,23 @@ import { ToolInvocation } from '@k8ordo/ui/ai';
267
267
  name="search_web"
268
268
  state="output-available"
269
269
  input={{ query: 'k8ordo UI' }}
270
- output="検索結果…"
270
+ output="Search results…"
271
271
  />;
272
272
  ```
273
273
 
274
274
  Props:
275
275
 
276
- - `name`: string(必須、ツール名)
277
- - `state`: ToolState(必須)
278
- - `input`: unknown(文字列以外は JSON 表示)
279
- - `output`: ReactNode(文字列は `pre` 表示、要素はそのまま描画)
280
- - `errorText`: string(`state="output-error"` 時の表示。省略時は既定文言)
281
- - `deniedReason`: string(`state="output-denied"` 時の表示。省略時は既定文言)
282
- - `isOpen` / `defaultOpen` / `onChange`: 開閉の controlled / uncontrolled 両対応
276
+ - `name`: string (required, the tool name)
277
+ - `state`: ToolState (required)
278
+ - `input`: unknown (anything other than a string is shown as JSON)
279
+ - `output`: ReactNode (a string is shown in a `pre`; an element renders as-is)
280
+ - `errorText`: string (shown when `state="output-error"`; falls back to the default wording)
281
+ - `deniedReason`: string (shown when `state="output-denied"`; falls back to the default wording)
282
+ - `isOpen` / `defaultOpen` / `onChange`: open state, controlled or uncontrolled
283
283
 
284
- ## AI SDK 連携(mapMessageParts)
284
+ ## AI SDK integration (mapMessageParts)
285
285
 
286
- `@k8ordo/ui/ai-sdk` の `mapMessageParts` は、AI SDK の `UIMessage.parts` を描画しやすい `MappedPart[]` に変換する。React 非依存で、optional peer の `ai`(v7)が必要。
286
+ `mapMessageParts` from `@k8ordo/ui/ai-sdk` converts the AI SDK's `UIMessage.parts` into a `MappedPart[]` that is easy to render. It has no React dependency and requires the optional peer `ai` (v7).
287
287
 
288
288
  ```ts
289
289
  type MappedPart =
@@ -301,7 +301,7 @@ type MappedPart =
301
301
  };
302
302
  ```
303
303
 
304
- 利用側が `kind` で分岐して `Response` / `Reasoning` / `ToolInvocation` を描画する:
304
+ The caller switches on `kind` and renders `Response`, `Reasoning`, or `ToolInvocation`:
305
305
 
306
306
  ```tsx
307
307
  import { mapMessageParts } from '@k8ordo/ui/ai-sdk';
@@ -313,7 +313,7 @@ import { Response } from '@k8ordo/ui/ai/response';
313
313
  if (part.kind === 'reasoning')
314
314
  return <Reasoning key={i}>{part.text}</Reasoning>;
315
315
  if (part.kind === 'tool') {
316
- // output は unknown なので、ReactNode に変換してから渡す
316
+ // output is unknown, so convert it to a ReactNode before passing it on
317
317
  return (
318
318
  <ToolInvocation
319
319
  key={part.toolCallId}
@@ -335,11 +335,11 @@ import { Response } from '@k8ordo/ui/ai/response';
335
335
  </Message.Content>;
336
336
  ```
337
337
 
338
- `ChatStatus` / `ToolState` 型は `@k8ordo/ui/ai-sdk` からも re-export される。
338
+ The `ChatStatus` and `ToolState` types are re-exported from `@k8ordo/ui/ai-sdk` as well.
339
339
 
340
- ## 生成 UI との組み合わせ
340
+ ## Combining with generative UI
341
341
 
342
- LLM がツール結果として返した UI spec を、チャットの吹き出し内で描画できる。詳細は [生成 UI リファレンス](generative-ui.md) を参照。
342
+ A UI spec returned by an LLM as a tool result can be rendered inside a chat bubble. See the [generative UI reference](generative-ui.md) for details.
343
343
 
344
344
  ```tsx
345
345
  'use client';
@@ -1,121 +1,122 @@
1
- # カラーシステム
2
-
3
- k8ordo UI のカラーは OKLCH 色空間で定義され、穏やかなトーンでUIに出る。
4
-
5
- ## 設計思想
6
-
7
- - **OKLCH パレット**: 全色を OKLCH で定義。明度(L)を全色相で統一し、同じステップ番号同士のコントラストが揃う
8
- - **鮮やかなパレット、穏やかなトークン**: パレット自体は鮮やかに保ち、セマンティックトークンで抑えたトーンにマッピング
9
- - **WCAG AAA 準拠**: fg/bg の組み合わせで 7:1 以上のコントラスト比を確保
10
- - **60-30-10 ルール**: 60% ニュートラル(グレー系)、30% サポート(bg-subtle 等)、10% アクセント(primary)
11
- - Primary に Teal(H:180)、Secondary に Cyan(H:210)を使用
12
- - ダークモードは「ライトモードの反転」ではなく、独立したトーンで設計
13
-
14
- ## OKLCH パレット設計
15
-
16
- 明度スケール(全色相で共通):
17
-
18
- | Step | L | 用途 |
19
- | ---- | ----- | -------------------------- |
20
- | 50 | 0.975 | 最も薄い背景 |
21
- | 100 | 0.945 | 薄い背景 |
22
- | 200 | 0.900 | 控えめな背景 |
23
- | 300 | 0.840 | サポートカラー |
24
- | 400 | 0.750 | 中間トーン |
25
- | 500 | 0.660 | コアカラー |
26
- | 600 | 0.575 | やや暗い |
27
- | 700 | 0.490 | 暗いトーン |
28
- | 800 | 0.410 | テキスト用(AAA on white) |
29
- | 900 | 0.370 | 最も暗い |
30
-
31
- Chroma は色相ごとに最適化(gamut 内で最大化)。
32
-
33
- ## セマンティックカラー(前景)
34
-
35
- | Tailwind クラス | Light | Dark | 用途 |
36
- | ----------------- | ---------- | ---------- | ---------------- |
37
- | `text-fg-base` | gray-900 | gray-50 | 基本テキスト |
38
- | `text-fg-subtle` | gray-400 | gray-500 | プレースホルダー |
39
- | `text-fg-mute` | gray-700 | gray-300 | 補足テキスト |
40
- | `text-fg-inverse` | gray-50 | gray-900 | 反転テキスト |
41
- | `text-fg-info` | blue-800 | blue-200 | 情報 |
42
- | `text-fg-success` | green-800 | green-200 | 成功 |
43
- | `text-fg-warning` | yellow-800 | yellow-200 | 警告 |
44
- | `text-fg-error` | red-800 | red-200 | エラー |
45
-
46
- ## セマンティックカラー(背景)
47
-
48
- | Tailwind クラス | Light | Dark | 用途 |
49
- | ----------------- | ---------- | ---------- | ---------------- |
50
- | `bg-bg-base` | white | gray-900 | カード背景 |
51
- | `bg-bg-subtle` | gray-100 | gray-800 | ページ背景 |
52
- | `bg-bg-mute` | gray-200 | gray-700 | ホバー状態用 |
53
- | `bg-bg-emphasize` | gray-300 | gray-600 | アクティブ状態用 |
54
- | `bg-bg-inverse` | gray-900 | white | 反転背景 |
55
- | `bg-bg-info` | blue-100 | blue-900 | 情報背景 |
56
- | `bg-bg-success` | green-100 | green-900 | 成功背景 |
57
- | `bg-bg-warning` | yellow-100 | yellow-900 | 警告背景 |
58
- | `bg-bg-error` | red-100 | red-900 | エラー背景 |
59
-
60
- ## セマンティックカラー(ボーダー)
61
-
62
- | Tailwind クラス | Light | Dark | 用途 |
63
- | ------------------------- | ---------- | ---------- | -------------- |
64
- | `border-border-base` | gray-400 | gray-600 | 標準ボーダー |
65
- | `border-border-subtle` | gray-100 | gray-900 | 薄いボーダー |
66
- | `border-border-mute` | gray-200 | gray-800 | 控えめボーダー |
67
- | `border-border-emphasize` | gray-500 | gray-500 | 強調ボーダー |
68
- | `border-border-inverse` | gray-700 | gray-300 | 反転ボーダー |
69
- | `border-border-info` | blue-500 | blue-500 | 情報ボーダー |
70
- | `border-border-success` | green-500 | green-500 | 成功ボーダー |
71
- | `border-border-warning` | yellow-500 | yellow-500 | 警告ボーダー |
72
- | `border-border-error` | red-500 | red-500 | エラーボーダー |
73
-
74
- ## ブランドカラー(Primary: Teal H:180)
75
-
76
- | Tailwind クラス | Light | Dark | 用途 |
77
- | ------------------------- | -------- | -------- | ------------------- |
78
- | `text-primary-fg` | teal-800 | teal-100 | Primary テキスト |
79
- | `bg-primary-bg` | teal-200 | teal-800 | Primary 背景 |
80
- | `bg-primary-bg-subtle` | teal-50 | teal-900 | 薄い Primary 背景 |
81
- | `bg-primary-bg-mute` | teal-100 | teal-800 | 控えめ Primary 背景 |
82
- | `bg-primary-bg-emphasize` | teal-200 | teal-700 | 強調 Primary 背景 |
83
- | `border-primary-border` | teal-500 | teal-500 | Primary ボーダー |
84
-
85
- ## ブランドカラー(Secondary: Cyan H:210)
86
-
87
- | Tailwind クラス | Light | Dark | 用途 |
88
- | ------------------------- | -------- | -------- | ------------------- |
89
- | `text-secondary-fg` | cyan-700 | cyan-300 | Secondary テキスト |
90
- | `bg-secondary-bg` | cyan-300 | cyan-700 | Secondary 背景 |
91
- | `bg-secondary-bg-subtle` | cyan-100 | cyan-900 | 薄い Secondary 背景 |
92
- | `border-secondary-border` | cyan-600 | cyan-600 | Secondary ボーダー |
93
-
94
- ## 使い方の例
1
+ # Color system
2
+
3
+ The colors in `@k8ordo/ui` are defined in the OKLCH color space and reach the UI
4
+ in a calm tone.
5
+
6
+ ## Design intent
7
+
8
+ - **An OKLCH palette**: every color is defined in OKLCH. Lightness (L) is shared across hues, so the same step number gives matching contrast whatever the hue
9
+ - **A vivid palette, calm tokens**: the palette itself stays vivid; the semantic tokens map it down to a restrained tone
10
+ - **WCAG AAA**: fg/bg pairs hold a contrast ratio of 7:1 or better
11
+ - **The 60-30-10 rule**: 60% neutral (greys), 30% supporting (`bg-subtle`, …), 10% accent (primary)
12
+ - Primary is teal (H:180), secondary is cyan (H:210)
13
+ - Dark mode is designed as its own tone, not as an inversion of light mode
14
+
15
+ ## OKLCH palette design
16
+
17
+ The lightness scale (shared by every hue):
18
+
19
+ | Step | L | Use |
20
+ | ---- | ----- | ----------------------- |
21
+ | 50 | 0.975 | The lightest background |
22
+ | 100 | 0.945 | A light background |
23
+ | 200 | 0.900 | A restrained background |
24
+ | 300 | 0.840 | Supporting color |
25
+ | 400 | 0.750 | Mid tone |
26
+ | 500 | 0.660 | The core color |
27
+ | 600 | 0.575 | Slightly dark |
28
+ | 700 | 0.490 | Dark tone |
29
+ | 800 | 0.410 | For text (AAA on white) |
30
+ | 900 | 0.370 | The darkest |
31
+
32
+ Chroma is optimized per hue (maximized within gamut).
33
+
34
+ ## Semantic colors (foreground)
35
+
36
+ | Tailwind class | Light | Dark | Use |
37
+ | ----------------- | ---------- | ---------- | --------------- |
38
+ | `text-fg-base` | gray-900 | gray-50 | Body text |
39
+ | `text-fg-subtle` | gray-400 | gray-500 | Placeholders |
40
+ | `text-fg-mute` | gray-700 | gray-300 | Supporting text |
41
+ | `text-fg-inverse` | gray-50 | gray-900 | Inverted text |
42
+ | `text-fg-info` | blue-800 | blue-200 | Information |
43
+ | `text-fg-success` | green-800 | green-200 | Success |
44
+ | `text-fg-warning` | yellow-800 | yellow-200 | Warning |
45
+ | `text-fg-error` | red-800 | red-200 | Error |
46
+
47
+ ## Semantic colors (background)
48
+
49
+ | Tailwind class | Light | Dark | Use |
50
+ | ----------------- | ---------- | ---------- | -------------------- |
51
+ | `bg-bg-base` | white | gray-900 | Card background |
52
+ | `bg-bg-subtle` | gray-100 | gray-800 | Page background |
53
+ | `bg-bg-mute` | gray-200 | gray-700 | For the hover state |
54
+ | `bg-bg-emphasize` | gray-300 | gray-600 | For the active state |
55
+ | `bg-bg-inverse` | gray-900 | white | Inverted background |
56
+ | `bg-bg-info` | blue-100 | blue-900 | Information ground |
57
+ | `bg-bg-success` | green-100 | green-900 | Success ground |
58
+ | `bg-bg-warning` | yellow-100 | yellow-900 | Warning ground |
59
+ | `bg-bg-error` | red-100 | red-900 | Error ground |
60
+
61
+ ## Semantic colors (border)
62
+
63
+ | Tailwind class | Light | Dark | Use |
64
+ | ------------------------- | ---------- | ---------- | ------------------ |
65
+ | `border-border-base` | gray-400 | gray-600 | Standard border |
66
+ | `border-border-subtle` | gray-100 | gray-900 | Light border |
67
+ | `border-border-mute` | gray-200 | gray-800 | Restrained border |
68
+ | `border-border-emphasize` | gray-500 | gray-500 | Emphasized border |
69
+ | `border-border-inverse` | gray-700 | gray-300 | Inverted border |
70
+ | `border-border-info` | blue-500 | blue-500 | Information border |
71
+ | `border-border-success` | green-500 | green-500 | Success border |
72
+ | `border-border-warning` | yellow-500 | yellow-500 | Warning border |
73
+ | `border-border-error` | red-500 | red-500 | Error border |
74
+
75
+ ## Brand color (primary: teal H:180)
76
+
77
+ | Tailwind class | Light | Dark | Use |
78
+ | ------------------------- | -------- | -------- | ------------------------- |
79
+ | `text-primary-fg` | teal-800 | teal-100 | Primary text |
80
+ | `bg-primary-bg` | teal-200 | teal-800 | Primary background |
81
+ | `bg-primary-bg-subtle` | teal-50 | teal-900 | Light primary background |
82
+ | `bg-primary-bg-mute` | teal-100 | teal-800 | Restrained primary ground |
83
+ | `bg-primary-bg-emphasize` | teal-200 | teal-700 | Emphasized primary ground |
84
+ | `border-primary-border` | teal-500 | teal-500 | Primary border |
85
+
86
+ ## Brand color (secondary: cyan H:210)
87
+
88
+ | Tailwind class | Light | Dark | Use |
89
+ | ------------------------- | -------- | -------- | -------------------------- |
90
+ | `text-secondary-fg` | cyan-700 | cyan-300 | Secondary text |
91
+ | `bg-secondary-bg` | cyan-300 | cyan-700 | Secondary background |
92
+ | `bg-secondary-bg-subtle` | cyan-100 | cyan-900 | Light secondary background |
93
+ | `border-secondary-border` | cyan-600 | cyan-600 | Secondary border |
94
+
95
+ ## Usage examples
95
96
 
96
97
  ```tsx
97
- // ページ背景にカードを浮かせる
98
+ // Float a card on the page background
98
99
  <div className="bg-bg-subtle min-h-screen">
99
100
  <Card appearance="shadow">
100
101
  <div className="p-8">
101
- <h2 className="text-fg-base font-bold">タイトル</h2>
102
- <p className="text-fg-mute mt-2">補足テキスト</p>
103
- <span className="text-primary-fg">アクセントテキスト</span>
102
+ <h2 className="text-fg-base font-bold">Title</h2>
103
+ <p className="text-fg-mute mt-2">Supporting text</p>
104
+ <span className="text-primary-fg">Accent text</span>
104
105
  </div>
105
106
  </Card>
106
107
  </div>
107
108
 
108
- // ホバー状態
109
+ // Hover state
109
110
  <button className="bg-bg-base hover:bg-bg-mute transition-colors">
110
- ボタン
111
+ Button
111
112
  </button>
112
113
  ```
113
114
 
114
- ## やってはいけないこと
115
+ ## What not to do
115
116
 
116
- - グラデーション背景(`bg-gradient-to-*`)
117
- - 彩度の高い色の広範囲使用(アクセントは小面積で)
118
- - 透明度による状態表現(`/90`, `/80` など)— 専用のセマンティックカラーを使う
119
- - ホバーに `bg-primary-bg` を使う — `bg-bg-mute` を優先
120
- - 生のパレット色(`bg-teal-500`)を直接使う — セマンティックトークンを使う
121
- - ダークモードで単純にカラーを反転させる
117
+ - Gradient backgrounds (`bg-gradient-to-*`)
118
+ - Saturated color over large areas (keep accents small)
119
+ - Expressing state with opacity (`/90`, `/80`, …) — use the dedicated semantic color
120
+ - Using `bg-primary-bg` on hover — prefer `bg-bg-mute`
121
+ - Using a raw palette color (`bg-teal-500`) directly — use a semantic token
122
+ - Simply inverting the colors for dark mode