termcast 1.3.48 → 1.3.50

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 (255) hide show
  1. package/dist/build.d.ts.map +1 -1
  2. package/dist/build.js +12 -0
  3. package/dist/build.js.map +1 -1
  4. package/dist/cli.js +5 -40
  5. package/dist/cli.js.map +1 -1
  6. package/dist/colors.d.ts +7 -7
  7. package/dist/colors.js +7 -7
  8. package/dist/compile.d.ts +6 -1
  9. package/dist/compile.d.ts.map +1 -1
  10. package/dist/compile.js +45 -26
  11. package/dist/compile.js.map +1 -1
  12. package/dist/components/actions.js +1 -1
  13. package/dist/components/actions.js.map +1 -1
  14. package/dist/components/bar-chart.d.ts +38 -0
  15. package/dist/components/bar-chart.d.ts.map +1 -0
  16. package/dist/components/bar-chart.js +158 -0
  17. package/dist/components/bar-chart.js.map +1 -0
  18. package/dist/components/bar-graph.d.ts +41 -0
  19. package/dist/components/bar-graph.d.ts.map +1 -0
  20. package/dist/components/bar-graph.js +95 -0
  21. package/dist/components/bar-graph.js.map +1 -0
  22. package/dist/components/detail.d.ts.map +1 -1
  23. package/dist/components/detail.js +5 -7
  24. package/dist/components/detail.js.map +1 -1
  25. package/dist/components/footer.d.ts.map +1 -1
  26. package/dist/components/footer.js +8 -9
  27. package/dist/components/footer.js.map +1 -1
  28. package/dist/components/form/date-picker.d.ts.map +1 -1
  29. package/dist/components/form/date-picker.js +7 -1
  30. package/dist/components/form/date-picker.js.map +1 -1
  31. package/dist/components/form/dropdown.d.ts.map +1 -1
  32. package/dist/components/form/dropdown.js +10 -2
  33. package/dist/components/form/dropdown.js.map +1 -1
  34. package/dist/components/form/index.d.ts.map +1 -1
  35. package/dist/components/form/index.js +4 -5
  36. package/dist/components/form/index.js.map +1 -1
  37. package/dist/components/form/use-form-navigation.d.ts.map +1 -1
  38. package/dist/components/form/use-form-navigation.js +6 -0
  39. package/dist/components/form/use-form-navigation.js.map +1 -1
  40. package/dist/components/graph.d.ts +111 -0
  41. package/dist/components/graph.d.ts.map +1 -0
  42. package/dist/components/graph.js +392 -0
  43. package/dist/components/graph.js.map +1 -0
  44. package/dist/components/icon.js +5 -5
  45. package/dist/components/icon.js.map +1 -1
  46. package/dist/components/list.d.ts +53 -5
  47. package/dist/components/list.d.ts.map +1 -1
  48. package/dist/components/list.js +125 -71
  49. package/dist/components/list.js.map +1 -1
  50. package/dist/components/loading-bar.js +3 -3
  51. package/dist/components/loading-bar.js.map +1 -1
  52. package/dist/components/loading-text.d.ts +1 -1
  53. package/dist/components/loading-text.d.ts.map +1 -1
  54. package/dist/components/loading-text.js +3 -1
  55. package/dist/components/loading-text.js.map +1 -1
  56. package/dist/components/metadata.js +2 -2
  57. package/dist/components/metadata.js.map +1 -1
  58. package/dist/components/row.d.ts +10 -0
  59. package/dist/components/row.d.ts.map +1 -0
  60. package/dist/components/row.js +12 -0
  61. package/dist/components/row.js.map +1 -0
  62. package/dist/components/table.d.ts +57 -0
  63. package/dist/components/table.d.ts.map +1 -0
  64. package/dist/components/table.js +365 -0
  65. package/dist/components/table.js.map +1 -0
  66. package/dist/descendants.js +13 -13
  67. package/dist/descendants.js.map +1 -1
  68. package/dist/examples/bar-graph-weekly.d.ts +2 -0
  69. package/dist/examples/bar-graph-weekly.d.ts.map +1 -0
  70. package/dist/examples/bar-graph-weekly.js +95 -0
  71. package/dist/examples/bar-graph-weekly.js.map +1 -0
  72. package/dist/examples/components-weird-places.d.ts +2 -0
  73. package/dist/examples/components-weird-places.d.ts.map +1 -0
  74. package/dist/examples/components-weird-places.js +46 -0
  75. package/dist/examples/components-weird-places.js.map +1 -0
  76. package/dist/examples/graph-bar-chart.d.ts +2 -0
  77. package/dist/examples/graph-bar-chart.d.ts.map +1 -0
  78. package/dist/examples/graph-bar-chart.js +270 -0
  79. package/dist/examples/graph-bar-chart.js.map +1 -0
  80. package/dist/examples/graph-multi-series.d.ts +2 -0
  81. package/dist/examples/graph-multi-series.d.ts.map +1 -0
  82. package/dist/examples/graph-multi-series.js +23 -0
  83. package/dist/examples/graph-multi-series.js.map +1 -0
  84. package/dist/examples/graph-polymarket.d.ts +2 -0
  85. package/dist/examples/graph-polymarket.d.ts.map +1 -0
  86. package/dist/examples/graph-polymarket.js +109 -0
  87. package/dist/examples/graph-polymarket.js.map +1 -0
  88. package/dist/examples/graph-row.d.ts +2 -0
  89. package/dist/examples/graph-row.d.ts.map +1 -0
  90. package/dist/examples/graph-row.js +226 -0
  91. package/dist/examples/graph-row.js.map +1 -0
  92. package/dist/examples/graph-styles.d.ts +2 -0
  93. package/dist/examples/graph-styles.d.ts.map +1 -0
  94. package/dist/examples/graph-styles.js +316 -0
  95. package/dist/examples/graph-styles.js.map +1 -0
  96. package/dist/examples/list-accessory-table.d.ts +2 -0
  97. package/dist/examples/list-accessory-table.d.ts.map +1 -0
  98. package/dist/examples/list-accessory-table.js +46 -0
  99. package/dist/examples/list-accessory-table.js.map +1 -0
  100. package/dist/examples/list-item-accessories.d.ts +2 -0
  101. package/dist/examples/list-item-accessories.d.ts.map +1 -0
  102. package/dist/examples/list-item-accessories.js +27 -0
  103. package/dist/examples/list-item-accessories.js.map +1 -0
  104. package/dist/examples/list-no-actions.d.ts +2 -0
  105. package/dist/examples/list-no-actions.d.ts.map +1 -0
  106. package/dist/examples/list-no-actions.js +7 -0
  107. package/dist/examples/list-no-actions.js.map +1 -0
  108. package/dist/examples/simple-detail-table.d.ts +2 -0
  109. package/dist/examples/simple-detail-table.d.ts.map +1 -0
  110. package/dist/examples/simple-detail-table.js +45 -0
  111. package/dist/examples/simple-detail-table.js.map +1 -0
  112. package/dist/examples/simple-graph.d.ts +2 -0
  113. package/dist/examples/simple-graph.d.ts.map +1 -0
  114. package/dist/examples/simple-graph.js +32 -0
  115. package/dist/examples/simple-graph.js.map +1 -0
  116. package/dist/examples/simple-table-wrap.d.ts +2 -0
  117. package/dist/examples/simple-table-wrap.d.ts.map +1 -0
  118. package/dist/examples/simple-table-wrap.js +37 -0
  119. package/dist/examples/simple-table-wrap.js.map +1 -0
  120. package/dist/examples/table-edge-cases.d.ts +2 -0
  121. package/dist/examples/table-edge-cases.d.ts.map +1 -0
  122. package/dist/examples/table-edge-cases.js +70 -0
  123. package/dist/examples/table-edge-cases.js.map +1 -0
  124. package/dist/examples/table-flex-grow.d.ts +2 -0
  125. package/dist/examples/table-flex-grow.d.ts.map +1 -0
  126. package/dist/examples/table-flex-grow.js +18 -0
  127. package/dist/examples/table-flex-grow.js.map +1 -0
  128. package/dist/extensions/dev.d.ts.map +1 -1
  129. package/dist/extensions/dev.js +5 -1
  130. package/dist/extensions/dev.js.map +1 -1
  131. package/dist/globals.d.ts +1 -0
  132. package/dist/globals.d.ts.map +1 -1
  133. package/dist/globals.js +2 -0
  134. package/dist/globals.js.map +1 -1
  135. package/dist/index.d.ts +10 -0
  136. package/dist/index.d.ts.map +1 -1
  137. package/dist/index.js +10 -0
  138. package/dist/index.js.map +1 -1
  139. package/dist/internal/date-picker-widget.d.ts.map +1 -1
  140. package/dist/internal/date-picker-widget.js +4 -0
  141. package/dist/internal/date-picker-widget.js.map +1 -1
  142. package/dist/internal/providers.d.ts.map +1 -1
  143. package/dist/internal/providers.js +1 -3
  144. package/dist/internal/providers.js.map +1 -1
  145. package/dist/markdown-utils.d.ts +22 -1
  146. package/dist/markdown-utils.d.ts.map +1 -1
  147. package/dist/markdown-utils.js +66 -1
  148. package/dist/markdown-utils.js.map +1 -1
  149. package/dist/opentui.d.ts +4 -0
  150. package/dist/opentui.d.ts.map +1 -0
  151. package/dist/opentui.js +3 -0
  152. package/dist/opentui.js.map +1 -0
  153. package/dist/release.d.ts +2 -1
  154. package/dist/release.d.ts.map +1 -1
  155. package/dist/release.js +2 -1
  156. package/dist/release.js.map +1 -1
  157. package/dist/state.d.ts +1 -0
  158. package/dist/state.d.ts.map +1 -1
  159. package/dist/state.js +1 -1
  160. package/dist/state.js.map +1 -1
  161. package/dist/theme.d.ts +1 -0
  162. package/dist/theme.d.ts.map +1 -1
  163. package/dist/theme.js +13 -0
  164. package/dist/theme.js.map +1 -1
  165. package/dist/themes/nerv.json +227 -0
  166. package/dist/themes/termcast.json +72 -71
  167. package/dist/themes.d.ts +2 -1
  168. package/dist/themes.d.ts.map +1 -1
  169. package/dist/themes.js +7 -5
  170. package/dist/themes.js.map +1 -1
  171. package/dist/utils.d.ts.map +1 -1
  172. package/dist/utils.js +3 -0
  173. package/dist/utils.js.map +1 -1
  174. package/package.json +13 -5
  175. package/src/build.tsx +13 -0
  176. package/src/cli.tsx +5 -49
  177. package/src/colors.tsx +7 -7
  178. package/src/compile.tsx +52 -29
  179. package/src/components/actions.tsx +1 -1
  180. package/src/components/bar-chart.tsx +271 -0
  181. package/src/components/bar-graph.tsx +214 -0
  182. package/src/components/detail.tsx +7 -8
  183. package/src/components/footer.tsx +14 -15
  184. package/src/components/form/date-picker.tsx +9 -0
  185. package/src/components/form/dropdown.tsx +13 -3
  186. package/src/components/form/index.tsx +4 -6
  187. package/src/components/form/use-form-navigation.tsx +6 -0
  188. package/src/components/graph.tsx +506 -0
  189. package/src/components/icon.tsx +5 -5
  190. package/src/components/list.tsx +210 -102
  191. package/src/components/loading-bar.tsx +3 -3
  192. package/src/components/loading-text.tsx +4 -2
  193. package/src/components/metadata.tsx +2 -2
  194. package/src/components/row.tsx +31 -0
  195. package/src/components/table.tsx +511 -0
  196. package/src/descendants.tsx +13 -13
  197. package/src/examples/action-shortcut.vitest.tsx +1 -1
  198. package/src/examples/actions-context.vitest.tsx +1 -1
  199. package/src/examples/bar-graph-weekly.tsx +264 -0
  200. package/src/examples/bar-graph-weekly.vitest.tsx +275 -0
  201. package/src/examples/detail-metadata-showcase.vitest.tsx +8 -8
  202. package/src/examples/form-basic.vitest.tsx +239 -0
  203. package/src/examples/form-dropdown.vitest.tsx +29 -29
  204. package/src/examples/form-tagpicker.vitest.tsx +27 -27
  205. package/src/examples/github.vitest.tsx +4 -4
  206. package/src/examples/graph-bar-chart.tsx +408 -0
  207. package/src/examples/graph-bar-chart.vitest.tsx +283 -0
  208. package/src/examples/graph-multi-series.tsx +36 -0
  209. package/src/examples/graph-multi-series.vitest.tsx +89 -0
  210. package/src/examples/graph-polymarket.tsx +182 -0
  211. package/src/examples/graph-polymarket.vitest.tsx +130 -0
  212. package/src/examples/graph-row.tsx +347 -0
  213. package/src/examples/graph-row.vitest.tsx +295 -0
  214. package/src/examples/graph-styles.tsx +457 -0
  215. package/src/examples/graph-styles.vitest.tsx +322 -0
  216. package/src/examples/list-accessory-table.tsx +77 -0
  217. package/src/examples/list-detail-metadata.vitest.tsx +21 -21
  218. package/src/examples/list-dropdown-default.vitest.tsx +12 -12
  219. package/src/examples/list-item-accessories.tsx +106 -0
  220. package/src/examples/list-item-accessories.vitest.tsx +115 -0
  221. package/src/examples/list-no-actions.tsx +18 -0
  222. package/src/examples/list-no-actions.vitest.tsx +97 -0
  223. package/src/examples/list-spacing-mode.vitest.tsx +6 -6
  224. package/src/examples/list-with-detail.vitest.tsx +92 -92
  225. package/src/examples/list-with-dropdown.vitest.tsx +49 -6
  226. package/src/examples/list-with-sections.vitest.tsx +61 -56
  227. package/src/examples/simple-detail-markdown.vitest.tsx +21 -17
  228. package/src/examples/simple-detail-table.tsx +65 -0
  229. package/src/examples/simple-detail-table.vitest.tsx +200 -0
  230. package/src/examples/simple-graph.tsx +51 -0
  231. package/src/examples/simple-graph.vitest.tsx +124 -0
  232. package/src/examples/simple-grid.vitest.tsx +3 -3
  233. package/src/examples/simple-list-search.vitest.tsx +65 -0
  234. package/src/examples/simple-navigation.vitest.tsx +3 -3
  235. package/src/examples/simple-table-wrap.tsx +55 -0
  236. package/src/examples/simple-table-wrap.vitest.tsx +91 -0
  237. package/src/examples/store.vitest.tsx +1 -1
  238. package/src/examples/table-edge-cases.tsx +72 -0
  239. package/src/examples/table-edge-cases.vitest.tsx +307 -0
  240. package/src/examples/table-flex-grow.tsx +53 -0
  241. package/src/examples/table-flex-grow.vitest.tsx +124 -0
  242. package/src/extensions/dev.tsx +7 -1
  243. package/src/globals.ts +3 -0
  244. package/src/index.tsx +31 -0
  245. package/src/internal/date-picker-widget.tsx +4 -0
  246. package/src/internal/providers.tsx +1 -4
  247. package/src/markdown-utils.tsx +82 -1
  248. package/src/opentui.tsx +5 -0
  249. package/src/release.tsx +3 -0
  250. package/src/state.tsx +2 -1
  251. package/src/theme.tsx +14 -0
  252. package/src/themes/nerv.json +231 -0
  253. package/src/themes/termcast.json +75 -71
  254. package/src/themes.ts +8 -5
  255. package/src/utils.tsx +4 -0
@@ -0,0 +1,347 @@
1
+ // Example: Two graphs side by side using the Row component.
2
+ // Shows horizontal graph layouts in both List side-detail and
3
+ // full Detail view (pushed on Enter).
4
+
5
+ import React from 'react'
6
+ import { List, Detail, Graph, Color, Action, ActionPanel, Row } from 'termcast'
7
+ import { useNavigation } from 'termcast/src/internal/navigation'
8
+ import { renderWithProviders } from '../utils'
9
+
10
+ // ── Data ─────────────────────────────────────────────────────────────
11
+
12
+ const cpuData = [
13
+ 25, 30, 45, 60, 55, 72, 80, 65, 50, 40,
14
+ 35, 55, 70, 85, 90, 75, 60, 45, 38, 30,
15
+ ]
16
+ const memData = [
17
+ 40, 42, 44, 48, 52, 55, 58, 60, 62, 65,
18
+ 68, 70, 72, 74, 76, 78, 80, 82, 84, 86,
19
+ ]
20
+ const readOps = [
21
+ 120, 85, 150, 200, 180, 95, 110, 250, 300, 275,
22
+ 190, 160, 140, 220, 280, 310, 200, 170, 130, 100,
23
+ ]
24
+ const writeOps = [
25
+ 40, 55, 30, 80, 70, 45, 60, 90, 120, 100,
26
+ 65, 50, 70, 85, 110, 130, 75, 60, 45, 35,
27
+ ]
28
+ const revenue = [
29
+ 10, 15, 12, 18, 25, 22, 30, 35, 28, 40,
30
+ 45, 42, 50, 55, 60, 58, 65, 70, 68, 75,
31
+ ]
32
+ const expenses = [
33
+ 8, 10, 11, 14, 16, 18, 20, 22, 21, 25,
34
+ 28, 30, 32, 34, 36, 38, 40, 42, 41, 45,
35
+ ]
36
+ const temperature = [
37
+ 15, 14, 16, 18, 22, 25, 28, 30, 29, 27,
38
+ 24, 20, 17, 15, 14, 16, 19, 23, 26, 28,
39
+ ]
40
+ const humidity = [
41
+ 80, 82, 78, 72, 65, 58, 52, 48, 50, 55,
42
+ 62, 70, 75, 80, 83, 78, 72, 60, 54, 52,
43
+ ]
44
+
45
+ const sparseData = [
46
+ 0, 0, 15, 30, 0, 0, 50, 80, 0, 0,
47
+ 0, 45, 60, 0, 0, 0, 70, 90, 0, 0,
48
+ ]
49
+
50
+ const hourLabels = ['0h', '6h', '12h', '18h', '24h']
51
+ const monthLabels = ['Jan', 'Apr', 'Jul', 'Oct']
52
+
53
+ // ── Items ────────────────────────────────────────────────────────────
54
+
55
+ interface PairItem {
56
+ title: string
57
+ subtitle: string
58
+ left: {
59
+ variant: 'area' | 'filled' | 'striped'
60
+ series: Array<{ data: number[]; color?: Color.ColorLike }>
61
+ xLabels: string[]
62
+ yRange?: [number, number]
63
+ stripeColors?: [Color.ColorLike, Color.ColorLike]
64
+ }
65
+ right: {
66
+ variant: 'area' | 'filled' | 'striped'
67
+ series: Array<{ data: number[]; color?: Color.ColorLike }>
68
+ xLabels: string[]
69
+ yRange?: [number, number]
70
+ stripeColors?: [Color.ColorLike, Color.ColorLike]
71
+ }
72
+ markdown?: string
73
+ meta: Array<{ title: string; text: string; color?: Color.ColorLike }>
74
+ }
75
+
76
+ const items: PairItem[] = [
77
+ {
78
+ title: 'CPU vs Memory',
79
+ subtitle: 'Area + Filled side by side',
80
+ left: {
81
+ variant: 'area',
82
+ series: [{ data: cpuData, color: Color.Blue }],
83
+ xLabels: hourLabels,
84
+ yRange: [0, 100],
85
+ },
86
+ right: {
87
+ variant: 'filled',
88
+ series: [{ data: memData, color: Color.Green }],
89
+ xLabels: hourLabels,
90
+ yRange: [0, 100],
91
+ },
92
+ markdown: [
93
+ '## CPU vs Memory',
94
+ '',
95
+ 'Area chart (left) shows CPU with high variance.',
96
+ 'Filled chart (right) shows memory steadily climbing.',
97
+ ].join('\n'),
98
+ meta: [
99
+ { title: 'CPU Peak', text: '90%', color: Color.Blue },
100
+ { title: 'Mem Peak', text: '86%', color: Color.Green },
101
+ ],
102
+ },
103
+ {
104
+ title: 'Disk I/O',
105
+ subtitle: 'Read vs Write operations',
106
+ left: {
107
+ variant: 'filled',
108
+ series: [{ data: readOps, color: Color.Orange }],
109
+ xLabels: hourLabels,
110
+ },
111
+ right: {
112
+ variant: 'filled',
113
+ series: [{ data: writeOps, color: Color.Purple }],
114
+ xLabels: hourLabels,
115
+ },
116
+ meta: [
117
+ { title: 'Read Peak', text: '310 ops/s', color: Color.Orange },
118
+ { title: 'Write Peak', text: '130 ops/s', color: Color.Purple },
119
+ ],
120
+ },
121
+ {
122
+ title: 'Revenue vs Expenses',
123
+ subtitle: 'Striped comparison',
124
+ left: {
125
+ variant: 'striped',
126
+ series: [{ data: revenue }],
127
+ xLabels: monthLabels,
128
+ stripeColors: [Color.Green, Color.Blue],
129
+ },
130
+ right: {
131
+ variant: 'striped',
132
+ series: [{ data: expenses }],
133
+ xLabels: monthLabels,
134
+ stripeColors: [Color.Red, Color.Orange],
135
+ },
136
+ markdown: [
137
+ '## Revenue vs Expenses',
138
+ '',
139
+ 'Revenue growing faster than expenses.',
140
+ 'Profit margin widening over the year.',
141
+ '',
142
+ '- Revenue: **$10k** to **$75k**',
143
+ '- Expenses: **$8k** to **$45k**',
144
+ ].join('\n'),
145
+ meta: [
146
+ { title: 'Revenue', text: '$75k', color: Color.Green },
147
+ { title: 'Expenses', text: '$45k', color: Color.Red },
148
+ { title: 'Margin', text: '40%' },
149
+ ],
150
+ },
151
+ {
152
+ title: 'Weather Station',
153
+ subtitle: 'Temperature + Humidity',
154
+ left: {
155
+ variant: 'area',
156
+ series: [{ data: temperature, color: Color.Red }],
157
+ xLabels: hourLabels,
158
+ yRange: [10, 35],
159
+ },
160
+ right: {
161
+ variant: 'area',
162
+ series: [{ data: humidity, color: Color.Blue }],
163
+ xLabels: hourLabels,
164
+ yRange: [40, 90],
165
+ },
166
+ markdown: [
167
+ '## Weather Station',
168
+ '',
169
+ 'Temperature and humidity are **inversely correlated**.',
170
+ 'As temperature rises, humidity drops.',
171
+ ].join('\n'),
172
+ meta: [
173
+ { title: 'Temp Range', text: '14-30\u00B0C', color: Color.Red },
174
+ { title: 'Humidity', text: '48-83%', color: Color.Blue },
175
+ ],
176
+ },
177
+ {
178
+ title: 'Mixed Variants',
179
+ subtitle: 'Area left, Striped right',
180
+ left: {
181
+ variant: 'area',
182
+ series: [
183
+ { data: cpuData, color: Color.Blue },
184
+ { data: memData, color: Color.Magenta },
185
+ ],
186
+ xLabels: hourLabels,
187
+ yRange: [0, 100],
188
+ },
189
+ right: {
190
+ variant: 'striped',
191
+ series: [{ data: cpuData }],
192
+ xLabels: hourLabels,
193
+ yRange: [0, 100],
194
+ stripeColors: [Color.Yellow, Color.Red],
195
+ },
196
+ meta: [
197
+ { title: 'Left', text: 'area (2 series)' },
198
+ { title: 'Right', text: 'striped Yellow/Red' },
199
+ ],
200
+ },
201
+ {
202
+ title: 'Sparse Data (Zeros)',
203
+ subtitle: 'Filled vs Striped with zero values',
204
+ left: {
205
+ variant: 'filled',
206
+ series: [{ data: sparseData, color: Color.Red }],
207
+ xLabels: hourLabels,
208
+ yRange: [0, 100],
209
+ },
210
+ right: {
211
+ variant: 'striped',
212
+ series: [{ data: sparseData }],
213
+ xLabels: hourLabels,
214
+ yRange: [0, 100],
215
+ stripeColors: [Color.Blue, Color.Green],
216
+ },
217
+ markdown: [
218
+ '## Sparse Data',
219
+ '',
220
+ 'Data with many **zero values** should show a thin',
221
+ 'baseline line so bars are visible even at zero.',
222
+ ].join('\n'),
223
+ meta: [
224
+ { title: 'Zeros', text: '12 of 20' },
225
+ { title: 'Peak', text: '90' },
226
+ ],
227
+ },
228
+ ]
229
+
230
+ // ── Shared: render a graph from config ───────────────────────────────
231
+
232
+ function GraphFromConfig({ config, height }: {
233
+ config: PairItem['left']
234
+ height: number
235
+ }): any {
236
+ return (
237
+ <Graph
238
+ variant={config.variant}
239
+ height={height}
240
+ xLabels={config.xLabels}
241
+ yRange={config.yRange}
242
+ yTicks={4}
243
+ yFormat={(v) => v.toFixed(0)}
244
+ stripeColors={config.stripeColors}
245
+ >
246
+ {config.series.map((s, i) => {
247
+ return <Graph.Line key={i} data={s.data} color={s.color} />
248
+ })}
249
+ </Graph>
250
+ )
251
+ }
252
+
253
+ // ── Full Detail view ─────────────────────────────────────────────────
254
+
255
+ function GraphRowDetail({ item }: { item: PairItem }): any {
256
+ const { pop } = useNavigation()
257
+
258
+ const markdown = [
259
+ `# ${item.title}`,
260
+ '',
261
+ item.subtitle,
262
+ ...(item.markdown ? ['', item.markdown] : []),
263
+ ].join('\n')
264
+
265
+ return (
266
+ <Detail
267
+ navigationTitle={item.title}
268
+ markdown={markdown}
269
+ metadata={
270
+ <Detail.Metadata>
271
+ <Row gap={1}>
272
+ <GraphFromConfig config={item.left} height={12} />
273
+ <GraphFromConfig config={item.right} height={12} />
274
+ </Row>
275
+ <Detail.Metadata.Separator />
276
+ {item.meta.map((m) => {
277
+ return (
278
+ <Detail.Metadata.Label
279
+ key={m.title}
280
+ title={m.title}
281
+ text={m.color ? { value: m.text, color: m.color } : m.text}
282
+ />
283
+ )
284
+ })}
285
+ </Detail.Metadata>
286
+ }
287
+ actions={
288
+ <ActionPanel>
289
+ <Action title="Go Back" onAction={() => { pop() }} />
290
+ </ActionPanel>
291
+ }
292
+ />
293
+ )
294
+ }
295
+
296
+ // ── Main list ────────────────────────────────────────────────────────
297
+
298
+ function GraphRowExample() {
299
+ const { push } = useNavigation()
300
+
301
+ return (
302
+ <List navigationTitle="Graph Rows" isShowingDetail={true}>
303
+ {items.map((item) => {
304
+ return (
305
+ <List.Item
306
+ key={item.title}
307
+ title={item.title}
308
+ subtitle={item.subtitle}
309
+ detail={
310
+ <List.Item.Detail
311
+ markdown={item.markdown}
312
+ metadata={
313
+ <List.Item.Detail.Metadata>
314
+ <Row gap={1}>
315
+ <GraphFromConfig config={item.left} height={8} />
316
+ <GraphFromConfig config={item.right} height={8} />
317
+ </Row>
318
+ <List.Item.Detail.Metadata.Separator />
319
+ {item.meta.map((m) => {
320
+ return (
321
+ <List.Item.Detail.Metadata.Label
322
+ key={m.title}
323
+ title={m.title}
324
+ text={m.color ? { value: m.text, color: m.color } : m.text}
325
+ />
326
+ )
327
+ })}
328
+ </List.Item.Detail.Metadata>
329
+ }
330
+ />
331
+ }
332
+ actions={
333
+ <ActionPanel>
334
+ <Action
335
+ title="Open Detail"
336
+ onAction={() => { push(<GraphRowDetail item={item} />) }}
337
+ />
338
+ </ActionPanel>
339
+ }
340
+ />
341
+ )
342
+ })}
343
+ </List>
344
+ )
345
+ }
346
+
347
+ renderWithProviders(<GraphRowExample />)
@@ -0,0 +1,295 @@
1
+ import { test, expect, afterEach, beforeEach } from 'vitest'
2
+ import { launchTerminal, Session } from 'tuistory/src'
3
+
4
+ let session: Session
5
+
6
+ beforeEach(async () => {
7
+ session = await launchTerminal({
8
+ command: 'bun',
9
+ args: ['src/examples/graph-row.tsx'],
10
+ cols: 100,
11
+ rows: 30,
12
+ })
13
+ })
14
+
15
+ afterEach(() => {
16
+ session?.close()
17
+ })
18
+
19
+ test('side detail shows two graphs in a row', async () => {
20
+ const text = await session.text({
21
+ waitFor: (text) => {
22
+ return text.includes('CPU vs Memory') && text.includes('│')
23
+ },
24
+ timeout: 10000,
25
+ })
26
+
27
+ expect(text).toMatchInlineSnapshot(`
28
+ "
29
+
30
+
31
+ Graph Rows ───────────────────────────────────────────────────────────────────────────────────
32
+
33
+ > Search...
34
+
35
+ ›CPU vs Memory Area + Filled side by side │ CPU vs Memory
36
+ Disk I/O Read vs Write operations │
37
+ Revenue vs Expenses Striped comparison │ Area chart (left) shows CPU with high
38
+ Weather Station Temperature + Humidity │ variance.
39
+ Mixed Variants Area left, Striped right │ Filled chart (right) shows memory steadily
40
+ Sparse Data (Zeros) Filled vs Striped with zer │ climbing.
41
+
42
+ │ 100│ ⡀ 100│
43
+ │ │ ⡄ ⣼⣷⡀ │ ▄▄▀▀
44
+ │ 67│ ⣸⣿⡄ ⣸⣿⣿⣧ 67│ ▄▄▀▀▀▀▀▀▀
45
+ │ │ ⣼⣶⣿⣿⣷⡀ ⢰⣿⣿⣿⣿⣧ │ ▄▄▀▀▀▀▀▀▀▀▀▀▀▀
46
+ │ │ ⢸⣿⣿⣿⣿⣿⣷⣀⣿⣿⣿⣿⣿⣿⣇ │▄▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
47
+ │ 33│⣀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧ 33│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
48
+ │ │⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ │▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
49
+ │ 0│⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ 0│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
50
+ │ 0h 6h 12h 18h24h 0h 6h 12h 18h24h
51
+
52
+ │ ───────────────────────────────────────────
53
+
54
+ │ CPU Peak: 90%
55
+
56
+ ↵ open detail ↑↓ navigate ^k actions │ Mem Peak: 86%
57
+
58
+ "
59
+ `)
60
+
61
+ // Both graphs should render (braille for area, block for filled)
62
+ expect(text).toMatch(/[\u2800-\u28FF]/)
63
+ expect(text).toContain('CPU vs Memory')
64
+ }, 30000)
65
+
66
+ test('enter pushes full detail with two graphs', async () => {
67
+ await session.text({
68
+ waitFor: (text) => {
69
+ return text.includes('CPU vs Memory') && text.includes('│')
70
+ },
71
+ timeout: 10000,
72
+ })
73
+
74
+ await session.press('return')
75
+
76
+ const text = await session.text({
77
+ waitFor: (text) => {
78
+ return text.includes('CPU vs Memory') && text.includes('Area chart')
79
+ },
80
+ timeout: 5000,
81
+ })
82
+
83
+ expect(text).toMatchInlineSnapshot(`
84
+ "
85
+
86
+
87
+
88
+
89
+ CPU vs Memory ▀
90
+
91
+ Area + Filled side by side
92
+
93
+ CPU vs Memory
94
+
95
+ Area chart (left) shows CPU with high variance.
96
+ Filled chart (right) shows memory steadily climbing.
97
+
98
+ 100│ 100│
99
+ │ ⢀⣠⣴⡄ │ ▄
100
+ │ ⣠⣦ ⣠⣿⣿⣿⣿⣦ │ ▄▄▄▄▄▀▀▀▀▀
101
+ │ ⢠⣾⣿⣿⣷⡄ ⢀⣼⣿⣿⣿⣿⣿⣿⣷⡀ │ ▄▄▄▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
102
+ 67│ ⢀⡀ ⣰⣿⣿⣿⣿⣿⣿⡄ ⢀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀ 67│ ▄▄▄▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
103
+ │ ⢠⣿⣿⣷⣿⣿⣿⣿⣿⣿⣿⣿⡄ ⢀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄ │ ▄▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
104
+ │ ⣠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀ ⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄⡀ │ ▄▄▄▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
105
+ 33│ ⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄ 33│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
106
+ │⣀⣤⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷ │▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
107
+ │⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ │▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
108
+ │⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ │▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
109
+ 0│⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ 0│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
110
+
111
+
112
+ esc go back ^k actions ↵ Go Back powered by termcast.app
113
+
114
+ "
115
+ `)
116
+
117
+ expect(text).toContain('CPU vs Memory')
118
+ expect(text).toContain('Area chart')
119
+ }, 30000)
120
+
121
+ test('esc returns from detail to list', async () => {
122
+ await session.text({
123
+ waitFor: (text) => {
124
+ return text.includes('CPU vs Memory') && text.includes('│')
125
+ },
126
+ timeout: 10000,
127
+ })
128
+
129
+ await session.press('return')
130
+ await session.text({
131
+ waitFor: (text) => {
132
+ return text.includes('Area chart')
133
+ },
134
+ timeout: 5000,
135
+ })
136
+
137
+ await session.press('escape')
138
+
139
+ const text = await session.text({
140
+ waitFor: (text) => {
141
+ return text.includes('›CPU vs Memory') && text.includes('Graph Rows')
142
+ },
143
+ timeout: 5000,
144
+ })
145
+
146
+ expect(text).toMatchInlineSnapshot(`
147
+ "
148
+
149
+
150
+ Graph Rows ───────────────────────────────────────────────────────────────────────────────────
151
+
152
+ > Search...
153
+
154
+ ›CPU vs Memory Area + Filled side by side │ CPU vs Memory
155
+ Disk I/O Read vs Write operations │
156
+ Revenue vs Expenses Striped comparison │ Area chart (left) shows CPU with high
157
+ Weather Station Temperature + Humidity │ variance.
158
+ Mixed Variants Area left, Striped right │ Filled chart (right) shows memory steadily
159
+ Sparse Data (Zeros) Filled vs Striped with zer │ climbing.
160
+
161
+ │ 100│ ⡀ 100│
162
+ │ │ ⣠ ⢠⣾⣷⡀ │ ▄▄▀▀
163
+ │ 67│ ⣰⣿⣧ ⢀⣿⣿⣿⣇ 67│ ▄▄▀▀▀▀▀▀▀
164
+ │ │ ⢠⣷⣿⣿⣿⣆ ⣾⣿⣿⣿⣿⡄ │ ▄▄▀▀▀▀▀▀▀▀▀▀▀▀
165
+ │ │ ⢀⣿⣿⣿⣿⣿⣿⣆⣸⣿⣿⣿⣿⣿⣿⣄ │▄▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
166
+ │ 33│⣀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧ 33│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
167
+ │ │⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ │▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
168
+ │ 0│⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ 0│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
169
+ │ 0h 6h 12h 18h24h 0h 6h 12h 18h24h
170
+
171
+ │ ────────────────────────────────────────────
172
+
173
+ │ CPU Peak: 90%
174
+
175
+ ↵ open detail ↑↓ navigate ^k actions │ Mem Peak: 86%
176
+
177
+ "
178
+ `)
179
+
180
+ expect(text).toContain('›CPU vs Memory')
181
+ }, 30000)
182
+
183
+ test('sparse data with zeros shows baseline', async () => {
184
+ await session.text({
185
+ waitFor: (text) => {
186
+ return text.includes('Sparse Data')
187
+ },
188
+ timeout: 10000,
189
+ })
190
+
191
+ // Navigate to last item: "Sparse Data (Zeros)"
192
+ for (let i = 0; i < 5; i++) {
193
+ await session.press('down')
194
+ }
195
+
196
+ const text = await session.text({
197
+ waitFor: (text) => {
198
+ return text.includes('›Sparse Data')
199
+ },
200
+ timeout: 5000,
201
+ })
202
+
203
+ expect(text).toMatchInlineSnapshot(`
204
+ "
205
+
206
+
207
+ Graph Rows ───────────────────────────────────────────────────────────────────────────────────
208
+
209
+ > Search...
210
+
211
+ CPU vs Memory Area + Filled side by side │ Sparse Data
212
+ Disk I/O Read vs Write operations │
213
+ Revenue vs Expenses Striped comparison │ Data with many zero values should show a
214
+ Weather Station Temperature + Humidity │ thin
215
+ Mixed Variants Area left, Striped right │ baseline line so bars are visible even at
216
+ ›Sparse Data (Zeros) Filled vs Striped with zer │ zero.
217
+
218
+ │ 100│ ▄ 100│ ▄
219
+ │ │ ▄ ▄▀ │ ▄ ▄▀
220
+ │ 67│ ▀ ▀▀ 67│ ▀ ▀▀
221
+ │ │ ▀▀ ▄▀ ▀▀ │ ▀▀ ▄▀ ▀▀
222
+ │ │ ▀▀ ▀▀ ▀▀▄ │ ▀▀ ▀▀ ▀▀▄
223
+ │ 33│ ▄ ▀▀▀ ▀▀▄▀▀▀▀ 33│ ▄ ▀▀▀ ▀▀▄ ▀▀▀
224
+ │ │ ▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀ │ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀
225
+ │ 0│▁▀▀▀▀▀▀▀▁▀▀▀▀▀▀▀▀▁ 0│▁▀▀▀▀▀▀▀▀▀▀▀▁▀▀▀▁
226
+ │ 0h 6h 12h 18h24h 0h 6h 12h 18h24h
227
+
228
+ │ ────────────────────────────────────────────
229
+
230
+ │ Zeros: 12 of 20
231
+
232
+ ↵ open detail ↑↓ navigate ^k actions │ Peak: 90
233
+
234
+ "
235
+ `)
236
+
237
+ expect(text).toContain('›Sparse Data')
238
+ }, 30000)
239
+
240
+ test('navigate to striped pair', async () => {
241
+ await session.text({
242
+ waitFor: (text) => {
243
+ return text.includes('Revenue vs Expenses')
244
+ },
245
+ timeout: 10000,
246
+ })
247
+
248
+ // Navigate to 3rd item: "Revenue vs Expenses"
249
+ await session.press('down')
250
+ await session.press('down')
251
+
252
+ const text = await session.text({
253
+ waitFor: (text) => {
254
+ return text.includes('›Revenue vs Expenses')
255
+ },
256
+ timeout: 5000,
257
+ })
258
+
259
+ expect(text).toMatchInlineSnapshot(`
260
+ "
261
+
262
+
263
+ Graph Rows ───────────────────────────────────────────────────────────────────────────────────
264
+
265
+ > Search...
266
+
267
+ CPU vs Memory Area + Filled side by side │ Revenue vs Expenses ▲
268
+ Disk I/O Read vs Write operations │ █
269
+ ›Revenue vs Expenses Striped comparison │ Revenue growing faster than expenses. █
270
+ Weather Station Temperature + Humidity │ Profit margin widening over the year. █
271
+ Mixed Variants Area left, Striped right │ █
272
+ Sparse Data (Zeros) Filled vs Striped with zer │ - Revenue: $10k to $75k
273
+ │ - Expenses: $8k to $45k
274
+
275
+
276
+ │ 78│ ▄ 47│ ▄
277
+ │ │ ▄▀▀▀ │ ▄▄▀▀▀
278
+ │ 54│ ▄▀▀▀▀▀ 33│ ▄▄▀▀▀▀▀
279
+ │ │ ▄▄▀▀▀▀▀▀▀ │ ▄▀▀▀▀▀▀▀▀
280
+ │ │ ▄ ▀▀▀▀▀▀▀▀▀▀ │ ▄▄▀▀▀▀▀▀▀▀▀▀
281
+ │ 31│ ▄▀▀▀▀▀▀▀▀▀▀▀▀▀ 20│ ▄▀▀▀▀▀▀▀▀▀▀▀▀▀
282
+ │ │ ▄ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │ ▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
283
+ │ 7│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ 6│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
284
+ │ Jan Apr Jul Oct Jan Apr Jul Oct
285
+
286
+ │ ───────────────────────────────────────────
287
+
288
+ ↵ open detail ↑↓ navigate ^k actions │ Revenue: $75k ▼
289
+
290
+ "
291
+ `)
292
+
293
+ expect(text).toContain('›Revenue vs Expenses')
294
+ expect(text).toMatch(/[▄▀]/)
295
+ }, 30000)