@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,55 @@
1
+ import type { ComponentDefinition } from '@textui/core';
2
+ import { Alert } from './alert.js';
3
+ import { Badge } from './badge.js';
4
+ import { Card } from './card.js';
5
+ import { EmptyState } from './empty-state.js';
6
+ import { ErrorState } from './error-state.js';
7
+ import { Heading } from './heading.js';
8
+ import { KeyValue } from './key-value.js';
9
+ import { Label } from './label.js';
10
+ import { Marquee } from './marquee.js';
11
+ import { Progress } from './progress.js';
12
+ import { Skeleton } from './skeleton.js';
13
+ import { Spinner } from './spinner.js';
14
+ import { StatusDot } from './status-dot.js';
15
+ import { Timeline } from './timeline.js';
16
+
17
+ /**
18
+ * Display components.
19
+ *
20
+ * The rule that shapes all of them: **meaning never depends on colour alone.**
21
+ * A degraded service is a different glyph as well as a different colour,
22
+ * because a 16-colour ssh session, a colourblind reader and a piped log all
23
+ * lose the colour and keep the glyph.
24
+ */
25
+ export * from './alert.js';
26
+ export * from './badge.js';
27
+ export * from './card.js';
28
+ export * from './empty-state.js';
29
+ export * from './error-state.js';
30
+ export * from './heading.js';
31
+ export * from './key-value.js';
32
+ export * from './label.js';
33
+ export * from './marquee.js';
34
+ export * from './progress.js';
35
+ export * from './skeleton.js';
36
+ export * from './spinner.js';
37
+ export * from './status-dot.js';
38
+ export * from './timeline.js';
39
+
40
+ export const DISPLAY_COMPONENTS: ComponentDefinition[] = [
41
+ { component: 'Heading', category: 'display', renderer: { kind: 'function', render: Heading }, role: 'heading', description: 'Section heading, three levels.' },
42
+ { component: 'Label', category: 'display', renderer: { kind: 'function', render: Label }, role: 'label', description: 'Secondary text with a semantic tone.' },
43
+ { component: 'Badge', category: 'display', renderer: { kind: 'function', render: Badge }, variants: ['solid', 'outline', 'ghost'], description: 'Small status marker; carries a glyph as well as a colour.' },
44
+ { component: 'StatusDot', category: 'display', renderer: { kind: 'function', render: StatusDot }, role: 'status', description: 'The shared status vocabulary: up, degraded, down.' },
45
+ { component: 'Alert', category: 'feedback', renderer: { kind: 'function', render: Alert }, role: 'alert', description: 'A message with an icon and a tone.' },
46
+ { component: 'Card', category: 'display', renderer: { kind: 'function', render: Card }, description: 'Bordered block with a title.' },
47
+ { component: 'KeyValue', category: 'data', renderer: { kind: 'function', render: KeyValue }, description: 'Aligned label/value pairs.' },
48
+ { component: 'Progress', category: 'feedback', renderer: { kind: 'function', render: Progress }, role: 'progressbar', description: 'Determinate or indeterminate bar, sub-cell resolution.' },
49
+ { component: 'Spinner', category: 'feedback', renderer: { kind: 'function', render: Spinner }, role: 'status', description: 'Animated activity indicator.' },
50
+ { component: 'Marquee', category: 'display', renderer: { kind: 'function', render: Marquee }, role: 'marquee', description: 'Text too long for its box, read by sliding it while it has the cursor.' },
51
+ { component: 'Skeleton', category: 'feedback', renderer: { kind: 'function', render: Skeleton }, description: 'Loading placeholder.' },
52
+ { component: 'EmptyState', category: 'feedback', renderer: { kind: 'function', render: EmptyState }, description: 'Nothing here, and what to do about it.' },
53
+ { component: 'ErrorState', category: 'feedback', renderer: { kind: 'function', render: ErrorState }, role: 'alert', description: 'A failure, with its message.' },
54
+ { component: 'Timeline', category: 'data', renderer: { kind: 'function', render: Timeline }, description: 'Ordered events down a rail.' },
55
+ ];
@@ -0,0 +1,32 @@
1
+ import type { BoxProps, SemanticVariant } from '@textui/core';
2
+ import { defineComponent, h, stringWidth } from '@textui/core';
3
+ import { TONE as TONE_COLOR } from '../tone.js';
4
+
5
+ export interface KeyValueProps extends BoxProps {
6
+ items: { label: string; value: string; tone?: SemanticVariant }[];
7
+ /** Cells reserved for labels. Computed from the longest when unset. */
8
+ labelWidth?: number;
9
+ columns?: number;
10
+ }
11
+
12
+ /** Structured data as aligned label/value pairs. */
13
+ export const KeyValue = defineComponent<KeyValueProps>('KeyValue', (props) => {
14
+ const { items, labelWidth, columns = 1, ...rest } = props;
15
+ const width = labelWidth ?? Math.max(0, ...items.map((i) => stringWidth(i.label)));
16
+
17
+ const rows: typeof items[] = [];
18
+ for (let i = 0; i < items.length; i += columns) rows.push(items.slice(i, i + columns));
19
+
20
+ return h('box', { direction: 'column', ...rest },
21
+ ...rows.map((row, i) =>
22
+ h('box', { key: i, direction: 'row', gap: 2 },
23
+ ...row.map((item, j) =>
24
+ h('box', { key: j, direction: 'row', gap: 1, flex: 1 },
25
+ h('box', { width }, h('text', { content: item.label, fg: 'muted' })),
26
+ h('text', { content: item.value, fg: item.tone ? TONE_COLOR[item.tone] : undefined, truncate: 'end', flex: 1 }),
27
+ ),
28
+ ),
29
+ ),
30
+ ),
31
+ );
32
+ });
@@ -0,0 +1,11 @@
1
+ import type { SemanticVariant, TextProps } from '@textui/core';
2
+ import { defineComponent, h } from '@textui/core';
3
+ import { TONE as TONE_COLOR } from '../tone.js';
4
+
5
+ export interface LabelProps extends TextProps {
6
+ tone?: SemanticVariant;
7
+ }
8
+
9
+ export const Label = defineComponent<LabelProps>('Label', ({ tone = 'muted', ...props }) =>
10
+ h('text', { role: 'label', fg: TONE_COLOR[tone], ...props }),
11
+ );
@@ -0,0 +1,88 @@
1
+ import type { TextProps } from '@textui/core';
2
+ import {
3
+ defineComponent,
4
+ h,
5
+ stringWidth,
6
+ useEffect,
7
+ useMeasure,
8
+ useRef,
9
+ useRuntime,
10
+ useState,
11
+ useTicker,
12
+ } from '@textui/core';
13
+
14
+ export interface MarqueeProps extends TextProps {
15
+ content: string;
16
+ /**
17
+ * Slide, or rest.
18
+ *
19
+ * Off is the resting state and the common one: a list of twenty rows is
20
+ * twenty still labels, and the one under the cursor is the one that moves.
21
+ * It is also what keeps the cost honest - a marquee that is not sliding
22
+ * holds no ticker at all, so a menu is one animation and not one per row.
23
+ */
24
+ active?: boolean;
25
+ /** Cells a second. */
26
+ speed?: number;
27
+ /** How long it waits at each end, in milliseconds. */
28
+ dwell?: number;
29
+ fps?: number;
30
+ }
31
+
32
+ /**
33
+ * Text too long for its box, read by sliding it.
34
+ *
35
+ * The alternative was making something else give up room, and in a list there
36
+ * is nothing else to take it from: a row is a label and a description and both
37
+ * of them are the answer to "what is this". Truncating is fine for the rows you
38
+ * are scanning past and useless for the one you have stopped on - which is
39
+ * exactly the row a marquee costs anything for.
40
+ *
41
+ * Rests at the start, slides to the end, waits, and comes back. Not a loop
42
+ * that wraps around: text that reappears from the right while its own tail is
43
+ * still leaving reads as two strings rather than one long one.
44
+ *
45
+ * Nothing moves where animation is off - a pipe, a CI log, a reader who asked
46
+ * for stillness - and there it is an ordinary truncated label.
47
+ */
48
+ export const Marquee = defineComponent<MarqueeProps>('Marquee', (props) => {
49
+ const { content, active = false, speed = 8, dwell = 900, fps = 10, ...rest } = props;
50
+ const runtime = useRuntime();
51
+ // What the last layout gave this text. Zero on the first pass, which is the
52
+ // one where nothing is known and the honest answer is the whole string.
53
+ const width = useMeasure().width;
54
+ const travel = width > 0 ? Math.max(0, stringWidth(content) - width) : 0;
55
+ const sliding = active && travel > 0 && !runtime.animation.disabled;
56
+
57
+ // The ticker's own frame number, not a count of callbacks. A clock driven
58
+ // by hand - a test, a static render - advances several frames in one call,
59
+ // and a counter that adds one per call runs at whatever rate it was driven
60
+ // at rather than at the rate it asked for.
61
+ const [frame, setFrame] = useState(0);
62
+ const seen = useRef(0);
63
+ const from = useRef(0);
64
+ useTicker((at) => { seen.current = at; setFrame(at - from.current); }, { fps, enabled: sliding });
65
+ // Back to the start whenever it stops or the words change, so the row you
66
+ // leave is the row you saw when you arrived at it.
67
+ useEffect(() => { from.current = seen.current; setFrame(0); }, [sliding, content]);
68
+
69
+ if (!sliding) return h('text', { content, truncate: 'end', ...rest });
70
+
71
+ const perCell = Math.max(1, Math.round(fps / speed));
72
+ const hold = Math.max(1, Math.round((dwell / 1000) * fps));
73
+ const slide = travel * perCell;
74
+ const cycle = hold + slide + hold + slide;
75
+ const at = frame % cycle;
76
+ const offset = at < hold
77
+ ? 0
78
+ : at < hold + slide
79
+ ? Math.floor((at - hold) / perCell)
80
+ : at < hold + slide + hold
81
+ ? travel
82
+ : travel - Math.floor((at - hold - slide - hold) / perCell);
83
+
84
+ // Pinned to what it measured. Without this the sliced string is narrower
85
+ // than the whole one, the layout hands the box back a different width, and
86
+ // the next frame measures something new - a row that shivers.
87
+ return h('text', { content: content.slice(offset, offset + width), width, ...rest });
88
+ });
@@ -0,0 +1,92 @@
1
+ import type { BoxProps, SemanticVariant } from '@textui/core';
2
+ import { defineComponent, h, useFrame, useTheme } from '@textui/core';
3
+ import { TONE as TONE_COLOR } from '../tone.js';
4
+
5
+ export interface ProgressProps extends BoxProps {
6
+ /** 0..1. Omit for an indeterminate bar. */
7
+ value?: number;
8
+ total?: number;
9
+ label?: string;
10
+ /** Show the percentage after the bar. */
11
+ showValue?: boolean;
12
+ tone?: SemanticVariant;
13
+ barWidth?: number;
14
+ /**
15
+ * A fixed gutter for the label, so a stack of bars starts at one column.
16
+ *
17
+ * Labels are their own width otherwise, which is right for one bar and
18
+ * wrong for three: "download", "index" and "working" each push their track
19
+ * to a different place and the group reads as three unrelated widgets.
20
+ * Nothing here can measure its siblings, so whoever stacks them says.
21
+ */
22
+ labelWidth?: number;
23
+ /**
24
+ * Push the bar away from the label, to the right edge of the row.
25
+ *
26
+ * The label's cell stretches, so it stays at the left and
27
+ * the track ends up hard against the right - which is what makes a column of
28
+ * these read as a table rather than as a ragged stack.
29
+ *
30
+ * Different from `labelWidth`, which pads the *label* to a fixed gutter: use
31
+ * that when the bars should start at one column, and this when they should
32
+ * end at one. Needs a row wider than its contents to have any effect, so the
33
+ * caller has to have given it a width or a `flex`.
34
+ */
35
+ spacer?: boolean;
36
+ }
37
+
38
+ /**
39
+ * A progress bar with sub-cell resolution: the partial block glyphs give eight
40
+ * steps per cell, so a 20-cell bar moves smoothly rather than in 5% jumps.
41
+ */
42
+ export const Progress = defineComponent<ProgressProps>('Progress', (props) => {
43
+ const theme = useTheme();
44
+ const {
45
+ value, total = 1, label, showValue = true, tone = 'primary', barWidth, labelWidth,
46
+ spacer, ...rest
47
+ } = props;
48
+ const frame = useFrame(8);
49
+
50
+ const width = barWidth ?? 20;
51
+ const ratio = value === undefined ? 0 : Math.max(0, Math.min(1, value / total));
52
+
53
+ let bar: string;
54
+ if (value === undefined) {
55
+ // Indeterminate: a block that travels the track.
56
+ const pos = frame % Math.max(1, width);
57
+ bar = Array.from({ length: width }, (_, i) =>
58
+ Math.abs(i - pos) < 2 ? theme.glyphs.progressFull : theme.glyphs.progressEmpty,
59
+ ).join('');
60
+ } else {
61
+ const exact = ratio * width;
62
+ const full = Math.floor(exact);
63
+ const partials = theme.glyphs.progressPartial;
64
+ const remainder = exact - full;
65
+ const partialIndex = Math.floor(remainder * partials.length);
66
+ const partial = full < width && remainder > 0 ? (partials[partialIndex] ?? '') : '';
67
+ bar =
68
+ theme.glyphs.progressFull.repeat(full) +
69
+ partial +
70
+ theme.glyphs.progressEmpty.repeat(Math.max(0, width - full - (partial ? 1 : 0)));
71
+ }
72
+
73
+ return h('box', { role: 'progressbar', label, direction: 'row', gap: 1, ...rest },
74
+ label !== undefined
75
+ ? h('text', {
76
+ content: label,
77
+ fg: 'muted',
78
+ ...(labelWidth === undefined ? {} : { width: labelWidth, truncate: 'end' as const }),
79
+ // The label's own cell stretches, rather than a `spacer` child going
80
+ // in beside it. A child would be free but its *gap* would not: with
81
+ // `gap: 1` on the row, one more child is one more cell, so a row
82
+ // sized exactly to its contents would truncate the bar to make room
83
+ // for a space. Stretching what is already there costs nothing.
84
+ ...(spacer === true ? { flex: 1 } : {}),
85
+ })
86
+ : spacer === true ? h('spacer', {}) : null,
87
+ h('text', { content: bar, fg: TONE_COLOR[tone] }),
88
+ showValue && value !== undefined
89
+ ? h('text', { content: `${Math.round(ratio * 100)}%`, fg: 'muted' })
90
+ : null,
91
+ );
92
+ });
@@ -0,0 +1,34 @@
1
+ import type { BoxProps } from '@textui/core';
2
+ import { defineComponent, h } from '@textui/core';
3
+
4
+ export interface SkeletonProps extends BoxProps {
5
+ lines?: number;
6
+ /** Width of each line, in cells or as a fraction of the box. */
7
+ widths?: number[];
8
+ }
9
+
10
+ /**
11
+ * The shape of content that has not arrived.
12
+ *
13
+ * Drawn in `borderStrong` rather than `borderSubtle`, and the difference is
14
+ * whether it is there at all: against the dark theme's canvas, subtle is
15
+ * 1.24:1 - the glyphs are in the buffer and nobody can see them, which is a
16
+ * placeholder that fails at the one thing it does. Strong is 2.28:1, still
17
+ * quiet enough not to read as content.
18
+ *
19
+ * `fg` overrides it, because how loud a placeholder should be depends on what
20
+ * it is standing in for.
21
+ */
22
+ export const Skeleton = defineComponent<SkeletonProps>('Skeleton', ({ lines = 3, widths, fg, ...rest }) =>
23
+ h('box', { direction: 'column', gap: 0, ...rest },
24
+ ...Array.from({ length: lines }, (_, i) =>
25
+ h('box', {
26
+ key: i,
27
+ height: 1,
28
+ width: widths?.[i] ?? (i === lines - 1 ? '60%' : '100%'),
29
+ fill: '░',
30
+ fg: fg ?? 'borderStrong',
31
+ }),
32
+ ),
33
+ ),
34
+ );
@@ -0,0 +1,20 @@
1
+ import type { BoxProps, SemanticVariant } from '@textui/core';
2
+ import { defineComponent, h, useFrame, useTheme } from '@textui/core';
3
+ import { TONE as TONE_COLOR } from '../tone.js';
4
+
5
+ export interface SpinnerProps extends BoxProps {
6
+ label?: string;
7
+ tone?: SemanticVariant;
8
+ }
9
+
10
+ export const Spinner = defineComponent<SpinnerProps>('Spinner', ({ label, tone = 'accent', ...rest }) => {
11
+ const theme = useTheme();
12
+ const frame = useFrame(10);
13
+ const frames = theme.glyphs.spinner;
14
+ const glyph = frames[frame % frames.length] ?? frames[0] ?? '*';
15
+
16
+ return h('box', { role: 'status', direction: 'row', gap: 1, ...rest },
17
+ h('text', { content: glyph, fg: TONE_COLOR[tone] }),
18
+ label ? h('text', { content: label }) : null,
19
+ );
20
+ });
@@ -0,0 +1,24 @@
1
+ import type { BoxProps, StyleColor } from '@textui/core';
2
+ import { defineComponent, h, useTheme } from '@textui/core';
3
+
4
+ export interface StatusDotProps extends BoxProps {
5
+ status: 'up' | 'down' | 'degraded' | 'unknown' | 'pending';
6
+ label?: string;
7
+ }
8
+
9
+ /** The status vocabulary, so "degraded" looks the same everywhere. */
10
+ export const StatusDot = defineComponent<StatusDotProps>('StatusDot', ({ status, label, ...rest }) => {
11
+ const theme = useTheme();
12
+ const map = {
13
+ up: { glyph: theme.glyphs.bulletFilled, fg: 'success' as StyleColor },
14
+ degraded: { glyph: theme.glyphs.bulletHalf, fg: 'warning' as StyleColor },
15
+ down: { glyph: theme.glyphs.bulletHollow, fg: 'danger' as StyleColor },
16
+ pending: { glyph: theme.glyphs.bulletHollow, fg: 'info' as StyleColor },
17
+ unknown: { glyph: theme.glyphs.separator, fg: 'muted' as StyleColor },
18
+ }[status];
19
+
20
+ return h('box', { role: 'status', label: label ?? status, direction: 'row', gap: 1, ...rest },
21
+ h('text', { content: map.glyph, fg: map.fg }),
22
+ label ? h('text', { content: label }) : null,
23
+ );
24
+ });
@@ -0,0 +1,47 @@
1
+ import type { BoxProps, SemanticVariant } from '@textui/core';
2
+ import { defineComponent, h, useTheme } from '@textui/core';
3
+ import { TONE as TONE_COLOR } from '../tone.js';
4
+
5
+ export interface TimelineProps extends BoxProps {
6
+ items: {
7
+ time?: string;
8
+ title: string;
9
+ description?: string;
10
+ tone?: SemanticVariant;
11
+ icon?: string;
12
+ }[];
13
+ }
14
+
15
+ export const Timeline = defineComponent<TimelineProps>('Timeline', ({ items, ...rest }) => {
16
+ const theme = useTheme();
17
+ return h('box', { direction: 'column', ...rest },
18
+ ...items.map((item, i) =>
19
+ h('box', { key: i, direction: 'row', gap: 1 },
20
+ // The gutter is as tall as the entry beside it, not two rows.
21
+ //
22
+ // The connector used to be a single `text`, so an entry whose
23
+ // description wrapped - or which had a description at all, in a narrow
24
+ // column - got one cell of line under its bullet and then a gap, and
25
+ // the thread that makes a timeline a timeline broke at every long
26
+ // entry. `fill` paints the glyph into every cell it is given and
27
+ // `flex` gives it whatever the row turned out to be.
28
+ h('box', { direction: 'column', width: 1, alignSelf: 'stretch' },
29
+ h('text', { content: item.icon ?? theme.glyphs.bulletFilled, fg: item.tone ? TONE_COLOR[item.tone] : 'accent' }),
30
+ i < items.length - 1
31
+ // `minHeight` as well as `flex`: an entry with nothing but a
32
+ // title is one row tall, so a purely elastic connector would get
33
+ // no rows at all and two short entries would sit against each
34
+ // other with no thread between them. One row is the floor, and
35
+ // what a short entry has always had.
36
+ ? h('box', { flex: 1, minHeight: 1, fill: theme.borderChars().left, fg: 'borderSubtle' })
37
+ : null),
38
+ h('box', { direction: 'column', flex: 1 },
39
+ h('box', { direction: 'row', gap: 1 },
40
+ h('text', { content: item.title, bold: true }),
41
+ item.time ? h('spacer', { flex: 1 }) : null,
42
+ item.time ? h('text', { content: item.time, fg: 'muted' }) : null),
43
+ item.description ? h('text', { content: item.description, fg: 'muted', wrap: 'word' }) : null),
44
+ ),
45
+ ),
46
+ );
47
+ });
package/src/find.ts ADDED
@@ -0,0 +1,138 @@
1
+ import type { BindingPath, ReactiveStore } from '@textui/core';
2
+ import { useStoreValue } from '@textui/core';
3
+
4
+ /**
5
+ * Finding things in text.
6
+ *
7
+ * The query lives in the store rather than in whatever component is showing
8
+ * the text, for the usual reason: a search box, a status bar counting matches
9
+ * and the viewer painting them are three things that have to agree, and the
10
+ * only way three things agree is by reading one answer.
11
+ *
12
+ * The matching itself is here and not in the editor because a viewer wants it
13
+ * too, and so will a results panel: given a string and a query, which spans
14
+ * matched. Nothing here knows what a caret is.
15
+ */
16
+
17
+ export const FIND_ROOT = '$/ui/find';
18
+ export const FIND_QUERY = `${FIND_ROOT}/query` as BindingPath;
19
+ export const FIND_CASE = `${FIND_ROOT}/matchCase` as BindingPath;
20
+ /** Bumped to ask whoever is showing the text to move to the next match. */
21
+ export const FIND_STEP = `${FIND_ROOT}/step` as BindingPath;
22
+
23
+ export interface FindQuery {
24
+ text: string;
25
+ matchCase?: boolean;
26
+ }
27
+
28
+ /** Where one match is: the line it is on, and the columns it covers. */
29
+ export interface Match {
30
+ line: number;
31
+ start: number;
32
+ end: number;
33
+ }
34
+
35
+ /**
36
+ * Every match in a document, in reading order.
37
+ *
38
+ * Overlapping matches are not a thing: a search steps past what it found, so
39
+ * `aa` in `aaaa` is two matches and not three. That is what every editor does
40
+ * and what "next" means when you press it twice.
41
+ */
42
+ export function findMatches(text: string, query: FindQuery): Match[] {
43
+ const needle = query.matchCase === true ? query.text : query.text.toLowerCase();
44
+ if (needle === '') return [];
45
+
46
+ const out: Match[] = [];
47
+ const lines = text.split('\n');
48
+ for (let line = 0; line < lines.length; line++) {
49
+ const raw = lines[line] as string;
50
+ const haystack = query.matchCase === true ? raw : raw.toLowerCase();
51
+ let at = haystack.indexOf(needle);
52
+ while (at !== -1) {
53
+ out.push({ line, start: at, end: at + needle.length });
54
+ at = haystack.indexOf(needle, at + needle.length);
55
+ }
56
+ }
57
+ return out;
58
+ }
59
+
60
+ /**
61
+ * The match a caret should go to next, wrapping at the end.
62
+ *
63
+ * From *after* the caret, not from the caret: pressing next while sitting on a
64
+ * match has to move, or "next" is a key that does nothing. Backwards is the
65
+ * mirror, and both wrap, because a search that stops at the end of the file is
66
+ * a search you have to restart by hand.
67
+ */
68
+ export function stepMatch(
69
+ matches: Match[],
70
+ from: { line: number; column: number },
71
+ direction: 1 | -1,
72
+ ): number {
73
+ if (matches.length === 0) return -1;
74
+
75
+ if (direction === 1) {
76
+ const found = matches.findIndex(
77
+ (m) => m.line > from.line || (m.line === from.line && m.start > from.column),
78
+ );
79
+ return found === -1 ? 0 : found;
80
+ }
81
+
82
+ /*
83
+ * Backwards, from *before* the caret - and the caret is usually sitting at
84
+ * the end of the match it just stepped to, so "before" has to mean the match
85
+ * *ends* before it. Comparing starts instead finds the match the caret is
86
+ * standing in and calls that the previous one, which is a key that does
87
+ * nothing every other press.
88
+ */
89
+ for (let i = matches.length - 1; i >= 0; i--) {
90
+ const m = matches[i] as Match;
91
+ if (m.line < from.line || (m.line === from.line && m.end < from.column)) return i;
92
+ }
93
+ return matches.length - 1;
94
+ }
95
+
96
+ /** Which match the caret is sitting inside, or -1. */
97
+ export function matchAt(matches: Match[], at: { line: number; column: number }): number {
98
+ return matches.findIndex(
99
+ (m) => m.line === at.line && at.column >= m.start && at.column <= m.end,
100
+ );
101
+ }
102
+
103
+ export function readQuery(store: ReactiveStore): FindQuery {
104
+ return {
105
+ text: store.get<string>(FIND_QUERY) ?? '',
106
+ matchCase: store.get<boolean>(FIND_CASE) === true,
107
+ };
108
+ }
109
+
110
+ export function setQuery(store: ReactiveStore, text: string): void {
111
+ store.set(FIND_QUERY, text);
112
+ }
113
+
114
+ /**
115
+ * Ask whoever is showing the text to move.
116
+ *
117
+ * A counter rather than a command, so the thing that moves is the thing that
118
+ * knows where the caret is - and so two panels showing the same file each move
119
+ * their own.
120
+ */
121
+ export function stepFind(store: ReactiveStore, direction: 1 | -1): void {
122
+ const at = store.get<{ n: number }>(FIND_STEP);
123
+ store.set(FIND_STEP, { n: (at?.n ?? 0) + 1, direction });
124
+ }
125
+
126
+ export interface FindHandle {
127
+ query: FindQuery;
128
+ /** Changes when somebody asks for the next match. */
129
+ step: { n: number; direction: 1 | -1 } | undefined;
130
+ }
131
+
132
+ /** The current query, and the ask to move, for a component showing text. */
133
+ export function useFind(): FindHandle {
134
+ const text = useStoreValue<string>(FIND_QUERY, '') ?? '';
135
+ const matchCase = useStoreValue<boolean>(FIND_CASE, false) === true;
136
+ const step = useStoreValue<{ n: number; direction: 1 | -1 }>(FIND_STEP);
137
+ return { query: { text, matchCase }, step };
138
+ }
@@ -0,0 +1,34 @@
1
+ import type { BoxProps } from '@textui/core';
2
+ import { defineComponent, h, useTheme } from '@textui/core';
3
+ import { Button } from '../control/index.js';
4
+
5
+ export interface DangerZoneProps extends BoxProps {
6
+ title?: string;
7
+ description?: string;
8
+ actionLabel: string;
9
+ onAction?(): void;
10
+ /** Require typing this exact text before the action is enabled. */
11
+ confirmText?: string;
12
+ }
13
+
14
+ export const DangerZone = defineComponent<DangerZoneProps>('DangerZone', (props) => {
15
+ const theme = useTheme();
16
+ const {
17
+ title = 'Danger zone', description, actionLabel, onAction, confirmText, ...rest
18
+ } = props;
19
+
20
+ return h('box', {
21
+ border: { style: theme.border, color: 'danger' },
22
+ padding: [0, 1],
23
+ direction: 'column',
24
+ title: ` ${title} `,
25
+ ...rest,
26
+ },
27
+ description ? h('text', { content: description, fg: 'muted', wrap: 'word' }) : null,
28
+ confirmText
29
+ ? h('text', { content: `Type "${confirmText}" to confirm.`, fg: 'subtle' })
30
+ : null,
31
+ h('box', { direction: 'row', justify: 'end' },
32
+ h(Button, { label: actionLabel, tone: 'danger', variant: 'outline', onPress: onAction })),
33
+ );
34
+ });
@@ -0,0 +1,61 @@
1
+ import type { BoxProps } from '@textui/core';
2
+ import { defineComponent, h, useTheme } from '@textui/core';
3
+ import { useFormContext } from './shared.js';
4
+
5
+ export interface FieldProps extends BoxProps {
6
+ name: string;
7
+ label?: string;
8
+ hint?: string;
9
+ required?: boolean;
10
+ /** Cells reserved for the label, so a column of fields lines up. */
11
+ labelWidth?: number;
12
+ /** Label above the control rather than beside it. */
13
+ stacked?: boolean;
14
+ }
15
+
16
+ /**
17
+ * A labelled control with its error.
18
+ *
19
+ * The error line is always reserved when one could appear, so a form does not
20
+ * jump a row every time validation fires.
21
+ */
22
+ export const Field = defineComponent<FieldProps>('Field', (props) => {
23
+ const theme = useTheme();
24
+ const form = useFormContext();
25
+ const { name, label, hint, required, labelWidth, stacked, children, ...rest } = props;
26
+ const error = form.errorFor(name);
27
+
28
+ const labelNode = label
29
+ ? h('box', { direction: 'row', gap: 0, width: stacked ? undefined : labelWidth },
30
+ h('text', { content: label, fg: error ? 'danger' : 'muted' }),
31
+ required ? h('text', { content: '*', fg: 'danger' }) : null)
32
+ : null;
33
+
34
+ return h('box', { direction: 'column', ...rest },
35
+ // Centred, so the label sits on the control's line of text rather than on
36
+ // the top edge of its frame.
37
+ //
38
+ // A row stretches its children, so a one-row label beside a three-row
39
+ // bordered input was drawn at the top of three rows - level with the
40
+ // border, one above the text it names. Borderless controls are one row
41
+ // and were fine, which is why a form mixing the two had half its labels
42
+ // aligned and half of them a row high.
43
+ h('box', {
44
+ direction: stacked ? 'column' : 'row',
45
+ gap: stacked ? 0 : 1,
46
+ ...(stacked ? {} : { align: 'center' as const }),
47
+ },
48
+ labelNode,
49
+ h('box', { flex: 1 }, children)),
50
+ error
51
+ ? h('box', { direction: 'row', gap: 1 },
52
+ stacked ? null : h('box', { width: labelWidth }),
53
+ h('text', { content: theme.glyphs.warning, fg: 'danger' }),
54
+ h('text', { content: error, fg: 'danger', wrap: 'word' }))
55
+ : hint
56
+ ? h('box', { direction: 'row', gap: 1 },
57
+ stacked ? null : h('box', { width: labelWidth }),
58
+ h('text', { content: hint, fg: 'subtle' }))
59
+ : null,
60
+ );
61
+ });