@squadbase/vantage-template 0.1.1-dev1.ac4fa22 → 0.1.1-dev1.da9fa09

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.
@@ -0,0 +1,87 @@
1
+ # デザイントークンと書体
2
+
3
+ ## トークンの解決順
4
+
5
+ スライドの色と書体は CSS カスタムプロパティ `--slide-*` から来ます。値は 3 段で解決されます:
6
+
7
+ 1. **`theme.css`(フレームワーク)** —— `--slide-bg: var(--background)` のように、アプリ自身の
8
+ トークンへの**別名**が張ってあります。CSS 変数は遅延解決なので、これだけでデッキは
9
+ アプリのライト / ダークの切り替えに自動で追従します
10
+ 2. **アプリの `styles.css`** —— アプリがトークンを上書きしていれば、デッキもそれに従います
11
+ 3. **`<SlideDeck theme={{ … }}>`** —— そのデッキのルートにインラインで書かれ、上の 2 つに勝ちます
12
+
13
+ | プロパティ | CSS 変数 | 既定 |
14
+ | --- | --- | --- |
15
+ | `bg` | `--slide-bg` | `var(--background)` |
16
+ | `fg` | `--slide-fg` | `var(--foreground)` |
17
+ | `accent` | `--slide-accent` | `var(--primary)` |
18
+ | `muted` | `--slide-muted` | `var(--muted-foreground)` |
19
+ | `fontDisplay` | `--slide-font-display` | 日本語を含むシステムスタック |
20
+ | `fontBody` | `--slide-font-body` | `var(--slide-font-display)` |
21
+ | `sizeHero` | `--slide-size-hero` | `168px` |
22
+ | `sizeBody` | `--slide-size-body` | `36px` |
23
+ | `radius` | `--slide-radius` | `var(--radius)` |
24
+
25
+ ```tsx
26
+ <SlideDeck theme={{ bg: "#0f172a", fg: "#f8fafc", accent: "#fbbf24" }}>
27
+ ```
28
+
29
+ **使うときは変数で参照します** —— `color: "var(--slide-fg)"`、`fontSize: "var(--slide-size-hero)"`。
30
+ `theme` に渡した値を直接書かないこと。変数経由にしておけば、`theme` を 1 行変えるだけでデッキ全体が
31
+ 追従します。
32
+
33
+ **`theme` に無い色はプレーンな const に置く**(`const muted = "#94a3b8"`)。`theme` は
34
+ 「デッキの土台となる 3〜4 色」のためのもので、パレット全部を入れる場所ではありません。
35
+
36
+ ## ダークモード
37
+
38
+ `theme` を渡さないデッキは、アプリのトークンをそのまま使います —— つまり `.dark` /
39
+ `[data-theme="dark"]` が祖先に付いていればダークで描かれます。**明暗の両方を自分で管理する必要は
40
+ ありません。**
41
+
42
+ `theme` に具体的な色(`bg: "#0f172a"`)を書いた瞬間、そのデッキは明暗に追従しなくなります。
43
+ これは意図的です —— 配色を決め打ちしたデッキは、どちらのモードでもその配色であるべきだからです。
44
+ 追従させたいなら `theme` を渡さないこと。
45
+
46
+ ## 書体
47
+
48
+ **既定はシステムスタックで、まずこれを使ってください。** 日本語を含むスタックが最初から入っています。
49
+
50
+ webfont が本当に必要なとき(ブランドフォント、あるいはシステムのカバーが薄い言語):
51
+
52
+ - **`<head>` に 1 回だけ、冪等に読み込む。** スライドコンポーネントの中でスタイルシートを描かないこと。
53
+ 切り替え中は 2 枚のスライドが同時にマウントされ、pptx 書き出しでは全スライドが同時にマウントされます
54
+ —— コンポーネントの中で `<link>` を描くと `@font-face` の集合が**マウント数だけ**登録されます。
55
+ `.slide.tsx` のモジュールのトップレベルから 1 回だけ注入します:
56
+
57
+ ```tsx
58
+ const FONT_HREF = "https://fonts.googleapis.com/css2?family=...&display=swap"
59
+ const FONT_LINK_ID = "vantage-slide-font-june" // デッキごとに違う id にする
60
+ if (typeof document !== "undefined" && !document.getElementById(FONT_LINK_ID)) {
61
+ const link = document.createElement("link")
62
+ link.id = FONT_LINK_ID
63
+ link.rel = "stylesheet"
64
+ link.href = FONT_HREF
65
+ document.head.appendChild(link)
66
+ }
67
+ ```
68
+
69
+ - **実際に使うウェイトだけを列挙する。** 余分な 1 ウェイトが `@font-face` の数を倍にします。
70
+
71
+ ## webfont と pptx 書き出し —— ここが壊れやすい
72
+
73
+ pptx 書き出しは、スライドを SVG の `foreignObject` の中でラスタライズします。この隔離された
74
+ 描画コンテキストでは:
75
+
76
+ - **ローカルにインストールされたフォントはファミリ名で解決されます。** システムスタックなら
77
+ 埋め込みは 1 バイトも要りません。既定のままなら何もしなくてよい、ということです
78
+ - **webfont は data URL としてインライン化されていなければ使えません。** 書き出しは
79
+ `getFontEmbedCSS` で自動的にインライン化しますが、それには 2 つの条件があります:
80
+ - **同一オリジンか CORS が有効**であること(Google Fonts は CORS 有効)
81
+ - **サブセットしてあること。** 日本語の webfont は致命的です —— Noto Sans JP のフルセットは
82
+ 100 を超える `unicode-range` の face を持ち、全部を base64 化しようとして書き出しが
83
+ タイムアウトするか、文字が豆腐になります。デッキの文字が固定なら Google Fonts の URL に
84
+ **`&text=<使う文字>`** を付けて、単一 face の小さなサブセットを要求してください
85
+
86
+ **判断の目安**: 日本語のデッキで pptx を配る予定があるなら、**システムスタックのままにする**のが
87
+ 最も確実です。ブランドフォントが要件なら `&text=` のサブセットにすること。
@@ -0,0 +1,196 @@
1
+ # ページ間トランジション(`SlideTransition`)
2
+
3
+ スライドが切り替わるときの enter / exit アニメーションです。**既定はありません** ——
4
+ 宣言しない限りページはスナップで入れ替わり、そのスナップも十分に上品な既定です。
5
+ 動きが何かを足すときだけ opt-in してください。
6
+
7
+ `prefers-reduced-motion: reduce` は自動で尊重されます。フォールバックを書く必要はありません。
8
+
9
+ (2 枚にまたがる同一要素を動かす「Magic Move」は `morph.md`。この契約の上に載っています。)
10
+
11
+ ## 契約
12
+
13
+ トランジションは `<Slide transition={...}>` に付けます。**入ってくる側が勝ちます**:
14
+ A → B の移動では B の `transition` が使われ、その `exit` が A で、`enter` が B で再生されます。
15
+ B → A と戻るときは A の `transition` が使われるので、**両方向で動かしたいなら両方のスライドに
16
+ 同じオブジェクトを渡します**。
17
+
18
+ ```tsx
19
+ import { Slide, SlideDeck, type SlideTransition } from "@squadbase/vantage/slide"
20
+
21
+ const RISE: SlideTransition = { /* … */ }
22
+
23
+ <SlideDeck>
24
+ <Slide>…</Slide>
25
+ <Slide transition={RISE}>…</Slide>
26
+ </SlideDeck>
27
+ ```
28
+
29
+ ```ts
30
+ type TransitionPhase = {
31
+ keyframes: Keyframe[] | PropertyIndexedKeyframes // WAAPI のキーフレーム
32
+ duration?: number // ms(省略時は上位の duration)
33
+ easing?: string // CSS easing
34
+ delay?: number // ms —— exit と enter を重ねるのに使う
35
+ }
36
+ type SlideTransition = {
37
+ duration: number // 上位のフォールバック
38
+ easing?: string
39
+ enter?: TransitionPhase // 入ってくる側で再生
40
+ exit?: TransitionPhase // 出ていく側で再生
41
+ morph?: boolean | MorphTransition // 共有要素のモーフ → morph.md
42
+ }
43
+ ```
44
+
45
+ ラッパーには `--slide-dir`(前進 `1` / 後退 `-1`)と `data-slide-dir`(`"forward"` / `"backward"`)が
46
+ 出るので、JS のコールバック無しに 1 つのキーフレームで方向を反転できます。
47
+
48
+ ## 設計原則(ここは崩さない)
49
+
50
+ 「PowerPoint で作りました」と最も大きく告げるのは、1 つのデッキに 6 種類のトランジションがあることです。
51
+ 節度がリズムを作ります。
52
+
53
+ - **DNA を 1 つ選び、デッキ全体で保つ。** 同じ duration 帯、同じ easing の組、同じ out → in のずらし方。
54
+ 変えてよいのは**どのプロパティに小さな一押しを乗せるか**だけ —— Y、X、opacity、scale、blur
55
+ - **duration: 140–280ms。** exit 140–180ms、enter 200–280ms、enter を 80ms ほど遅らせて重ねる。
56
+ 350ms を超えると動画編集の領域で、本当の状態変化のために取っておく
57
+ - **振れ幅の上限: 12px か 3% スケール。** 6px の Y 上昇は「次の考え」に読める。1920px の translateX は
58
+ 「別の書類」に読める。上質なツールは、気づく分だけしか動かさない
59
+ - **opacity は常に混ぜる。** transform だけのトランジションは硬く、opacity だけのものは最も安全な既定
60
+ - **easing: exit は ease-in、enter は ease-out。** 出るとき `cubic-bezier(0.4, 0, 1, 1)`、
61
+ 入るとき `cubic-bezier(0, 0, 0.2, 1)`。`linear` は使わない(スライドショーに見える)。
62
+ 対称な ease-in-out は状態が繋がるモーフのために取っておく
63
+
64
+ ## 上品な family —— 6 つ、1 つの DNA
65
+
66
+ ここから選んでください。1 つをデッキの標準にし、必要ならヒーロー / 表紙用に 2 つ目、
67
+ 本当の章の切れ目に 3 つ目を取っておきます。
68
+
69
+ ```tsx
70
+ const EASE_OUT = "cubic-bezier(0, 0, 0.2, 1)"
71
+ const EASE_IN = "cubic-bezier(0.4, 0, 1, 1)"
72
+
73
+ // RISE —— 標準の静けさ。Y に 6px。デッキの既定にする
74
+ const RISE: SlideTransition = {
75
+ duration: 200,
76
+ exit: {
77
+ duration: 140, easing: EASE_IN,
78
+ keyframes: [
79
+ { opacity: 1, transform: "translateY(0)" },
80
+ { opacity: 0, transform: "translateY(-4px)" },
81
+ ],
82
+ },
83
+ enter: {
84
+ duration: 200, delay: 80, easing: EASE_OUT,
85
+ keyframes: [
86
+ { opacity: 0, transform: "translateY(6px)" },
87
+ { opacity: 1, transform: "translateY(0)" },
88
+ ],
89
+ },
90
+ }
91
+
92
+ // DISSOLVE —— opacity だけ。最も静か
93
+ const DISSOLVE: SlideTransition = {
94
+ duration: 240,
95
+ exit: { duration: 200, easing: EASE_IN, keyframes: [{ opacity: 1 }, { opacity: 0 }] },
96
+ enter: { duration: 240, delay: 40, easing: EASE_OUT, keyframes: [{ opacity: 0 }, { opacity: 1 }] },
97
+ }
98
+
99
+ // SETTLE —— 表紙向き。上昇 + enter だけにわずかな blur
100
+ const SETTLE: SlideTransition = {
101
+ duration: 280,
102
+ exit: {
103
+ duration: 160, easing: EASE_IN,
104
+ keyframes: [
105
+ { opacity: 1, transform: "translateY(0)" },
106
+ { opacity: 0, transform: "translateY(-6px)" },
107
+ ],
108
+ },
109
+ enter: {
110
+ duration: 280, delay: 100, easing: EASE_OUT,
111
+ keyframes: [
112
+ { opacity: 0, transform: "translateY(12px)", filter: "blur(4px)" },
113
+ { opacity: 1, transform: "translateY(0)", filter: "blur(0)" },
114
+ ],
115
+ },
116
+ }
117
+
118
+ // BLOOM —— scale 0.97 → 1、移動なし。その場で像を結ぶ
119
+ const BLOOM: SlideTransition = {
120
+ duration: 240,
121
+ exit: {
122
+ duration: 160, easing: EASE_IN,
123
+ keyframes: [
124
+ { opacity: 1, transform: "scale(1)" },
125
+ { opacity: 0, transform: "scale(1.01)" },
126
+ ],
127
+ },
128
+ enter: {
129
+ duration: 240, delay: 80, easing: EASE_OUT,
130
+ keyframes: [
131
+ { opacity: 0, transform: "scale(0.97)" },
132
+ { opacity: 1, transform: "scale(1)" },
133
+ ],
134
+ },
135
+ }
136
+
137
+ // FALL —— RISE の鏡像。上から降りてくる
138
+ const FALL: SlideTransition = {
139
+ duration: 200,
140
+ exit: {
141
+ duration: 140, easing: EASE_IN,
142
+ keyframes: [
143
+ { opacity: 1, transform: "translateY(0)" },
144
+ { opacity: 0, transform: "translateY(4px)" },
145
+ ],
146
+ },
147
+ enter: {
148
+ duration: 200, delay: 80, easing: EASE_OUT,
149
+ keyframes: [
150
+ { opacity: 0, transform: "translateY(-6px)" },
151
+ { opacity: 1, transform: "translateY(0)" },
152
+ ],
153
+ },
154
+ }
155
+
156
+ // BREATH —— 章の切れ目。完全に消してから 120ms 置いて入る
157
+ // 本当の区切りにだけ、デッキ 1 本で 1〜2 回まで
158
+ const BREATH: SlideTransition = {
159
+ duration: 460,
160
+ exit: { duration: 180, easing: EASE_IN, keyframes: [{ opacity: 1 }, { opacity: 0 }] },
161
+ enter: {
162
+ duration: 240, delay: 300, easing: EASE_OUT,
163
+ keyframes: [
164
+ { opacity: 0, transform: "translateY(8px)" },
165
+ { opacity: 1, transform: "translateY(0)" },
166
+ ],
167
+ },
168
+ }
169
+ ```
170
+
171
+ 6 つは同じ DNA を共有していて、小さな一押しをどのプロパティに乗せるかだけが違います。
172
+ 読み手には変化に見え、それでいて 1 つの手つきに読めます。
173
+
174
+ ## 方向を意識したキーフレーム(控えめに)
175
+
176
+ 上品なツールの多くは後退時に鏡像化しません。本当に必要なとき(横に流れるスライドを逆再生したい等)
177
+ だけ `--slide-dir` を `calc()` の中で使います:
178
+
179
+ ```tsx
180
+ { transform: "translateX(calc(var(--slide-dir, 1) * 8px))" },
181
+ { transform: "translateX(0)" },
182
+ ```
183
+
184
+ 毎回これに手が伸びるなら、たぶん設計しすぎです。前進 = 後退の方が洗練された既定。
185
+
186
+ ## アンチパターン
187
+
188
+ - ❌ 6 ページに 6 種類のトランジション —— 最も大きな「PowerPoint 感」の合図
189
+ - ❌ `translateX(100%)` の横スライド —— iOS のモーダルや PowerPoint の Push であって、スライドの切り替えではない
190
+ - ❌ 強いスケールのポップ(`0.85 → 1`)+ blur —— ライトボックスの語彙で、何かに「ズームイン」する意味になる
191
+ - ❌ `clip-path: inset(…)` のワイプ —— After Effects の語彙。芝居がかる
192
+ - ❌ 両方のレイヤーに同時に blur —— 視覚的な濁り。目が焦点を結べない
193
+ - ❌ 通常の切り替えで 350ms を超える duration —— 間延びする
194
+ - ❌ 12px / 3% を超える移動 —— 連続ではなく断絶に読める
195
+ - ❌ `linear` easing —— プロダクトではなくスライドショーに見える
196
+ - ❌ とりあえずトランジションを宣言する。**理由が無いなら書かない。** スナップで十分
@@ -93,7 +93,7 @@ root/
93
93
  | `@squadbase/vantage/router` | `Link`・`Outlet`・`useParams`・`useSearch`・`useNavigate`・`redirect`・`notFound`・`useRoutes`・`useCurrentRoute`・`useSearchParam`・`useSearchState` |
94
94
  | `@squadbase/vantage/query` | `useApiQuery`・`useApiMutation`・`apiJson`・`apiFetch`・`apiUrl`・`ApiError`、ほか `useQuery`/`useMutation` など TanStack Query の再エクスポート |
95
95
  | `@squadbase/vantage/ui` | shadcn/ui(Base UI バリアント)プリミティブ(`Button`・`Loading`・`ErrorState`・`Empty` ほか) |
96
- | `@squadbase/vantage/components` | 複合パーツ(`PageShell`・`DashboardCardPreset`・`DataTablePreset`・`EChart` ほか) |
96
+ | `@squadbase/vantage/components` | 複合パーツ(`PageShell`・`DashboardCardPreset`・`DataTablePreset`、型付きチャート `LineChart`・`BarChart`・`AreaChart`・`DonutChart`・`BarList` ほか、`EChart`) |
97
97
  | `@squadbase/vantage/table` | `FilePageView`・`TableViewer`(データファイルページの表ビューア) |
98
98
  | `@squadbase/vantage/slide` | `SlideDeck`・`Slide`・`Steps`・`Step`・`MorphElement`・`useSlideNumber`・`useIsActiveSlide`(スライド専用。1920×1080 の固定キャンバス) |
99
99
  | `@squadbase/vantage/doc` | `Document`(ドキュメント専用。中の素の HTML 要素に見出し・表などのスタイルが当たる) |
@@ -137,6 +137,15 @@ root/
137
137
  | `.xlsx` ページ | `agents/xlsx.md` |
138
138
  | `.csv` ページ | `agents/csv.md` |
139
139
 
140
+ **ページになるのは `.tsx` / `.jsx`(`.slide.tsx`・`.doc.tsx` を含む)・`.csv`・`.xlsx` だけ。**
141
+
142
+ - `.xls`・`.xlsm`・`.xlsb`・`.ods`・`.tsv`・`.numbers`・`.parquet` はルートにならず、404 にもならない。
143
+ `vantage check` の `UNSUPPORTED_PAGE_FILE` 警告だけが知らせる。`.xlsx` / `.csv` に変換して置く
144
+ - `.md`・`.json`・`.txt` は何も言わずに無視される。Markdown を見せたいときは `.tsx` のページで
145
+ `MarkdownRenderer`(`@squadbase/vantage/markdown`)に渡す
146
+ - `.csv` / `.xlsx` はブラウザでパースする。25MB を超えるとそのページにエラーが出る
147
+ (`FILE_PAGE_TOO_LARGE` 警告)。20 万行を超えるシートは先頭 20 万行だけが表示される
148
+
140
149
  ## 環境変数
141
150
 
142
151
  - **クライアントで読めるのは `import.meta.env.PUBLIC_*` と `MODE`/`DEV`/`PROD`/`SSR`/`BASE_URL`
@@ -399,7 +408,7 @@ lockfile は `bun.lock`)。Squadbase のサンドボックスは bun と依存
399
408
  - 書き込む場所: `src/<slug>.doc.tsx`
400
409
  - ページ: あり
401
410
  - 作り方: `src/<slug>.doc.tsx` に `<Document>` を返すページを置く(`@squadbase/vantage/doc`。
402
- ルートは `.doc` を除いた `/<slug>`)。数値はコードに直接書き、チャートは `EChart` を文中に置く。
411
+ ルートは `.doc` を除いた `/<slug>`)。数値はコードに直接書き、チャートは `@squadbase/vantage/components` のチャート部品を文中に置く。
403
412
  書く前に `agents/doc.md` を読む。
404
413
  - **オートメーション** — 決まった時刻に集計・通知・定期レポートを回したいとき、しきい値で
405
414
  アラートしたいとき。
@@ -408,6 +417,12 @@ lockfile は `bun.lock`)。Squadbase のサンドボックスは bun と依存
408
417
  - 作り方: `automations/<名前>/automation.yml` に定義し、処理のコードは `pipelines/` に置く。
409
418
  書く前に Automation のガイド(`readAutomationGuide`)を読む。
410
419
 
420
+ CSV / Excel(`.csv` / `.xlsx`)は上の項目とは別で、書いて作るものではない。受け取ったファイルを
421
+ `src/` に置くだけで表として読めるページになる(`src/data/june.xlsx` → `/data/june`。置く前に
422
+ `agents/csv.md` / `agents/xlsx.md` を読む)。値だけが表示され、数式・書式は再現されない。25MB を
423
+ 超えるとエラー、20 万行を超えると先頭 20 万行だけが出る。`.xls`・`.ods`・`.tsv` などはページにならない
424
+ (`vantage check` の `UNSUPPORTED_PAGE_FILE` 警告)。
425
+
411
426
  ## 表示領域に安定 ID を付ける(`data-sq-ref`)
412
427
 
413
428
  Squadbase のチャットは、回答の根拠になったグラフ・表・KPI カードを
@@ -427,8 +442,9 @@ Squadbase のチャットは、回答の根拠になったグラフ・表・KPI
427
442
  ### 追加の不変条件
428
443
 
429
444
  - **意味のまとまった表示領域(グラフ・表・KPI カードなど)のルート要素に付ける。** 通常は
430
- `DashboardCard` / `DashboardCardPreset` のように props を DOM に流す部品。`EChart` / `DataTable` /
431
- `SectionHeader` は付けた属性を DOM に流さないので、それらを包むカード(または `div`)に付ける。
445
+ `DashboardCard` / `DashboardCardPreset` のように props を DOM に流す部品。`EChart` と ECharts で描く
446
+ チャート部品(`LineChart` / `BarChart` / `AreaChart` / `DonutChart`)・`DataTable` / `SectionHeader` は
447
+ 付けた属性を DOM に流さないので、それらを包むカード(または `div`)に付ける。
432
448
  - **値の形式は `^[A-Za-z0-9][A-Za-z0-9_-]{0,99}$`**(ASCII の英数字・ハイフン・アンダースコア、先頭は英数字、
433
449
  100 文字以内)。これ以外の値はリンクから開けない。推奨は意味を表す小文字 kebab-case の名前
434
450
  (例: `revenue-trend`, `churn-by-segment`)。表示順・ソース上の位置・index・連番から作らない。
@@ -27,6 +27,7 @@
27
27
  ```
28
28
  - 数式・グラフ・セル書式といった概念は csv には無いので、値がそのまま読めます。ブラウザで
29
29
  パースするので 25MB を超えるファイルはそのページにエラーが出ます —— 大きいデータは
30
- `server/api/` から配って `DataTable` で描いてください
30
+ `server/api/` から配って `DataTable` で描いてください。20 万行を超えると先頭
31
+ 20 万行だけが表示されます(残りはダウンロードして見る)
31
32
  - **ビューアの無い拡張子(`.tsv` など)はルートになりません。** `vantage check` の
32
33
  `UNSUPPORTED_PAGE_FILE` 警告が知らせます
@@ -12,7 +12,7 @@
12
12
 
13
13
  ```tsx
14
14
  import { definePage } from "@squadbase/vantage"
15
- import { EChart } from "@squadbase/vantage/components"
15
+ import { BarChart } from "@squadbase/vantage/components"
16
16
  import { Document } from "@squadbase/vantage/doc"
17
17
 
18
18
  export const page = definePage({ title: "6月度サマリー", navLabel: "6月度サマリー" })
@@ -23,7 +23,7 @@ export default function JuneSummary() {
23
23
  <p>6月の売上は <strong>5,250 万円</strong>で、目標を 5% 上回りました。</p>
24
24
  <h2>週次の売上</h2>
25
25
  <figure>
26
- <EChart option={option} height={280} ariaLabel="6月の週次売上" />
26
+ <BarChart data={weekly} index="week" categories={["売上"]} height={280} ariaLabel="6月の週次売上" />
27
27
  <figcaption>第4週はキャンペーン初週</figcaption>
28
28
  </figure>
29
29
  <table>…</table>
@@ -42,7 +42,8 @@ export default function JuneSummary() {
42
42
  見た目を変えたいときはユーティリティを並べず、下の `--doc-*` を変えます。
43
43
  - **数値はコードに直接書く。** ドキュメントは書いた時点の内容を残すページです。
44
44
  `useApiQuery` などで読み込むと、開くたびに値が変わります。
45
- - **チャートは `EChart`(`@squadbase/vantage/components`)を文中に置く。** `figure` で囲めば
45
+ - **チャートは型付きチャート(`BarChart`・`LineChart` など。`@squadbase/vantage/components`)を
46
+ 文中に置く**(選び方は `agents/page.md` の「グラフはまず型付きチャートで描く」)。`figure` で囲めば
46
47
  `figcaption` がキャプションになります。
47
48
  - **PDF の保存ボタンは作らない。** `<Document>` が右下に PDF 保存のボタンを出し、印刷時は
48
49
  本文だけをライトの配色で出します。
@@ -26,8 +26,8 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@squadbase/vantage": "^0.5.0",
29
- "react": "^19.2.7",
30
- "react-dom": "^19.2.7"
29
+ "react": "^19.3.1",
30
+ "react-dom": "^19.3.1"
31
31
  }
32
32
  }
33
33
  ```
@@ -164,17 +164,31 @@ refetchOnWindowFocus false・networkMode "always")。挙動を変えたいとき
164
164
  ## 組み込み UI / コンポーネントの入口
165
165
 
166
166
  - **プリミティブ**は `@squadbase/vantage/ui` から import する(`Button`・`Loading`・
167
- `ErrorState`・`Empty`・`Select`・`Checkbox` ほか)。
167
+ `ErrorState`・`Empty`・`Select`・`Checkbox`・`RadioGroup`・`Field`・`Combobox`・`Pagination`・
168
+ `Avatar`・`Kbd`・`HoverCard`・`AlertDialog`・`Drawer`・`Toaster` + `toast.add()` ほか)。
168
169
  - **複合パーツ**は `@squadbase/vantage/components` から(`PageShell`・`DashboardCardPreset`・
169
- `DataTablePreset`・`EChart` ほか)。
170
+ `DataTablePreset`・型付きチャート(下の「グラフ」)・`EChart` ほか)。
170
171
  - **どの名前が import 可能か / props の詳細は `vantage docs` で引く。** ガイドとコンポーネント
171
172
  リファレンスはパッケージに同梱されていて、オフラインでも読める(→ `AGENTS.md` の
172
173
  「ドキュメントを引く」)。**名前が分からないとき**はやりたいことで `vantage search` する
173
174
  (→ 同節)。
174
- - **UI は import して使うのが既定。取り出す(eject)必要はない。** `vantage add ui|block` は
175
- ソースを手元にコピーして**改造したいとき専用**のコマンドで、取り出せるのは
176
- `ui: data-table` と `block: sales-overview` の 2 つだけ。それ以外の名前は import 専用で
177
- `add` の対象ではない(未知名で実行すると候補が一覧表示される)。
175
+ - **UI は import して使うのが既定。取り出す(eject)必要はない。** `vantage add ui <name>` は
176
+ ソースを手元にコピーして**改造したいとき専用**のコマンド。`/ui` のプリミティブも
177
+ `/components` の複合パーツも全部取り出せる(名前は kebab-case: `add ui bar-chart`。
178
+ ブロックは `add block sales-overview`。未知名で実行すると候補が一覧表示される)。
179
+
180
+ ### グラフはまず型付きチャートで描く
181
+
182
+ - 推移は `LineChart` / `AreaChart`、比較は `BarChart`、構成比は `DonutChart`。`data`(行の配列)・
183
+ `index`(x 軸の列)・`categories`(系列の列)を渡すだけで、option は書かない
184
+ (`DonutChart` は `category` / `value`)。
185
+ - 上位 N 件は `BarList`、判定帯や予算消化は `CategoryBar`、達成率は `ProgressCircle`、
186
+ 日ごとの状態は `Tracker`。
187
+ - `EChart` + `EChartsOption` は、二軸・散布図・ヒートマップ・注釈線など型付きチャートの props で
188
+ 表せないときだけ使う。
189
+ - 色は省略してテーマトークンに任せる。指定するなら `"chart-1"`〜`"chart-5"` か
190
+ `"var(--destructive)"` などの CSS 色。hex の直書きや Tailwind クラスは使わない。
191
+ - 数値の書式(`¥`・`%`・桁区切り)は `valueFormatter` に渡す。
178
192
 
179
193
  ### ダークモードは opt-in
180
194
 
@@ -28,6 +28,7 @@
28
28
  - **複数シートに対応**しています。シートのタブは下端に並び、切り替えて閲覧できます。
29
29
  - 数式・グラフ・セル書式は再現されません(値として読めれば十分、という設計)。ブラウザでパースするので
30
30
  25MB を超えるファイルはそのページにエラーが出ます —— 大きいデータは `server/api/` から配って
31
- `DataTable` で描いてください
31
+ `DataTable` で描いてください。20 万行を超えると先頭
32
+ 20 万行だけが表示されます(残りはダウンロードして見る)
32
33
  - **ビューアの無い拡張子(`.xls`・`.ods` など)はルートになりません。** `vantage check` の
33
34
  `UNSUPPORTED_PAGE_FILE` 警告が知らせます