@textui/widgets 0.1.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 (423) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +69 -0
  3. package/dist/chart/area-chart.d.ts +4 -0
  4. package/dist/chart/area-chart.d.ts.map +1 -0
  5. package/dist/chart/area-chart.js +3 -0
  6. package/dist/chart/bar-chart.d.ts +32 -0
  7. package/dist/chart/bar-chart.d.ts.map +1 -0
  8. package/dist/chart/bar-chart.js +32 -0
  9. package/dist/chart/gauge.d.ts +26 -0
  10. package/dist/chart/gauge.d.ts.map +1 -0
  11. package/dist/chart/gauge.js +26 -0
  12. package/dist/chart/heatmap.d.ts +13 -0
  13. package/dist/chart/heatmap.d.ts.map +1 -0
  14. package/dist/chart/heatmap.js +27 -0
  15. package/dist/chart/histogram.d.ts +12 -0
  16. package/dist/chart/histogram.d.ts.map +1 -0
  17. package/dist/chart/histogram.js +22 -0
  18. package/dist/chart/index.d.ts +20 -0
  19. package/dist/chart/index.d.ts.map +1 -0
  20. package/dist/chart/index.js +32 -0
  21. package/dist/chart/line-chart.d.ts +7 -0
  22. package/dist/chart/line-chart.d.ts.map +1 -0
  23. package/dist/chart/line-chart.js +75 -0
  24. package/dist/chart/shared.d.ts +34 -0
  25. package/dist/chart/shared.d.ts.map +1 -0
  26. package/dist/chart/shared.js +78 -0
  27. package/dist/chart/sparkline.d.ts +15 -0
  28. package/dist/chart/sparkline.d.ts.map +1 -0
  29. package/dist/chart/sparkline.js +22 -0
  30. package/dist/control/button.d.ts +27 -0
  31. package/dist/control/button.d.ts.map +1 -0
  32. package/dist/control/button.js +130 -0
  33. package/dist/control/checkbox.d.ts +10 -0
  34. package/dist/control/checkbox.d.ts.map +1 -0
  35. package/dist/control/checkbox.js +31 -0
  36. package/dist/control/index.d.ts +20 -0
  37. package/dist/control/index.d.ts.map +1 -0
  38. package/dist/control/index.js +36 -0
  39. package/dist/control/radio-group.d.ts +17 -0
  40. package/dist/control/radio-group.d.ts.map +1 -0
  41. package/dist/control/radio-group.js +32 -0
  42. package/dist/control/search-box.d.ts +7 -0
  43. package/dist/control/search-box.d.ts.map +1 -0
  44. package/dist/control/search-box.js +3 -0
  45. package/dist/control/select.d.ts +42 -0
  46. package/dist/control/select.d.ts.map +1 -0
  47. package/dist/control/select.js +166 -0
  48. package/dist/control/shared.d.ts +41 -0
  49. package/dist/control/shared.d.ts.map +1 -0
  50. package/dist/control/shared.js +1 -0
  51. package/dist/control/slider.d.ts +14 -0
  52. package/dist/control/slider.d.ts.map +1 -0
  53. package/dist/control/slider.js +45 -0
  54. package/dist/control/switch.d.ts +10 -0
  55. package/dist/control/switch.d.ts.map +1 -0
  56. package/dist/control/switch.js +32 -0
  57. package/dist/control/text-area.d.ts +87 -0
  58. package/dist/control/text-area.d.ts.map +1 -0
  59. package/dist/control/text-area.js +299 -0
  60. package/dist/control/text-input.d.ts +10 -0
  61. package/dist/control/text-input.d.ts.map +1 -0
  62. package/dist/control/text-input.js +129 -0
  63. package/dist/data/code-viewer.d.ts +71 -0
  64. package/dist/data/code-viewer.d.ts.map +1 -0
  65. package/dist/data/code-viewer.js +232 -0
  66. package/dist/data/feed.d.ts +43 -0
  67. package/dist/data/feed.d.ts.map +1 -0
  68. package/dist/data/feed.js +178 -0
  69. package/dist/data/index.d.ts +54 -0
  70. package/dist/data/index.d.ts.map +1 -0
  71. package/dist/data/index.js +187 -0
  72. package/dist/data/list.d.ts +31 -0
  73. package/dist/data/list.d.ts.map +1 -0
  74. package/dist/data/list.js +104 -0
  75. package/dist/data/log-viewer.d.ts +26 -0
  76. package/dist/data/log-viewer.d.ts.map +1 -0
  77. package/dist/data/log-viewer.js +69 -0
  78. package/dist/data/markdown-view.d.ts +31 -0
  79. package/dist/data/markdown-view.d.ts.map +1 -0
  80. package/dist/data/markdown-view.js +139 -0
  81. package/dist/data/pagination.d.ts +9 -0
  82. package/dist/data/pagination.d.ts.map +1 -0
  83. package/dist/data/pagination.js +18 -0
  84. package/dist/data/scroll-thumb.d.ts +15 -0
  85. package/dist/data/scroll-thumb.d.ts.map +1 -0
  86. package/dist/data/scroll-thumb.js +32 -0
  87. package/dist/data/shared.d.ts +12 -0
  88. package/dist/data/shared.d.ts.map +1 -0
  89. package/dist/data/shared.js +17 -0
  90. package/dist/data/tree.d.ts +35 -0
  91. package/dist/data/tree.d.ts.map +1 -0
  92. package/dist/data/tree.js +158 -0
  93. package/dist/decorations.d.ts +88 -0
  94. package/dist/decorations.d.ts.map +1 -0
  95. package/dist/decorations.js +135 -0
  96. package/dist/display/alert.d.ts +8 -0
  97. package/dist/display/alert.d.ts.map +1 -0
  98. package/dist/display/alert.js +12 -0
  99. package/dist/display/badge.d.ts +10 -0
  100. package/dist/display/badge.d.ts.map +1 -0
  101. package/dist/display/badge.js +21 -0
  102. package/dist/display/card.d.ts +8 -0
  103. package/dist/display/card.d.ts.map +1 -0
  104. package/dist/display/card.js +12 -0
  105. package/dist/display/empty-state.d.ts +10 -0
  106. package/dist/display/empty-state.d.ts.map +1 -0
  107. package/dist/display/empty-state.js +5 -0
  108. package/dist/display/error-state.d.ts +9 -0
  109. package/dist/display/error-state.d.ts.map +1 -0
  110. package/dist/display/error-state.js +7 -0
  111. package/dist/display/heading.d.ts +6 -0
  112. package/dist/display/heading.d.ts.map +1 -0
  113. package/dist/display/heading.js +8 -0
  114. package/dist/display/index.d.ts +25 -0
  115. package/dist/display/index.d.ts.map +1 -0
  116. package/dist/display/index.js +52 -0
  117. package/dist/display/key-value.d.ts +14 -0
  118. package/dist/display/key-value.d.ts.map +1 -0
  119. package/dist/display/key-value.js +11 -0
  120. package/dist/display/label.d.ts +6 -0
  121. package/dist/display/label.d.ts.map +1 -0
  122. package/dist/display/label.js +3 -0
  123. package/dist/display/marquee.d.ts +36 -0
  124. package/dist/display/marquee.d.ts.map +1 -0
  125. package/dist/display/marquee.js +55 -0
  126. package/dist/display/progress.d.ts +39 -0
  127. package/dist/display/progress.d.ts.map +1 -0
  128. package/dist/display/progress.js +46 -0
  129. package/dist/display/skeleton.d.ts +20 -0
  130. package/dist/display/skeleton.d.ts.map +1 -0
  131. package/dist/display/skeleton.js +20 -0
  132. package/dist/display/spinner.d.ts +7 -0
  133. package/dist/display/spinner.d.ts.map +1 -0
  134. package/dist/display/spinner.js +9 -0
  135. package/dist/display/status-dot.d.ts +8 -0
  136. package/dist/display/status-dot.d.ts.map +1 -0
  137. package/dist/display/status-dot.js +13 -0
  138. package/dist/display/timeline.d.ts +12 -0
  139. package/dist/display/timeline.d.ts.map +1 -0
  140. package/dist/display/timeline.js +22 -0
  141. package/dist/find.d.ts +74 -0
  142. package/dist/find.d.ts.map +1 -0
  143. package/dist/find.js +102 -0
  144. package/dist/form/danger-zone.d.ts +11 -0
  145. package/dist/form/danger-zone.d.ts.map +1 -0
  146. package/dist/form/danger-zone.js +15 -0
  147. package/dist/form/field.d.ts +19 -0
  148. package/dist/form/field.d.ts.map +1 -0
  149. package/dist/form/field.js +35 -0
  150. package/dist/form/form-actions.d.ts +11 -0
  151. package/dist/form/form-actions.d.ts.map +1 -0
  152. package/dist/form/form-actions.js +16 -0
  153. package/dist/form/form-section.d.ts +7 -0
  154. package/dist/form/form-section.d.ts.map +1 -0
  155. package/dist/form/form-section.js +5 -0
  156. package/dist/form/form.d.ts +7 -0
  157. package/dist/form/form.d.ts.map +1 -0
  158. package/dist/form/form.js +3 -0
  159. package/dist/form/index.d.ts +42 -0
  160. package/dist/form/index.d.ts.map +1 -0
  161. package/dist/form/index.js +140 -0
  162. package/dist/form/shared.d.ts +25 -0
  163. package/dist/form/shared.d.ts.map +1 -0
  164. package/dist/form/shared.js +9 -0
  165. package/dist/index.d.ts +26 -0
  166. package/dist/index.d.ts.map +1 -0
  167. package/dist/index.js +62 -0
  168. package/dist/layout/center.d.ts +7 -0
  169. package/dist/layout/center.d.ts.map +1 -0
  170. package/dist/layout/center.js +8 -0
  171. package/dist/layout/column.d.ts +3 -0
  172. package/dist/layout/column.d.ts.map +1 -0
  173. package/dist/layout/column.js +2 -0
  174. package/dist/layout/divider.d.ts +11 -0
  175. package/dist/layout/divider.d.ts.map +1 -0
  176. package/dist/layout/divider.js +13 -0
  177. package/dist/layout/grid.d.ts +10 -0
  178. package/dist/layout/grid.d.ts.map +1 -0
  179. package/dist/layout/grid.js +22 -0
  180. package/dist/layout/index.d.ts +19 -0
  181. package/dist/layout/index.d.ts.map +1 -0
  182. package/dist/layout/index.js +36 -0
  183. package/dist/layout/panel.d.ts +37 -0
  184. package/dist/layout/panel.d.ts.map +1 -0
  185. package/dist/layout/panel.js +71 -0
  186. package/dist/layout/row.d.ts +7 -0
  187. package/dist/layout/row.d.ts.map +1 -0
  188. package/dist/layout/row.js +2 -0
  189. package/dist/layout/scroll-view.d.ts +27 -0
  190. package/dist/layout/scroll-view.d.ts.map +1 -0
  191. package/dist/layout/scroll-view.js +93 -0
  192. package/dist/layout/splitter.d.ts +10 -0
  193. package/dist/layout/splitter.d.ts.map +1 -0
  194. package/dist/layout/splitter.js +9 -0
  195. package/dist/layout/stack.d.ts +7 -0
  196. package/dist/layout/stack.d.ts.map +1 -0
  197. package/dist/layout/stack.js +5 -0
  198. package/dist/navigation/breadcrumb.d.ts +14 -0
  199. package/dist/navigation/breadcrumb.d.ts.map +1 -0
  200. package/dist/navigation/breadcrumb.js +24 -0
  201. package/dist/navigation/index.d.ts +17 -0
  202. package/dist/navigation/index.d.ts.map +1 -0
  203. package/dist/navigation/index.js +30 -0
  204. package/dist/navigation/key-hints.d.ts +11 -0
  205. package/dist/navigation/key-hints.d.ts.map +1 -0
  206. package/dist/navigation/key-hints.js +13 -0
  207. package/dist/navigation/menu.d.ts +35 -0
  208. package/dist/navigation/menu.d.ts.map +1 -0
  209. package/dist/navigation/menu.js +88 -0
  210. package/dist/navigation/status-bar.d.ts +18 -0
  211. package/dist/navigation/status-bar.d.ts.map +1 -0
  212. package/dist/navigation/status-bar.js +12 -0
  213. package/dist/navigation/tabs.d.ts +20 -0
  214. package/dist/navigation/tabs.d.ts.map +1 -0
  215. package/dist/navigation/tabs.js +41 -0
  216. package/dist/navigation/toolbar.d.ts +14 -0
  217. package/dist/navigation/toolbar.d.ts.map +1 -0
  218. package/dist/navigation/toolbar.js +10 -0
  219. package/dist/navigation/wizard.d.ts +15 -0
  220. package/dist/navigation/wizard.d.ts.map +1 -0
  221. package/dist/navigation/wizard.js +17 -0
  222. package/dist/overlay/command-palette.d.ts +63 -0
  223. package/dist/overlay/command-palette.d.ts.map +1 -0
  224. package/dist/overlay/command-palette.js +415 -0
  225. package/dist/overlay/dialog.d.ts +16 -0
  226. package/dist/overlay/dialog.d.ts.map +1 -0
  227. package/dist/overlay/dialog.js +44 -0
  228. package/dist/overlay/index.d.ts +84 -0
  229. package/dist/overlay/index.d.ts.map +1 -0
  230. package/dist/overlay/index.js +157 -0
  231. package/dist/overlay/layer-scope.d.ts +21 -0
  232. package/dist/overlay/layer-scope.d.ts.map +1 -0
  233. package/dist/overlay/layer-scope.js +5 -0
  234. package/dist/overlay/path-picker.d.ts +14 -0
  235. package/dist/overlay/path-picker.d.ts.map +1 -0
  236. package/dist/overlay/path-picker.js +200 -0
  237. package/dist/overlay/prompt-dialog.d.ts +12 -0
  238. package/dist/overlay/prompt-dialog.d.ts.map +1 -0
  239. package/dist/overlay/prompt-dialog.js +29 -0
  240. package/dist/overlay/shared.d.ts +17 -0
  241. package/dist/overlay/shared.d.ts.map +1 -0
  242. package/dist/overlay/shared.js +17 -0
  243. package/dist/overlay/toast-host.d.ts +8 -0
  244. package/dist/overlay/toast-host.d.ts.map +1 -0
  245. package/dist/overlay/toast-host.js +19 -0
  246. package/dist/overlay/toast.d.ts +9 -0
  247. package/dist/overlay/toast.d.ts.map +1 -0
  248. package/dist/overlay/toast.js +20 -0
  249. package/dist/overlay/tooltip.d.ts +6 -0
  250. package/dist/overlay/tooltip.d.ts.map +1 -0
  251. package/dist/overlay/tooltip.js +11 -0
  252. package/dist/panel/index.d.ts +79 -0
  253. package/dist/panel/index.d.ts.map +1 -0
  254. package/dist/panel/index.js +256 -0
  255. package/dist/panel/resource-panel.d.ts +93 -0
  256. package/dist/panel/resource-panel.d.ts.map +1 -0
  257. package/dist/panel/resource-panel.js +213 -0
  258. package/dist/panel/shared.d.ts +6 -0
  259. package/dist/panel/shared.d.ts.map +1 -0
  260. package/dist/panel/shared.js +4 -0
  261. package/dist/shells/console-shell.d.ts +8 -0
  262. package/dist/shells/console-shell.d.ts.map +1 -0
  263. package/dist/shells/console-shell.js +30 -0
  264. package/dist/shells/index.d.ts +20 -0
  265. package/dist/shells/index.d.ts.map +1 -0
  266. package/dist/shells/index.js +61 -0
  267. package/dist/shells/paper-shell.d.ts +9 -0
  268. package/dist/shells/paper-shell.d.ts.map +1 -0
  269. package/dist/shells/paper-shell.js +23 -0
  270. package/dist/shells/plain-shell.d.ts +3 -0
  271. package/dist/shells/plain-shell.d.ts.map +1 -0
  272. package/dist/shells/plain-shell.js +11 -0
  273. package/dist/shells/shared.d.ts +12 -0
  274. package/dist/shells/shared.d.ts.map +1 -0
  275. package/dist/shells/shared.js +21 -0
  276. package/dist/shells/workbench-shell.d.ts +9 -0
  277. package/dist/shells/workbench-shell.d.ts.map +1 -0
  278. package/dist/shells/workbench-shell.js +43 -0
  279. package/dist/surface/bar-layout.d.ts +4 -0
  280. package/dist/surface/bar-layout.d.ts.map +1 -0
  281. package/dist/surface/bar-layout.js +7 -0
  282. package/dist/surface/index.d.ts +58 -0
  283. package/dist/surface/index.d.ts.map +1 -0
  284. package/dist/surface/index.js +48 -0
  285. package/dist/surface/inline-layout.d.ts +4 -0
  286. package/dist/surface/inline-layout.d.ts.map +1 -0
  287. package/dist/surface/inline-layout.js +7 -0
  288. package/dist/surface/mount-view.d.ts +7 -0
  289. package/dist/surface/mount-view.d.ts.map +1 -0
  290. package/dist/surface/mount-view.js +19 -0
  291. package/dist/surface/rail-layout.d.ts +4 -0
  292. package/dist/surface/rail-layout.d.ts.map +1 -0
  293. package/dist/surface/rail-layout.js +19 -0
  294. package/dist/surface/shared.d.ts +7 -0
  295. package/dist/surface/shared.d.ts.map +1 -0
  296. package/dist/surface/shared.js +1 -0
  297. package/dist/surface/single-layout.d.ts +4 -0
  298. package/dist/surface/single-layout.d.ts.map +1 -0
  299. package/dist/surface/single-layout.js +18 -0
  300. package/dist/surface/split-layout.d.ts +4 -0
  301. package/dist/surface/split-layout.d.ts.map +1 -0
  302. package/dist/surface/split-layout.js +7 -0
  303. package/dist/surface/stack-layout.d.ts +4 -0
  304. package/dist/surface/stack-layout.d.ts.map +1 -0
  305. package/dist/surface/stack-layout.js +22 -0
  306. package/dist/surface/surface-area.d.ts +16 -0
  307. package/dist/surface/surface-area.d.ts.map +1 -0
  308. package/dist/surface/surface-area.js +32 -0
  309. package/dist/surface/tabs-layout.d.ts +4 -0
  310. package/dist/surface/tabs-layout.d.ts.map +1 -0
  311. package/dist/surface/tabs-layout.js +24 -0
  312. package/dist/tone.d.ts +16 -0
  313. package/dist/tone.d.ts.map +1 -0
  314. package/dist/tone.js +45 -0
  315. package/dist/viewport.d.ts +30 -0
  316. package/dist/viewport.d.ts.map +1 -0
  317. package/dist/viewport.js +17 -0
  318. package/package.json +54 -0
  319. package/src/chart/area-chart.ts +9 -0
  320. package/src/chart/bar-chart.ts +74 -0
  321. package/src/chart/gauge.ts +60 -0
  322. package/src/chart/heatmap.ts +49 -0
  323. package/src/chart/histogram.ts +36 -0
  324. package/src/chart/index.ts +36 -0
  325. package/src/chart/line-chart.ts +100 -0
  326. package/src/chart/shared.ts +112 -0
  327. package/src/chart/sparkline.ts +46 -0
  328. package/src/control/button.ts +184 -0
  329. package/src/control/checkbox.ts +48 -0
  330. package/src/control/index.ts +40 -0
  331. package/src/control/radio-group.ts +60 -0
  332. package/src/control/search-box.ts +15 -0
  333. package/src/control/select.ts +230 -0
  334. package/src/control/shared.ts +41 -0
  335. package/src/control/slider.ts +63 -0
  336. package/src/control/switch.ts +39 -0
  337. package/src/control/text-area.ts +390 -0
  338. package/src/control/text-input.ts +150 -0
  339. package/src/data/code-viewer.ts +337 -0
  340. package/src/data/feed.ts +204 -0
  341. package/src/data/index.ts +243 -0
  342. package/src/data/list.ts +149 -0
  343. package/src/data/log-viewer.ts +105 -0
  344. package/src/data/markdown-view.ts +182 -0
  345. package/src/data/pagination.ts +31 -0
  346. package/src/data/scroll-thumb.ts +40 -0
  347. package/src/data/shared.ts +23 -0
  348. package/src/data/tree.ts +215 -0
  349. package/src/decorations.ts +176 -0
  350. package/src/display/alert.ts +28 -0
  351. package/src/display/badge.ts +40 -0
  352. package/src/display/card.ts +23 -0
  353. package/src/display/empty-state.ts +21 -0
  354. package/src/display/error-state.ts +23 -0
  355. package/src/display/heading.ts +17 -0
  356. package/src/display/index.ts +55 -0
  357. package/src/display/key-value.ts +32 -0
  358. package/src/display/label.ts +11 -0
  359. package/src/display/marquee.ts +88 -0
  360. package/src/display/progress.ts +92 -0
  361. package/src/display/skeleton.ts +34 -0
  362. package/src/display/spinner.ts +20 -0
  363. package/src/display/status-dot.ts +24 -0
  364. package/src/display/timeline.ts +47 -0
  365. package/src/find.ts +138 -0
  366. package/src/form/danger-zone.ts +34 -0
  367. package/src/form/field.ts +61 -0
  368. package/src/form/form-actions.ts +35 -0
  369. package/src/form/form-section.ts +16 -0
  370. package/src/form/form.ts +13 -0
  371. package/src/form/index.ts +191 -0
  372. package/src/form/shared.ts +36 -0
  373. package/src/index.ts +63 -0
  374. package/src/layout/center.ts +17 -0
  375. package/src/layout/column.ts +6 -0
  376. package/src/layout/divider.ts +31 -0
  377. package/src/layout/grid.ts +34 -0
  378. package/src/layout/index.ts +39 -0
  379. package/src/layout/panel.ts +114 -0
  380. package/src/layout/row.ts +11 -0
  381. package/src/layout/scroll-view.ts +115 -0
  382. package/src/layout/splitter.ts +24 -0
  383. package/src/layout/stack.ts +12 -0
  384. package/src/navigation/breadcrumb.ts +43 -0
  385. package/src/navigation/index.ts +33 -0
  386. package/src/navigation/key-hints.ts +25 -0
  387. package/src/navigation/menu.ts +134 -0
  388. package/src/navigation/status-bar.ts +43 -0
  389. package/src/navigation/tabs.ts +73 -0
  390. package/src/navigation/toolbar.ts +25 -0
  391. package/src/navigation/wizard.ts +43 -0
  392. package/src/overlay/command-palette.ts +485 -0
  393. package/src/overlay/dialog.ts +65 -0
  394. package/src/overlay/index.ts +232 -0
  395. package/src/overlay/layer-scope.ts +26 -0
  396. package/src/overlay/path-picker.ts +215 -0
  397. package/src/overlay/prompt-dialog.ts +45 -0
  398. package/src/overlay/shared.ts +19 -0
  399. package/src/overlay/toast-host.ts +30 -0
  400. package/src/overlay/toast.ts +35 -0
  401. package/src/overlay/tooltip.ts +17 -0
  402. package/src/panel/index.ts +317 -0
  403. package/src/panel/resource-panel.ts +304 -0
  404. package/src/panel/shared.ts +8 -0
  405. package/src/shells/console-shell.ts +47 -0
  406. package/src/shells/index.ts +66 -0
  407. package/src/shells/paper-shell.ts +31 -0
  408. package/src/shells/plain-shell.ts +19 -0
  409. package/src/shells/shared.ts +24 -0
  410. package/src/shells/workbench-shell.ts +62 -0
  411. package/src/surface/bar-layout.ts +11 -0
  412. package/src/surface/index.ts +53 -0
  413. package/src/surface/inline-layout.ts +11 -0
  414. package/src/surface/mount-view.ts +30 -0
  415. package/src/surface/rail-layout.ts +27 -0
  416. package/src/surface/shared.ts +7 -0
  417. package/src/surface/single-layout.ts +25 -0
  418. package/src/surface/split-layout.ts +11 -0
  419. package/src/surface/stack-layout.ts +32 -0
  420. package/src/surface/surface-area.ts +46 -0
  421. package/src/surface/tabs-layout.ts +30 -0
  422. package/src/tone.ts +48 -0
  423. package/src/viewport.ts +45 -0
@@ -0,0 +1,31 @@
1
+ import type { BoxProps } from '@textui/core';
2
+ import { defineComponent, h, useFocus, useInput, useTheme } from '@textui/core';
3
+
4
+ export interface PaginationProps extends BoxProps {
5
+ page: number;
6
+ pageCount: number;
7
+ total?: number;
8
+ onChange?(page: number): void;
9
+ }
10
+
11
+ export const Pagination = defineComponent<PaginationProps>('Pagination', (props) => {
12
+ const theme = useTheme();
13
+ const { page, pageCount, total, onChange, ...rest } = props;
14
+ const focus = useFocus({});
15
+
16
+ useInput(
17
+ (event) => {
18
+ if (event.name === 'left' && page > 1) { onChange?.(page - 1); return true; }
19
+ if (event.name === 'right' && page < pageCount) { onChange?.(page + 1); return true; }
20
+ return false;
21
+ },
22
+ { focusId: focus.id },
23
+ );
24
+
25
+ return h('box', { id: focus.id, direction: 'row', gap: 1, ...rest },
26
+ h('text', { content: theme.glyphs.chevronLeft, fg: page > 1 ? 'accent' : 'disabled' }),
27
+ h('text', { content: `${page} / ${pageCount}`, bold: focus.focused }),
28
+ h('text', { content: theme.glyphs.chevronRight, fg: page < pageCount ? 'accent' : 'disabled' }),
29
+ total !== undefined ? h('text', { content: `${total} items`, fg: 'muted' }) : null,
30
+ );
31
+ });
@@ -0,0 +1,40 @@
1
+ import type { RenderOutput } from '@textui/core';
2
+ import { defineComponent, h, useTheme } from '@textui/core';
3
+
4
+ /**
5
+ * A one-cell scrollbar.
6
+ *
7
+ * Exported because anything with a viewport wants the same one: two components
8
+ * drawing their own would drift, and the thumb is the only thing on screen
9
+ * that says how much of a file you are looking at.
10
+ */
11
+ export const ScrollThumb = defineComponent<{
12
+ total: number;
13
+ rows: number;
14
+ offset: number;
15
+ focused: boolean;
16
+ }>('ScrollThumb', ({ total, rows, offset, focused }) => {
17
+ const theme = useTheme();
18
+ // Not border glyphs: `left` and `right` are the same character in every
19
+ // border style, so a thumb drawn with one would be invisible against a
20
+ // track drawn with the other.
21
+ const thumbChar = theme.glyphs.progressFull;
22
+ const trackChar = theme.glyphs.progressEmpty;
23
+ const track = Math.max(1, rows);
24
+ const size = Math.max(1, Math.round((rows / Math.max(1, total)) * track));
25
+ const span = Math.max(0, track - size);
26
+ const position = total <= rows
27
+ ? 0
28
+ : Math.round((offset / Math.max(1, total - rows)) * span);
29
+
30
+ const cells: RenderOutput[] = [];
31
+ for (let i = 0; i < track; i++) {
32
+ const onThumb = i >= position && i < position + size;
33
+ cells.push(h('text', {
34
+ key: i,
35
+ content: onThumb ? thumbChar : trackChar,
36
+ fg: onThumb ? (focused ? 'focus' : 'border') : 'borderSubtle',
37
+ }));
38
+ }
39
+ return h('box', { width: 1, direction: 'column' }, ...cells);
40
+ });
@@ -0,0 +1,23 @@
1
+ import type { BoxProps } from '@textui/core';
2
+ import { defineComponent, h, useEffect, useMeasure, useTheme } from '@textui/core';
3
+
4
+ /**
5
+ * An entry that reports how tall it turned out to be.
6
+ *
7
+ * Through a ref rather than state, deliberately: a height arriving must not
8
+ * schedule a render, or every measurement would cause the next measurement.
9
+ */
10
+ export const FeedEntry = defineComponent<BoxProps & { onHeight(height: number): void }>(
11
+ 'FeedEntry',
12
+ (props) => {
13
+ const { onHeight, children, ...rest } = props;
14
+ const measured = useMeasure();
15
+ useEffect(() => { onHeight(measured.height); }, [measured.height]);
16
+ return h('box', { direction: 'column', ...rest }, children);
17
+ },
18
+ );
19
+
20
+ export const FeedScrollbar = defineComponent<Record<string, never>>('FeedScrollbar', () => {
21
+ const theme = useTheme();
22
+ return h('box', { width: 1, fill: theme.borderChars().left, fg: 'borderSubtle' });
23
+ });
@@ -0,0 +1,215 @@
1
+ import type { BoxProps, SemanticVariant } from '@textui/core';
2
+ import {
3
+ chorded,
4
+ defineComponent,
5
+ h,
6
+ useFocus,
7
+ useInput,
8
+ useMeasure,
9
+ useTheme,
10
+ } from '@textui/core';
11
+ import { usePanelState } from '../panel/index.js';
12
+ import { TONE } from '../tone.js';
13
+ import { viewportRows } from '../viewport.js';
14
+
15
+ export interface TreeNode {
16
+ id: string;
17
+ label: string;
18
+ icon?: string;
19
+ children?: TreeNode[];
20
+ /** Children exist but are not loaded yet. */
21
+ hasChildren?: boolean;
22
+ tone?: SemanticVariant;
23
+ meta?: string;
24
+ }
25
+
26
+ export interface TreeProps extends BoxProps {
27
+ nodes: TreeNode[];
28
+ selectedId?: string;
29
+ expandedIds?: string[];
30
+ onSelect?(id: string, node: TreeNode): void;
31
+ onActivate?(id: string, node: TreeNode): void;
32
+ onToggle?(id: string, expanded: boolean): void;
33
+ visibleRows?: number;
34
+ indent?: number;
35
+ /**
36
+ * What the expand mark looks like, when a chevron is not what it means.
37
+ *
38
+ * A file tree's twisty is not only "there is more here" - it is also the one
39
+ * thing on the row that says this is a folder, because a folder has no size
40
+ * beside it and nothing else distinguishes it. A caller that knows its rows
41
+ * are folders can say so; everything else gets the theme's chevrons.
42
+ */
43
+ twistyOpen?: string;
44
+ twistyClosed?: string;
45
+ /** Claim focus on mount, if nothing in this scope already has it. */
46
+ autoFocus?: boolean;
47
+ }
48
+
49
+ interface FlatNode {
50
+ node: TreeNode;
51
+ depth: number;
52
+ expanded: boolean;
53
+ expandable: boolean;
54
+ }
55
+
56
+ function flatten(nodes: TreeNode[], expanded: Set<string>, depth = 0, out: FlatNode[] = []): FlatNode[] {
57
+ for (const node of nodes) {
58
+ const expandable = Boolean(node.hasChildren || (node.children && node.children.length > 0));
59
+ const isExpanded = expanded.has(node.id);
60
+ out.push({ node, depth, expanded: isExpanded, expandable });
61
+ if (isExpanded && node.children) flatten(node.children, expanded, depth + 1, out);
62
+ }
63
+ return out;
64
+ }
65
+
66
+ export const Tree = defineComponent<TreeProps>('Tree', (props) => {
67
+ const theme = useTheme();
68
+ const {
69
+ nodes, selectedId, expandedIds, onSelect, onActivate, onToggle,
70
+ visibleRows, indent = 2, twistyOpen, twistyClosed, autoFocus, ...rest
71
+ } = props;
72
+
73
+ const focus = useFocus({ autoFocus });
74
+ const measured = useMeasure();
75
+ const treePage = viewportRows(props, measured, 10, { requested: visibleRows });
76
+ /*
77
+ * Uncontrolled expansion belongs to the panel when there is one: a JSON file
78
+ * opened as a structure, switched away from and switched back to, comes back
79
+ * open where it was rather than collapsed to the root. A tree in a sidebar
80
+ * is in no panel and keeps ordinary component state, which is what it had.
81
+ */
82
+ const [treeView, setTreeView] = usePanelState<{ expanded: string[]; selected: string | null }>(
83
+ { expanded: [], selected: null },
84
+ );
85
+ const internalExpanded = treeView.expanded;
86
+ const setInternalExpanded = (next: string[]): void => setTreeView({ expanded: next });
87
+ const internalSelected = treeView.selected;
88
+ const setInternalSelected = (next: string | null): void => setTreeView({ selected: next });
89
+
90
+ const expanded = new Set(expandedIds ?? internalExpanded);
91
+ const rows = flatten(nodes, expanded);
92
+ const currentId = selectedId ?? internalSelected ?? rows[0]?.node.id ?? null;
93
+ const index = Math.max(0, rows.findIndex((r) => r.node.id === currentId));
94
+
95
+ const select = (next: number): void => {
96
+ const row = rows[Math.max(0, Math.min(rows.length - 1, next))];
97
+ if (!row) return;
98
+ if (selectedId === undefined) setInternalSelected(row.node.id);
99
+ onSelect?.(row.node.id, row.node);
100
+ };
101
+
102
+ const toggle = (id: string, next: boolean): void => {
103
+ if (expandedIds === undefined) {
104
+ setInternalExpanded(next
105
+ ? [...internalExpanded, id]
106
+ : internalExpanded.filter((e) => e !== id));
107
+ }
108
+ onToggle?.(id, next);
109
+ };
110
+
111
+ useInput(
112
+ (event) => {
113
+ const row = rows[index];
114
+ if (!row || chorded(event)) return false;
115
+ switch (event.name) {
116
+ case 'up': select(index - 1); return true;
117
+ case 'down': select(index + 1); return true;
118
+ case 'pageup': select(index - treePage); return true;
119
+ case 'pagedown': select(index + treePage); return true;
120
+ case 'home': select(0); return true;
121
+ case 'end': select(rows.length - 1); return true;
122
+ case 'right':
123
+ // Right expands, and only moves inward once already expanded.
124
+ if (row.expandable && !row.expanded) toggle(row.node.id, true);
125
+ else select(index + 1);
126
+ return true;
127
+ case 'left':
128
+ if (row.expandable && row.expanded) toggle(row.node.id, false);
129
+ else {
130
+ const parent = rows.slice(0, index).reverse().find((r) => r.depth < row.depth);
131
+ if (parent) select(rows.indexOf(parent));
132
+ }
133
+ return true;
134
+ case 'enter':
135
+ if (row.expandable) toggle(row.node.id, !row.expanded);
136
+ onActivate?.(row.node.id, row.node);
137
+ return true;
138
+ default: return false;
139
+ }
140
+ },
141
+ { focusId: focus.id },
142
+ );
143
+
144
+ // As with the list: how many rows fit is a question for the layout, not for
145
+ // the data. A deep tree scrolls inside its pane instead of stretching it.
146
+ const shown = viewportRows(props, measured, rows.length, { requested: visibleRows });
147
+ const window = rows.length <= shown
148
+ ? rows
149
+ : rows.slice(
150
+ Math.max(0, Math.min(index - Math.floor(shown / 2), rows.length - shown)),
151
+ Math.max(0, Math.min(index - Math.floor(shown / 2), rows.length - shown)) + shown,
152
+ );
153
+
154
+ /*
155
+ * One glyph column, and a second only when something needs both.
156
+ *
157
+ * A row that cannot expand has no twisty, so its icon goes in the twisty's
158
+ * place - otherwise a file sits one column right of a folder at the same
159
+ * depth and reads as being inside it. That was exactly the bug: an explorer
160
+ * that puts the folder glyph *in* the twisty (which is what `twistyClosed`
161
+ * is for) had its files pushed two columns clear of their siblings the
162
+ * moment they grew icons of their own.
163
+ *
164
+ * A second column appears only for a tree where something expandable also
165
+ * has an icon - a chevron *and* a folder mark - and then it is reserved on
166
+ * every row, including the ones that leave it blank, because a column that
167
+ * comes and go by row is the same misalignment wearing a different hat.
168
+ * Measured over every row rather than the visible ones, so scrolling cannot
169
+ * shift the layout under you.
170
+ */
171
+ const iconColumn = rows.some((r) => r.expandable && r.node.icon !== undefined);
172
+
173
+ return h('box', { id: focus.id, role: 'tree', direction: 'column', ...rest },
174
+ ...window.map((row) => {
175
+ const active = row.node.id === currentId;
176
+ const twisty = row.expandable
177
+ ? (row.expanded ? (twistyOpen ?? theme.glyphs.chevronDown)
178
+ : (twistyClosed ?? theme.glyphs.chevronRight))
179
+ : (iconColumn ? ' ' : row.node.icon ?? ' ');
180
+ // The marker carries the node's colour only when it *is* the node's
181
+ // icon; a twisty is structure and takes the row's own colour.
182
+ const markerTone = !row.expandable && !iconColumn && row.node.tone !== undefined
183
+ ? TONE[row.node.tone]
184
+ : undefined;
185
+
186
+ return h('box', {
187
+ key: row.node.id,
188
+ role: 'treeitem',
189
+ label: row.node.label,
190
+ selected: active,
191
+ direction: 'row',
192
+ gap: 1,
193
+ bg: active && focus.focused ? 'selected' : active ? 'active' : undefined,
194
+ fg: active && focus.focused ? 'inverted' : undefined,
195
+ onClick: () => {
196
+ if (selectedId === undefined) setInternalSelected(row.node.id);
197
+ onSelect?.(row.node.id, row.node);
198
+ },
199
+ },
200
+ h('text', {
201
+ content: ' '.repeat(row.depth * indent) + twisty,
202
+ ...(markerTone !== undefined && !(active && focus.focused) ? { fg: markerTone } : {}),
203
+ }),
204
+ iconColumn
205
+ ? h('text', {
206
+ content: row.node.icon ?? ' ',
207
+ ...(row.node.tone !== undefined ? { fg: TONE[row.node.tone] } : {}),
208
+ })
209
+ : null,
210
+ h('text', { content: row.node.label, flex: 1, truncate: 'end' }),
211
+ row.node.meta ? h('text', { content: row.node.meta, fg: active ? undefined : 'muted' }) : null,
212
+ );
213
+ }),
214
+ );
215
+ });
@@ -0,0 +1,176 @@
1
+ import type { BindingPath, ReactiveStore, SemanticVariant, StyleColor } from '@textui/core';
2
+ import { useStoreSubtree, useRuntime, escapeSegment } from '@textui/core';
3
+
4
+ /**
5
+ * Marks on resources.
6
+ *
7
+ * A tree of files is the same tree whether or not anything has an opinion
8
+ * about the files in it - so "this one is modified", "this one has an error",
9
+ * "this one matched your search" cannot be properties of the explorer. They
10
+ * are contributions, and the explorer's job is to draw whatever arrived.
11
+ *
12
+ * They live in the store rather than behind a registry because a decoration is
13
+ * a *value that changes*, not a capability that is registered once: git
14
+ * refreshes, a build finishes, a search runs again. The explorer subscribes to
15
+ * one subtree and redraws; nothing has to be told about anything.
16
+ *
17
+ * Each contributor owns a source name and writes under it, which is what makes
18
+ * two contributors possible and what makes unloading one of them a single
19
+ * delete rather than a diff.
20
+ */
21
+
22
+ export const DECORATIONS_ROOT = '$/ui/decorations';
23
+
24
+ export interface ResourceDecoration {
25
+ /** A short mark at the end of the row. Wins over whatever the row had. */
26
+ badge?: string;
27
+ /** Colours the row, so a glance is enough. */
28
+ tone?: SemanticVariant;
29
+ /** Replaces the row's icon. */
30
+ icon?: string;
31
+ }
32
+
33
+ /** Where one contributor's marks live. */
34
+ export function decorationsPath(source: string): BindingPath {
35
+ return `${DECORATIONS_ROOT}/${escapeSegment(source)}` as BindingPath;
36
+ }
37
+
38
+ /**
39
+ * Publish one contributor's marks, as a whole.
40
+ *
41
+ * The whole set at once, deliberately: a decoration that is *gone* - the file
42
+ * was committed, the error was fixed - has to disappear, and a merge of
43
+ * updates leaves the old ones behind forever.
44
+ */
45
+ export function setDecorations(
46
+ store: ReactiveStore,
47
+ source: string,
48
+ byUri: Record<string, ResourceDecoration>,
49
+ ): void {
50
+ const escaped: Record<string, ResourceDecoration> = {};
51
+ for (const [uri, decoration] of Object.entries(byUri)) {
52
+ escaped[escapeSegment(uri)] = decoration;
53
+ }
54
+ store.set(decorationsPath(source), escaped);
55
+ }
56
+
57
+ export function clearDecorations(store: ReactiveStore, source: string): void {
58
+ store.set(decorationsPath(source), null);
59
+ }
60
+
61
+ /** Every contributor's mark for one resource, merged. */
62
+ export function decorationFor(store: ReactiveStore, uri: string): ResourceDecoration | null {
63
+ const all = store.get<Record<string, Record<string, ResourceDecoration>>>(
64
+ DECORATIONS_ROOT as BindingPath,
65
+ );
66
+ if (!all) return null;
67
+
68
+ const key = escapeSegment(uri);
69
+ let merged: ResourceDecoration | null = null;
70
+ for (const bySource of Object.values(all)) {
71
+ const found = bySource?.[key];
72
+ if (found) merged = { ...(merged ?? {}), ...found };
73
+ }
74
+ return merged;
75
+ }
76
+
77
+ /**
78
+ * A reader that redraws when anything changes.
79
+ *
80
+ * One subscription for the whole tree rather than one per row: a component
81
+ * cannot call a hook per item without its hook count moving as the list does,
82
+ * and the marks arrive as one refresh anyway.
83
+ */
84
+ export function useDecorations(): (uri: string) => ResourceDecoration | null {
85
+ const runtime = useRuntime();
86
+ useStoreSubtree(DECORATIONS_ROOT as BindingPath);
87
+ return (uri: string) => decorationFor(runtime.store, uri);
88
+ }
89
+
90
+ // ------------------------------------------------------------------ lines
91
+
92
+ /**
93
+ * Marks on lines, which is the same idea one level down.
94
+ *
95
+ * A gutter belongs to whatever is drawing the file, and what a line *means* -
96
+ * added since the last commit, covered by a test, carrying an error - belongs
97
+ * to whatever knows that. Same bargain as the tree: the editor draws a column
98
+ * of marks and has never heard of git.
99
+ *
100
+ * Kept per resource rather than per panel, because "this line is new" is true
101
+ * of the file and not of where you happen to be looking at it.
102
+ */
103
+ export const LINE_MARKS_ROOT = '$/ui/line-marks';
104
+
105
+ export type LineMark = 'added' | 'changed' | 'removed';
106
+
107
+ /** Line numbers count from zero, like an index, because a caret does. */
108
+ export type LineMarks = Record<number, LineMark>;
109
+
110
+ /**
111
+ * What a marked line looks like, in one cell.
112
+ *
113
+ * ASCII, so every terminal draws one - and here rather than in whichever
114
+ * component happened to need it first, because two renderers drawing the same
115
+ * mark differently is the same file looking like two files. It lived in the
116
+ * editor, which is why the *viewer* drew no marks at all: you turned the
117
+ * setting on, stayed in view mode, and nothing happened.
118
+ */
119
+ export const MARK_GLYPH: Record<LineMark, string> = {
120
+ added: '+',
121
+ changed: '~',
122
+ removed: '_',
123
+ };
124
+
125
+ export const MARK_TONE: Record<LineMark, StyleColor> = {
126
+ added: 'success',
127
+ changed: 'warning',
128
+ removed: 'danger',
129
+ };
130
+
131
+ export function lineMarksPath(source: string, uri: string): BindingPath {
132
+ return `${LINE_MARKS_ROOT}/${escapeSegment(source)}/${escapeSegment(uri)}` as BindingPath;
133
+ }
134
+
135
+ export function setLineMarks(
136
+ store: ReactiveStore,
137
+ source: string,
138
+ uri: string,
139
+ marks: LineMarks | null,
140
+ ): void {
141
+ store.set(lineMarksPath(source, uri), marks);
142
+ }
143
+
144
+ /** Drop everything one contributor said, wherever it said it. */
145
+ export function clearLineMarks(store: ReactiveStore, source: string): void {
146
+ store.set(`${LINE_MARKS_ROOT}/${escapeSegment(source)}` as BindingPath, null);
147
+ }
148
+
149
+ export function lineMarksFor(store: ReactiveStore, uri: string | null): LineMarks {
150
+ if (uri === null) return {};
151
+ const all = store.get<Record<string, Record<string, LineMarks>>>(
152
+ LINE_MARKS_ROOT as BindingPath,
153
+ );
154
+ if (!all) return {};
155
+
156
+ const key = escapeSegment(uri);
157
+ let merged: LineMarks = {};
158
+ for (const bySource of Object.values(all)) {
159
+ const found = bySource?.[key];
160
+ if (found) merged = { ...merged, ...found };
161
+ }
162
+ return merged;
163
+ }
164
+
165
+ /**
166
+ * The marks on one file, redrawn when they change.
167
+ *
168
+ * One subscription, and an empty object when nobody has said anything - so a
169
+ * renderer can ask unconditionally and draw no column at all when the answer
170
+ * is empty.
171
+ */
172
+ export function useLineMarks(uri: string | null): LineMarks {
173
+ const runtime = useRuntime();
174
+ useStoreSubtree(LINE_MARKS_ROOT as BindingPath);
175
+ return lineMarksFor(runtime.store, uri);
176
+ }
@@ -0,0 +1,28 @@
1
+ import type { BoxProps } from '@textui/core';
2
+ import { defineComponent, h, useTheme } from '@textui/core';
3
+
4
+ export interface AlertProps extends BoxProps {
5
+ tone?: 'info' | 'success' | 'warning' | 'danger';
6
+ title?: string;
7
+ message?: string;
8
+ }
9
+
10
+ export const Alert = defineComponent<AlertProps>('Alert', (props) => {
11
+ const theme = useTheme();
12
+ const { tone = 'info', title, message, children, ...rest } = props;
13
+ const icon = {
14
+ info: theme.glyphs.info,
15
+ success: theme.glyphs.check,
16
+ warning: theme.glyphs.warning,
17
+ danger: theme.glyphs.cross,
18
+ }[tone];
19
+
20
+ return h('box', { role: 'alert', direction: 'row', gap: 1, ...rest },
21
+ h('text', { content: icon, fg: tone }),
22
+ h('box', { direction: 'column', flex: 1 },
23
+ title ? h('text', { content: title, bold: true, fg: tone }) : null,
24
+ message ? h('text', { content: message, wrap: 'word' }) : null,
25
+ children,
26
+ ),
27
+ );
28
+ });
@@ -0,0 +1,40 @@
1
+ import type { BoxProps, SemanticVariant, SurfaceVariant } from '@textui/core';
2
+ import { defineComponent, h } from '@textui/core';
3
+ import { ON_TONE, TONE as TONE_COLOR } from '../tone.js';
4
+
5
+ export interface BadgeProps extends BoxProps {
6
+ label: string;
7
+ tone?: SemanticVariant;
8
+ variant?: SurfaceVariant;
9
+ /** Glyph before the label, so the badge reads without colour. */
10
+ icon?: string;
11
+ }
12
+
13
+ export const Badge = defineComponent<BadgeProps>('Badge', (props) => {
14
+ const { label, tone = 'default', variant = 'ghost', icon, ...rest } = props;
15
+ const color = TONE_COLOR[tone];
16
+
17
+ // `ghost` is chrome-less: the tone, and nothing around it. That is the
18
+ // default because it is the badge that fits in a sentence, and it is what
19
+ // `ghost` already means on Button - one word, one meaning, both components.
20
+ const style =
21
+ variant === 'solid' ? { bg: color, fg: ON_TONE[tone] } : { fg: color };
22
+
23
+ // A badge is inline: it sits in a row of text and must stay one line tall.
24
+ // The outline variant is therefore brackets rather than a box border - a
25
+ // three-row chip in a sentence is not an outline, it is a panel.
26
+ const brackets = variant === 'outline';
27
+
28
+ return h('box', {
29
+ direction: 'row',
30
+ gap: icon ? 1 : 0,
31
+ padding: variant === 'solid' ? [0, 1] : 0,
32
+ ...style,
33
+ ...rest,
34
+ },
35
+ brackets ? h('text', { content: '[' }) : null,
36
+ icon ? h('text', { content: icon }) : null,
37
+ h('text', { content: label }),
38
+ brackets ? h('text', { content: ']' }) : null,
39
+ );
40
+ });
@@ -0,0 +1,23 @@
1
+ import type { BoxProps } from '@textui/core';
2
+ import { defineComponent, h, useTheme } from '@textui/core';
3
+
4
+ export interface CardProps extends BoxProps {
5
+ title?: string;
6
+ subtitle?: string;
7
+ footer?: string;
8
+ }
9
+
10
+ export const Card = defineComponent<CardProps>('Card', (props) => {
11
+ const theme = useTheme();
12
+ const { title, subtitle, children, ...rest } = props;
13
+ return h('box', {
14
+ border: theme.border,
15
+ padding: theme.density === 'compact' ? 0 : [0, 1],
16
+ direction: 'column',
17
+ title,
18
+ ...rest,
19
+ },
20
+ subtitle ? h('text', { content: subtitle, fg: 'muted' }) : null,
21
+ children,
22
+ );
23
+ });
@@ -0,0 +1,21 @@
1
+ import type { BoxProps } from '@textui/core';
2
+ import { defineComponent, h } from '@textui/core';
3
+
4
+ export interface EmptyStateProps extends BoxProps {
5
+ title: string;
6
+ message?: string;
7
+ icon?: string;
8
+ /** Hint text: what the reader can do about it. */
9
+ hint?: string;
10
+ }
11
+
12
+ export const EmptyState = defineComponent<EmptyStateProps>('EmptyState', (props) => {
13
+ const { title, message, icon, hint, children, ...rest } = props;
14
+ return h('box', { direction: 'column', align: 'center', justify: 'center', flex: 1, gap: 0, ...rest },
15
+ icon ? h('text', { content: icon, fg: 'subtle' }) : null,
16
+ h('text', { content: title, bold: true, fg: 'muted' }),
17
+ message ? h('text', { content: message, fg: 'subtle', wrap: 'word', textAlign: 'center' }) : null,
18
+ hint ? h('text', { content: hint, fg: 'subtle', dim: true }) : null,
19
+ children,
20
+ );
21
+ });
@@ -0,0 +1,23 @@
1
+ import type { BoxProps } from '@textui/core';
2
+ import { defineComponent, h, useTheme } from '@textui/core';
3
+
4
+ export interface ErrorStateProps extends BoxProps {
5
+ title?: string;
6
+ error: unknown;
7
+ /** Command id offered as a retry. */
8
+ onRetry?: () => void;
9
+ }
10
+
11
+ export const ErrorState = defineComponent<ErrorStateProps>('ErrorState', (props) => {
12
+ const theme = useTheme();
13
+ const { title = 'Something went wrong', error, onRetry, ...rest } = props;
14
+ const message = error instanceof Error ? error.message : String(error);
15
+
16
+ return h('box', { role: 'alert', direction: 'column', gap: 1, padding: 1, ...rest },
17
+ h('box', { direction: 'row', gap: 1 },
18
+ h('text', { content: theme.glyphs.cross, fg: 'danger' }),
19
+ h('text', { content: title, bold: true, fg: 'danger' })),
20
+ h('text', { content: message, fg: 'muted', wrap: 'word' }),
21
+ onRetry ? h('text', { content: 'r retry', fg: 'subtle' }) : null,
22
+ );
23
+ });
@@ -0,0 +1,17 @@
1
+ import type { TextProps } from '@textui/core';
2
+ import { defineComponent, h } from '@textui/core';
3
+
4
+
5
+ export interface HeadingProps extends TextProps {
6
+ level?: 1 | 2 | 3;
7
+ }
8
+
9
+ export const Heading = defineComponent<HeadingProps>('Heading', ({ level = 1, ...props }) =>
10
+ h('text', {
11
+ role: 'heading',
12
+ bold: level <= 2,
13
+ dim: level === 3,
14
+ fg: level === 1 ? 'text' : level === 2 ? 'text' : 'muted',
15
+ ...props,
16
+ }),
17
+ );