@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,35 @@
1
+ import type { BoxProps, SemanticVariant } from '@textui/core';
2
+ import { defineComponent, h, useTheme } from '@textui/core';
3
+ import { Button } from '../control/index.js';
4
+ import { useFormContext } from './shared.js';
5
+
6
+ export interface FormActionsProps extends BoxProps {
7
+ submitLabel?: string;
8
+ cancelLabel?: string;
9
+ onCancel?(): void;
10
+ tone?: SemanticVariant;
11
+ /** Disable submit until something changed. */
12
+ requireDirty?: boolean;
13
+ }
14
+
15
+ export const FormActions = defineComponent<FormActionsProps>('FormActions', (props) => {
16
+ const theme = useTheme();
17
+ const form = useFormContext();
18
+ const {
19
+ submitLabel = 'Submit', cancelLabel = 'Cancel', onCancel,
20
+ tone = 'primary', requireDirty = false, ...rest
21
+ } = props;
22
+
23
+ const blocked = form.submitting || (requireDirty && !form.dirty);
24
+
25
+ return h('box', { direction: 'row', gap: 1, justify: 'end', ...rest },
26
+ onCancel ? h(Button, { label: cancelLabel, onPress: onCancel }) : null,
27
+ h(Button, {
28
+ label: form.submitting ? `${submitLabel}${theme.glyphs.ellipsis}` : submitLabel,
29
+ tone,
30
+ variant: 'solid',
31
+ disabled: blocked,
32
+ onPress: () => void form.submit(),
33
+ }),
34
+ );
35
+ });
@@ -0,0 +1,16 @@
1
+ import type { BoxProps } from '@textui/core';
2
+ import { defineComponent, h } from '@textui/core';
3
+
4
+ export interface FormSectionProps extends BoxProps {
5
+ title: string;
6
+ description?: string;
7
+ }
8
+
9
+ export const FormSection = defineComponent<FormSectionProps>('FormSection', (props) => {
10
+ const { title, description, children, ...rest } = props;
11
+ return h('box', { direction: 'column', ...rest },
12
+ h('text', { content: title, bold: true }),
13
+ description ? h('text', { content: description, fg: 'muted', wrap: 'word' }) : null,
14
+ h('box', { direction: 'column' }, children),
15
+ );
16
+ });
@@ -0,0 +1,13 @@
1
+ import type { BoxProps } from '@textui/core';
2
+ import { defineComponent, h } from '@textui/core';
3
+ import type { FormApi } from './shared.js';
4
+ import { FormContext } from './shared.js';
5
+
6
+ export interface FormProps extends BoxProps {
7
+ form: FormApi<Record<string, unknown>>;
8
+ }
9
+
10
+ export const Form = defineComponent<FormProps>('Form', ({ form, children, ...rest }) =>
11
+ h(FormContext.Provider, { value: form },
12
+ h('box', { role: 'form', direction: 'column', ...rest }, children)),
13
+ );
@@ -0,0 +1,191 @@
1
+ import type { ComponentDefinition } from '@textui/core';
2
+ import { useCallback, useMemo, useState } from '@textui/core';
3
+ import { DangerZone } from './danger-zone.js';
4
+ import { Field } from './field.js';
5
+ import { FormActions } from './form-actions.js';
6
+ import { FormSection } from './form-section.js';
7
+ import { Form } from './form.js';
8
+ import type { FormApi, FormErrors } from './shared.js';
9
+
10
+ /**
11
+ * Forms and validation.
12
+ *
13
+ * Validation runs on a whole values object rather than per field, because the
14
+ * rules people actually need are cross-field - "confirm must match password",
15
+ * "end must be after start" - and a per-field validator cannot see its
16
+ * siblings. Fields subscribe to the errors that name them.
17
+ */
18
+ export * from './danger-zone.js';
19
+ export * from './field.js';
20
+ export * from './form.js';
21
+ export * from './form-actions.js';
22
+ export * from './form-section.js';
23
+ export type { FormApi, FormErrors, FormState } from './shared.js';
24
+ export { useFormContext } from './shared.js';
25
+
26
+ export type Validator<T> = (values: T) => FormErrors<T> | null;
27
+
28
+ export interface UseFormOptions<T extends Record<string, unknown>> {
29
+ initialValues: T;
30
+ validate?: Validator<T>;
31
+ onSubmit?(values: T): void | Promise<void>;
32
+ }
33
+
34
+ export function useForm<T extends Record<string, unknown>>(
35
+ options: UseFormOptions<T>,
36
+ ): FormApi<T> {
37
+ const [values, setValuesState] = useState<T>(options.initialValues);
38
+ // Validate the initial values immediately: a form whose required fields are
39
+ // empty is not valid, and reporting it as valid until the first keystroke
40
+ // makes `form.valid` useless for enabling a submit button.
41
+ const [errors, setErrors] = useState<FormErrors<T>>(
42
+ () => options.validate?.(options.initialValues) ?? {},
43
+ );
44
+ const [touched, setTouched] = useState<Partial<Record<keyof T & string, boolean>>>({});
45
+ const [submitting, setSubmitting] = useState(false);
46
+ const [submitted, setSubmitted] = useState(false);
47
+ const [initial] = useState(options.initialValues);
48
+
49
+ const runValidation = useCallback(
50
+ (next: T): FormErrors<T> => options.validate?.(next) ?? {},
51
+ [options.validate],
52
+ );
53
+
54
+ const setValue = <K extends keyof T & string>(field: K, value: T[K]): void => {
55
+ const next = { ...values, [field]: value } as T;
56
+ setValuesState(next);
57
+ // Re-validate as they type, but only surface what they have touched.
58
+ setErrors(runValidation(next));
59
+ };
60
+
61
+ const dirty = useMemo(
62
+ () => JSON.stringify(values) !== JSON.stringify(initial),
63
+ [values, initial],
64
+ );
65
+
66
+ const api: FormApi<T> = {
67
+ values,
68
+ errors,
69
+ touched,
70
+ submitting,
71
+ submitted,
72
+ valid: Object.keys(errors).length === 0,
73
+ dirty,
74
+
75
+ setValue,
76
+ setValues(patch) {
77
+ const next = { ...values, ...patch } as T;
78
+ setValuesState(next);
79
+ setErrors(runValidation(next));
80
+ },
81
+ setError(field, message) {
82
+ setErrors({ ...errors, [field]: message } as FormErrors<T>);
83
+ },
84
+ touch(field) {
85
+ if (touched[field]) return;
86
+ setTouched({ ...touched, [field]: true });
87
+ },
88
+ reset(next) {
89
+ setValuesState(next ?? initial);
90
+ setErrors({});
91
+ setTouched({});
92
+ setSubmitted(false);
93
+ },
94
+ validate() {
95
+ const found = runValidation(values);
96
+ setErrors(found);
97
+ return Object.keys(found).length === 0;
98
+ },
99
+ async submit() {
100
+ const found = runValidation(values);
101
+ setErrors(found);
102
+ // Submitting reveals every error, touched or not.
103
+ setTouched(
104
+ Object.keys(values).reduce(
105
+ (acc, key) => ({ ...acc, [key]: true }),
106
+ {} as Partial<Record<keyof T & string, boolean>>,
107
+ ),
108
+ );
109
+ if (Object.keys(found).length > 0) return;
110
+
111
+ setSubmitting(true);
112
+ try {
113
+ await options.onSubmit?.(values);
114
+ setSubmitted(true);
115
+ } finally {
116
+ setSubmitting(false);
117
+ }
118
+ },
119
+ errorFor(field) {
120
+ return touched[field] || submitted ? errors[field] : undefined;
121
+ },
122
+ };
123
+
124
+ return api;
125
+ }
126
+
127
+ // ------------------------------------------------------------- validators
128
+
129
+ /** Small composable validators, because every form needs the same four. */
130
+ export const validators = {
131
+ required: (message = 'Required') =>
132
+ (value: unknown): string | undefined =>
133
+ value === undefined || value === null || value === '' ? message : undefined,
134
+
135
+ minLength: (n: number, message?: string) =>
136
+ (value: unknown): string | undefined =>
137
+ typeof value === 'string' && value.length < n
138
+ ? message ?? `Must be at least ${n} characters`
139
+ : undefined,
140
+
141
+ maxLength: (n: number, message?: string) =>
142
+ (value: unknown): string | undefined =>
143
+ typeof value === 'string' && value.length > n
144
+ ? message ?? `Must be at most ${n} characters`
145
+ : undefined,
146
+
147
+ pattern: (re: RegExp, message = 'Invalid format') =>
148
+ (value: unknown): string | undefined =>
149
+ typeof value === 'string' && value !== '' && !re.test(value) ? message : undefined,
150
+
151
+ range: (min: number, max: number, message?: string) =>
152
+ (value: unknown): string | undefined => {
153
+ const n = Number(value);
154
+ return Number.isNaN(n) || n < min || n > max
155
+ ? message ?? `Must be between ${min} and ${max}`
156
+ : undefined;
157
+ },
158
+ };
159
+
160
+ /**
161
+ * Build a whole-form validator from per-field rules.
162
+ *
163
+ * Returns the error map rather than `FormErrors | null`, so it composes: a
164
+ * caller can take the result, add a cross-field rule, and hand the whole thing
165
+ * to `useForm` without a null check in between.
166
+ */
167
+ export function fieldValidators<T extends Record<string, unknown>>(
168
+ rules: Partial<Record<keyof T & string, ((value: unknown) => string | undefined)[]>>,
169
+ ): (values: T) => FormErrors<T> {
170
+ return (values) => {
171
+ const errors: FormErrors<T> = {};
172
+ for (const [field, checks] of Object.entries(rules) as [keyof T & string, ((value: unknown) => string | undefined)[]][]) {
173
+ for (const check of checks ?? []) {
174
+ const message = check(values[field]);
175
+ if (message) {
176
+ errors[field] = message;
177
+ break;
178
+ }
179
+ }
180
+ }
181
+ return errors;
182
+ };
183
+ }
184
+
185
+ export const FORM_COMPONENTS: ComponentDefinition[] = [
186
+ { component: 'Form', category: 'form', renderer: { kind: 'function', render: Form }, role: 'form', description: 'Provides form state to its fields.' },
187
+ { component: 'Field', category: 'form', renderer: { kind: 'function', render: Field }, description: 'Label, control and error, aligned.' },
188
+ { component: 'FormSection', category: 'form', renderer: { kind: 'function', render: FormSection }, description: 'A titled group of fields.' },
189
+ { component: 'FormActions', category: 'form', renderer: { kind: 'function', render: FormActions }, description: 'Submit and cancel.' },
190
+ { component: 'DangerZone', category: 'form', renderer: { kind: 'function', render: DangerZone }, description: 'Destructive action, fenced off.' },
191
+ ];
@@ -0,0 +1,36 @@
1
+ import { createContext, useContext } from '@textui/core';
2
+
3
+ export type FormErrors<T> = Partial<Record<keyof T & string, string>>;
4
+
5
+ export interface FormState<T extends Record<string, unknown>> {
6
+ values: T;
7
+ errors: FormErrors<T>;
8
+ /** Fields the user has interacted with. Errors show only for these. */
9
+ touched: Partial<Record<keyof T & string, boolean>>;
10
+ submitting: boolean;
11
+ submitted: boolean;
12
+ valid: boolean;
13
+ dirty: boolean;
14
+ }
15
+
16
+ export interface FormApi<T extends Record<string, unknown>> extends FormState<T> {
17
+ setValue<K extends keyof T & string>(field: K, value: T[K]): void;
18
+ setValues(values: Partial<T>): void;
19
+ setError(field: keyof T & string, message: string | undefined): void;
20
+ touch(field: keyof T & string): void;
21
+ reset(values?: T): void;
22
+ validate(): boolean;
23
+ submit(): Promise<void>;
24
+ /** The error to show for a field: only after it has been touched. */
25
+ errorFor(field: keyof T & string): string | undefined;
26
+ }
27
+
28
+ export const FormContext = createContext<FormApi<Record<string, unknown>> | null>('Form', null);
29
+
30
+ export function useFormContext<T extends Record<string, unknown>>(): FormApi<T> {
31
+ const form = useContext(FormContext);
32
+ if (!form) {
33
+ throw new Error('[textui] a Field must be rendered inside a Form');
34
+ }
35
+ return form as unknown as FormApi<T>;
36
+ }
package/src/index.ts ADDED
@@ -0,0 +1,63 @@
1
+ import type { ComponentDefinition, TextUIApp, Disposable } from '@textui/core';
2
+ import { createBag, PRIMITIVES } from '@textui/core';
3
+ import { LAYOUT_COMPONENTS } from './layout/index.js';
4
+ import { DISPLAY_COMPONENTS } from './display/index.js';
5
+ import { CONTROL_COMPONENTS } from './control/index.js';
6
+ import { FORM_COMPONENTS } from './form/index.js';
7
+ import { DATA_COMPONENTS } from './data/index.js';
8
+ import { NAVIGATION_COMPONENTS } from './navigation/index.js';
9
+ import { OVERLAY_COMPONENTS } from './overlay/index.js';
10
+ import { CHART_COMPONENTS } from './chart/index.js';
11
+ import { SURFACE_COMPONENTS, BUILTIN_LAYOUTS } from './surface/index.js';
12
+ import { SHELL_COMPONENTS, BUILTIN_SHELLS } from './shells/index.js';
13
+ import { PANEL_COMPONENTS, panelCommands } from './panel/index.js';
14
+
15
+ export * from './layout/index.js';
16
+ export * from './display/index.js';
17
+ export * from './control/index.js';
18
+ export * from './form/index.js';
19
+ export * from './data/index.js';
20
+ export * from './navigation/index.js';
21
+ export * from './overlay/index.js';
22
+ export * from './chart/index.js';
23
+ export * from './surface/index.js';
24
+ export * from './shells/index.js';
25
+ export * from './panel/index.js';
26
+ export * from './decorations.js';
27
+ export * from './find.js';
28
+ export * from './viewport.js';
29
+ export * from './tone.js';
30
+
31
+ /** Every component the library ships, in one list. */
32
+ export const CATALOG: ComponentDefinition[] = [
33
+ ...PRIMITIVES,
34
+ ...LAYOUT_COMPONENTS,
35
+ ...DISPLAY_COMPONENTS,
36
+ ...CONTROL_COMPONENTS,
37
+ ...FORM_COMPONENTS,
38
+ ...DATA_COMPONENTS,
39
+ ...NAVIGATION_COMPONENTS,
40
+ ...OVERLAY_COMPONENTS,
41
+ ...CHART_COMPONENTS,
42
+ ...SURFACE_COMPONENTS,
43
+ ...SHELL_COMPONENTS,
44
+ ...PANEL_COMPONENTS,
45
+ ];
46
+
47
+ /**
48
+ * Register the catalog, the surface layouts and the built-in shells.
49
+ *
50
+ * One call, because a half-registered catalog fails at mount time rather than
51
+ * at boot - and a runtime miss is much harder to read than a boot error.
52
+ */
53
+ export function registerBuiltins(app: TextUIApp): Disposable {
54
+ const bag = createBag();
55
+ bag.add(app.components.registerMany(CATALOG));
56
+ for (const layout of BUILTIN_LAYOUTS) bag.add(app.layouts.register(layout));
57
+ for (const shell of BUILTIN_SHELLS) bag.add(app.shells.register(shell));
58
+ // The panel commands come with the panel: what they offer is read off the
59
+ // resource registry, so a host that mounts a panel has already said
60
+ // everything "open with" needs. Keys stay the host's choice.
61
+ for (const command of panelCommands(app)) bag.add(app.commands.register(command));
62
+ return bag;
63
+ }
@@ -0,0 +1,17 @@
1
+ import type { BoxProps } from '@textui/core';
2
+ import { defineComponent, h } from '@textui/core';
3
+
4
+ export interface CenterProps extends BoxProps {
5
+ /** Centre horizontally, vertically, or both. */
6
+ axis?: 'both' | 'horizontal' | 'vertical';
7
+ }
8
+
9
+ export const Center = defineComponent<CenterProps>('Center', ({ axis = 'both', ...props }) =>
10
+ h('box', {
11
+ flex: props.flex ?? 1,
12
+ direction: 'column',
13
+ justify: axis === 'horizontal' ? 'start' : 'center',
14
+ align: axis === 'vertical' ? 'stretch' : 'center',
15
+ ...props,
16
+ }),
17
+ );
@@ -0,0 +1,6 @@
1
+ import type { BoxProps } from '@textui/core';
2
+ import { defineComponent, h } from '@textui/core';
3
+
4
+ export const Column = defineComponent<BoxProps>('Column', (props) =>
5
+ h('box', { direction: 'column', ...props }),
6
+ );
@@ -0,0 +1,31 @@
1
+ import type { BoxProps } from '@textui/core';
2
+ import { defineComponent, h, useTheme } from '@textui/core';
3
+
4
+ export interface DividerProps extends Omit<BoxProps, 'direction'> {
5
+ /** A divider runs across the flow, so it names its own axis. */
6
+ direction?: 'horizontal' | 'vertical';
7
+ /** Text set into the rule. */
8
+ label?: string;
9
+ labelAlign?: 'left' | 'center' | 'right';
10
+ char?: string;
11
+ }
12
+
13
+ export const Divider = defineComponent<DividerProps>('Divider', (props) => {
14
+ const theme = useTheme();
15
+ const { direction = 'horizontal', label, labelAlign = 'left', char, ...rest } = props;
16
+ const chars = theme.borderChars();
17
+
18
+ if (direction === 'vertical') {
19
+ return h('box', { role: 'separator', width: 1, fill: char ?? chars.left, fg: 'border', ...rest });
20
+ }
21
+
22
+ if (!label) {
23
+ return h('box', { role: 'separator', height: 1, fill: char ?? chars.top, fg: 'border', ...rest });
24
+ }
25
+
26
+ return h('box', { direction: 'row', gap: 1, height: 1, ...rest },
27
+ labelAlign !== 'left' ? h('box', { flex: 1, fill: char ?? chars.top, fg: 'border' }) : null,
28
+ h('text', { content: label, fg: 'muted' }),
29
+ labelAlign !== 'right' ? h('box', { flex: 1, fill: char ?? chars.top, fg: 'border' }) : null,
30
+ );
31
+ });
@@ -0,0 +1,34 @@
1
+ import type { BoxProps } from '@textui/core';
2
+ import { defineComponent, h } from '@textui/core';
3
+
4
+ export interface GridProps extends BoxProps {
5
+ columns: number;
6
+ }
7
+
8
+ /**
9
+ * A grid is rows of equal-flex cells. Terminals have no sub-cell measurement,
10
+ * so an even split is the honest primitive; anything else is a Row with widths.
11
+ */
12
+ export const Grid = defineComponent<GridProps>('Grid', ({ columns, columnGap, rowGap, children, ...props }) => {
13
+ const items = Array.isArray(children) ? children : children === undefined ? [] : [children];
14
+ const rows: unknown[][] = [];
15
+ for (let i = 0; i < items.length; i += columns) rows.push(items.slice(i, i + columns));
16
+
17
+ return h('box', { direction: 'column', gap: rowGap ?? props.gap ?? 0, ...props },
18
+ ...rows.map((row, i) =>
19
+ h('box', { key: i, direction: 'row', gap: columnGap ?? props.gap ?? 1 },
20
+ // `basis: 0` is what makes the columns *equal* rather than merely
21
+ // flexible. Without it a cell starts at the width its content asked
22
+ // for and only the space left over is shared - so a pane holding one
23
+ // long unwrapped line takes two thirds of the row and the one beside
24
+ // it is squeezed to a column of single words. Equal columns is what
25
+ // this component says it is for; anything that wants to be sized by
26
+ // its content wants a `Row`.
27
+ ...row.map((cell, j) => h('box', { key: j, flex: 1, basis: 0, minWidth: 0 }, cell)),
28
+ // Pad the last row so its cells keep the same width as the others.
29
+ ...Array.from({ length: columns - row.length }, (_, k) =>
30
+ h('box', { key: `pad${k}`, flex: 1, basis: 0 })),
31
+ ),
32
+ ),
33
+ );
34
+ });
@@ -0,0 +1,39 @@
1
+ import type { ComponentDefinition } from '@textui/core';
2
+ import { Center } from './center.js';
3
+ import { Column } from './column.js';
4
+ import { Divider } from './divider.js';
5
+ import { Grid } from './grid.js';
6
+ import { Panel } from './panel.js';
7
+ import { Row } from './row.js';
8
+ import { ScrollView } from './scroll-view.js';
9
+ import { Splitter } from './splitter.js';
10
+ import { Stack } from './stack.js';
11
+
12
+ /**
13
+ * Layout and containers.
14
+ *
15
+ * Every one of these is `box` with an opinion. That is the point: the layout
16
+ * engine only ever sees the primitive, so a new container costs a function
17
+ * rather than a new case in the engine.
18
+ */
19
+ export * from './center.js';
20
+ export * from './column.js';
21
+ export * from './divider.js';
22
+ export * from './grid.js';
23
+ export * from './panel.js';
24
+ export * from './row.js';
25
+ export * from './scroll-view.js';
26
+ export * from './splitter.js';
27
+ export * from './stack.js';
28
+
29
+ export const LAYOUT_COMPONENTS: ComponentDefinition[] = [
30
+ { component: 'Row', category: 'layout', renderer: { kind: 'function', render: Row }, description: 'Horizontal flex container.' },
31
+ { component: 'Column', category: 'layout', renderer: { kind: 'function', render: Column }, description: 'Vertical flex container.' },
32
+ { component: 'Center', category: 'layout', renderer: { kind: 'function', render: Center }, description: 'Centres its children.' },
33
+ { component: 'Grid', category: 'layout', renderer: { kind: 'function', render: Grid }, description: 'Equal-width columns, wrapping into rows.' },
34
+ { component: 'Panel', category: 'layout', renderer: { kind: 'function', render: Panel }, description: 'Titled region. Bordered or airy, following the theme.', variants: ['bordered', 'plain'], role: 'region' },
35
+ { component: 'Divider', category: 'layout', renderer: { kind: 'function', render: Divider }, description: 'A rule, optionally labelled.', role: 'separator' },
36
+ { component: 'Stack', category: 'layout', renderer: { kind: 'function', render: Stack }, description: 'Column with themed spacing.' },
37
+ { component: 'ScrollView', category: 'layout', renderer: { kind: 'function', render: ScrollView }, description: 'Scrolling viewport with keyboard and wheel support.' },
38
+ { component: 'Splitter', category: 'layout', renderer: { kind: 'function', render: Splitter }, description: 'Two panes with a divider.' },
39
+ ];
@@ -0,0 +1,114 @@
1
+ import type { BorderSpec, BoxProps, StyleColor } from '@textui/core';
2
+ import { defineComponent, h, resolveEdges, useTheme } from '@textui/core';
3
+
4
+ export interface PanelProps extends BoxProps {
5
+ /** Section heading drawn into the border, or above a borderless panel. */
6
+ title?: string;
7
+ subtitle?: string;
8
+ /** Accent colour for the title. */
9
+ tone?: StyleColor;
10
+ /** Overrides the theme's default border. `'none'` gives an airy panel. */
11
+ border?: BorderSpec;
12
+ /**
13
+ * Right-aligned text beside the title, on the same row.
14
+ *
15
+ * The short thing that belongs next to a heading rather than under it: a
16
+ * count, a state, the shortcut that opens it. Optional, and most panels do
17
+ * not have one.
18
+ */
19
+ rightTitle?: string;
20
+ /**
21
+ * Right-aligned text on the *bottom* rule, where there is one.
22
+ *
23
+ * Not the title row - which is what this said for a long time while doing
24
+ * something else. A bordered panel put it in the footer and a borderless one
25
+ * put it beside the title, so the same prop meant two places depending on a
26
+ * different prop. `rightTitle` is the title row, in both; this is the
27
+ * bottom, and on a borderless panel there is no bottom to put it on.
28
+ */
29
+ meta?: string;
30
+ }
31
+
32
+ /**
33
+ * The workhorse container.
34
+ *
35
+ * A panel is the one component that has to look right in all three house
36
+ * styles, so it renders its title into the border when it has one and as a
37
+ * heading row when it does not - rather than forcing a border to have a title.
38
+ */
39
+ export const Panel = defineComponent<PanelProps>('Panel', (props) => {
40
+ const theme = useTheme();
41
+ const { title, subtitle, tone, meta, rightTitle, children, padding, ...rest } = props;
42
+
43
+ /*
44
+ * Padding is about the *body*, and a subtitle is not the body.
45
+ *
46
+ * It used to be one box: the border or the heading row, then the subtitle
47
+ * and the children together inside whatever padding the caller asked for. So
48
+ * `padding={1}` put a blank row between the title and its own caption and
49
+ * left the caption sitting directly on top of the content it was not part
50
+ * of - the subtitle read as the first line of the body. On a borderless
51
+ * panel it pushed the title down as well.
52
+ *
53
+ * Split, then: the horizontal padding stays outside and applies to
54
+ * everything, because a subtitle indented differently from the body under it
55
+ * is worse than either; the vertical padding wraps the children alone.
56
+ *
57
+ * Only when there is a subtitle to protect. Without one there is nothing to
58
+ * separate the padding from, and a panel that gained a box would be a panel
59
+ * whose layout changed for no reason.
60
+ */
61
+ const pad = resolveEdges(padding);
62
+ const split = subtitle !== undefined && (pad.top > 0 || pad.bottom > 0);
63
+ const outerPad = split ? { left: pad.left, right: pad.right } : padding;
64
+ const body = split
65
+ ? h('box', { direction: 'column', padding: { top: pad.top, bottom: pad.bottom } }, children)
66
+ : children;
67
+ const border = props.border ?? theme.border;
68
+ const borderless = border === 'none' || (typeof border === 'object' && border.style === 'none');
69
+
70
+ // A panel is a pane, so it fills its row rather than floating in the middle
71
+ // of it. `Row` centres its children by default, which is right for a row of
72
+ // labels and wrong for a row of panels sitting next to a taller one.
73
+ const fill = { alignSelf: 'stretch' as const };
74
+
75
+ if (!borderless) {
76
+ // Two rules, two labels: `rightTitle` on the top one beside the title, and
77
+ // `meta` on the bottom one. The border painter gives the right label its
78
+ // width first, so a long title truncates rather than running into it.
79
+ return h('box', {
80
+ role: 'region',
81
+ border,
82
+ title,
83
+ ...(rightTitle !== undefined ? { rightTitle } : {}),
84
+ footer: meta,
85
+ footerAlign: 'right',
86
+ ...fill,
87
+ ...(outerPad === undefined ? {} : { padding: outerPad }),
88
+ ...rest,
89
+ },
90
+ subtitle !== undefined ? h('text', { content: subtitle, fg: 'muted' }) : null,
91
+ body,
92
+ );
93
+ }
94
+
95
+ return h('box', {
96
+ role: 'region',
97
+ direction: 'column',
98
+ ...fill,
99
+ ...(outerPad === undefined ? {} : { padding: outerPad }),
100
+ ...rest,
101
+ border: 'none',
102
+ },
103
+ title
104
+ ? h('box', { direction: 'row', gap: 1 },
105
+ h('text', { content: title, bold: true, fg: tone ?? 'text', flex: 1, truncate: 'end' }),
106
+ // No border to write on, so the same two labels share the heading
107
+ // row - `rightTitle` first, because it is the one that must survive.
108
+ rightTitle ? h('text', { content: rightTitle, fg: 'muted' }) : null,
109
+ meta ? h('text', { content: meta, fg: 'muted' }) : null)
110
+ : null,
111
+ subtitle !== undefined ? h('text', { content: subtitle, fg: 'muted' }) : null,
112
+ body,
113
+ );
114
+ });
@@ -0,0 +1,11 @@
1
+ import type { BoxProps } from '@textui/core';
2
+ import { defineComponent, h } from '@textui/core';
3
+
4
+ export interface RowProps extends BoxProps {
5
+ /** Shorthand for `align`, which reads better on a row. */
6
+ vAlign?: BoxProps['align'];
7
+ }
8
+
9
+ export const Row = defineComponent<RowProps>('Row', ({ vAlign, ...props }) =>
10
+ h('box', { direction: 'row', align: vAlign ?? props.align ?? 'center', ...props }),
11
+ );