@visulima/tui-kit 0.0.0 → 1.0.1

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 (371) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE.md +2095 -0
  3. package/README.md +115 -29
  4. package/dist/accordion.d.ts +52 -0
  5. package/dist/accordion.js +1 -0
  6. package/dist/alert.d.ts +22 -0
  7. package/dist/alert.js +1 -0
  8. package/dist/animate-presence.d.ts +16 -0
  9. package/dist/animate-presence.js +1 -0
  10. package/dist/approval-prompt.d.ts +55 -0
  11. package/dist/approval-prompt.js +1 -0
  12. package/dist/area-chart.d.ts +56 -0
  13. package/dist/area-chart.js +1 -0
  14. package/dist/badge.d.ts +19 -0
  15. package/dist/badge.js +1 -0
  16. package/dist/bar-chart.d.ts +58 -0
  17. package/dist/bar-chart.js +1 -0
  18. package/dist/big-text.d.ts +71 -0
  19. package/dist/big-text.js +1 -0
  20. package/dist/blink-dot.d.ts +31 -0
  21. package/dist/blink-dot.js +1 -0
  22. package/dist/breadcrumb.d.ts +29 -0
  23. package/dist/breadcrumb.js +1 -0
  24. package/dist/button.d.ts +38 -0
  25. package/dist/button.js +1 -0
  26. package/dist/calendar.d.ts +60 -0
  27. package/dist/calendar.js +1 -0
  28. package/dist/card.d.ts +47 -0
  29. package/dist/card.js +1 -0
  30. package/dist/checkbox-group.d.ts +50 -0
  31. package/dist/checkbox-group.js +1 -0
  32. package/dist/checkbox.d.ts +40 -0
  33. package/dist/checkbox.js +1 -0
  34. package/dist/code.d.ts +41 -0
  35. package/dist/code.js +2 -0
  36. package/dist/collapsible.d.ts +44 -0
  37. package/dist/collapsible.js +1 -0
  38. package/dist/color-picker.d.ts +59 -0
  39. package/dist/color-picker.js +1 -0
  40. package/dist/command-block.d.ts +39 -0
  41. package/dist/command-block.js +4 -0
  42. package/dist/command-palette.d.ts +73 -0
  43. package/dist/command-palette.js +1 -0
  44. package/dist/confirm-dialog.d.ts +63 -0
  45. package/dist/confirm-dialog.js +1 -0
  46. package/dist/confirm-input.d.ts +32 -0
  47. package/dist/confirm-input.js +1 -0
  48. package/dist/console-overlay.d.ts +47 -0
  49. package/dist/console-overlay.js +1 -0
  50. package/dist/content-switcher.d.ts +49 -0
  51. package/dist/content-switcher.js +1 -0
  52. package/dist/controlled-scroll-view.d.ts +3 -0
  53. package/dist/controlled-scroll-view.js +1 -0
  54. package/dist/data-grid.d.ts +43 -0
  55. package/dist/data-grid.js +1 -0
  56. package/dist/date-picker.d.ts +54 -0
  57. package/dist/date-picker.js +1 -0
  58. package/dist/definition-list.d.ts +38 -0
  59. package/dist/definition-list.js +1 -0
  60. package/dist/dialog.d.ts +94 -0
  61. package/dist/dialog.js +1 -0
  62. package/dist/diff-view.d.ts +72 -0
  63. package/dist/diff-view.js +1 -0
  64. package/dist/digits.d.ts +26 -0
  65. package/dist/digits.js +1 -0
  66. package/dist/directory-tree.d.ts +44 -0
  67. package/dist/directory-tree.js +1 -0
  68. package/dist/divider.d.ts +41 -0
  69. package/dist/divider.js +1 -0
  70. package/dist/drawer.d.ts +45 -0
  71. package/dist/drawer.js +1 -0
  72. package/dist/file-change.d.ts +34 -0
  73. package/dist/file-change.js +1 -0
  74. package/dist/file-picker-entry.d.ts +2 -0
  75. package/dist/file-picker-entry.js +1 -0
  76. package/dist/form.d.ts +74 -0
  77. package/dist/form.js +1 -0
  78. package/dist/gauge.d.ts +76 -0
  79. package/dist/gauge.js +1 -0
  80. package/dist/gradient.d.ts +38 -0
  81. package/dist/gradient.js +1 -0
  82. package/dist/grid.d.ts +34 -0
  83. package/dist/grid.js +1 -0
  84. package/dist/heading.d.ts +28 -0
  85. package/dist/heading.js +1 -0
  86. package/dist/heatmap.d.ts +52 -0
  87. package/dist/heatmap.js +1 -0
  88. package/dist/help.d.ts +45 -0
  89. package/dist/help.js +1 -0
  90. package/dist/histogram.d.ts +52 -0
  91. package/dist/histogram.js +1 -0
  92. package/dist/image.d.ts +23 -0
  93. package/dist/image.js +1 -0
  94. package/dist/index.d.ts +125 -0
  95. package/dist/index.js +1 -0
  96. package/dist/json.d.ts +51 -0
  97. package/dist/json.js +1 -0
  98. package/dist/kbd.d.ts +27 -0
  99. package/dist/kbd.js +1 -0
  100. package/dist/line-chart.d.ts +100 -0
  101. package/dist/line-chart.js +1 -0
  102. package/dist/link.d.ts +34 -0
  103. package/dist/link.js +1 -0
  104. package/dist/loading-indicator.d.ts +25 -0
  105. package/dist/loading-indicator.js +1 -0
  106. package/dist/log.d.ts +33 -0
  107. package/dist/log.js +1 -0
  108. package/dist/markdown.d.ts +35 -0
  109. package/dist/markdown.js +1 -0
  110. package/dist/masked-input.d.ts +42 -0
  111. package/dist/masked-input.js +1 -0
  112. package/dist/menu.d.ts +70 -0
  113. package/dist/menu.js +1 -0
  114. package/dist/message-bubble.d.ts +36 -0
  115. package/dist/message-bubble.js +1 -0
  116. package/dist/model-badge.d.ts +33 -0
  117. package/dist/model-badge.js +1 -0
  118. package/dist/model-selector.d.ts +45 -0
  119. package/dist/model-selector.js +1 -0
  120. package/dist/multi-progress.d.ts +48 -0
  121. package/dist/multi-progress.js +1 -0
  122. package/dist/multi-select.d.ts +55 -0
  123. package/dist/multi-select.js +1 -0
  124. package/dist/notification-badge.d.ts +40 -0
  125. package/dist/notification-badge.js +1 -0
  126. package/dist/number-input.d.ts +71 -0
  127. package/dist/number-input.js +1 -0
  128. package/dist/operation-tree.d.ts +46 -0
  129. package/dist/operation-tree.js +1 -0
  130. package/dist/option-list.d.ts +41 -0
  131. package/dist/option-list.js +1 -0
  132. package/dist/ordered-list.d.ts +22 -0
  133. package/dist/ordered-list.js +1 -0
  134. package/dist/packem_shared/ControlledScrollView-BzlbIUph.js +1 -0
  135. package/dist/packem_shared/FilePicker-V5H2fb-v.js +1 -0
  136. package/dist/packem_shared/ScrollBar-C9i3z3Ft.js +1 -0
  137. package/dist/packem_shared/ScrollBarBox-DZEC8YnV.js +1 -0
  138. package/dist/packem_shared/ScrollList-Djtb2vVy.js +1 -0
  139. package/dist/packem_shared/ScrollView-ESxpl6Hy.js +1 -0
  140. package/dist/packem_shared/TreeNodeMap-cJlYqckB.js +1 -0
  141. package/dist/packem_shared/TreeView-q0l446NV.js +1 -0
  142. package/dist/packem_shared/controlled-scroll-view-DerVKKv6.js +1 -0
  143. package/dist/packem_shared/controlled-scroll-view.d-D3kZZzjI.d.ts +43 -0
  144. package/dist/packem_shared/file-picker.d-CH7pccC7.d.ts +92 -0
  145. package/dist/packem_shared/line-chart-BbhMhcZg.js +1 -0
  146. package/dist/packem_shared/scroll-bar-box.d-CpTYEcTZ.d.ts +13 -0
  147. package/dist/packem_shared/scroll-bar.d-B4sD1hBR.d.ts +18 -0
  148. package/dist/packem_shared/scroll-list.d-CPCF0RwC.d.ts +12 -0
  149. package/dist/packem_shared/scroll-view.d-GZcgAHl4.d.ts +98 -0
  150. package/dist/packem_shared/treeViewTheme-Dq3ekRAC.js +1 -0
  151. package/dist/packem_shared/use-tree-view.d-DeMQ6G2r.d.ts +314 -0
  152. package/dist/packem_shared/useTreeView-D0JEqT6W.js +1 -0
  153. package/dist/packem_shared/useTreeViewState-BiTJakkh.js +1 -0
  154. package/dist/packem_shared/variant-config-BJqpNo7H.js +1 -0
  155. package/dist/packem_shared/variant-config.d-D5NnrrQK.d.ts +2 -0
  156. package/dist/paginator.d.ts +59 -0
  157. package/dist/paginator.js +1 -0
  158. package/dist/paragraph.d.ts +32 -0
  159. package/dist/paragraph.js +1 -0
  160. package/dist/path-input.d.ts +56 -0
  161. package/dist/path-input.js +1 -0
  162. package/dist/pie-chart.d.ts +44 -0
  163. package/dist/pie-chart.js +1 -0
  164. package/dist/placeholder.d.ts +47 -0
  165. package/dist/placeholder.js +1 -0
  166. package/dist/popover.d.ts +39 -0
  167. package/dist/popover.js +1 -0
  168. package/dist/progress-bar.d.ts +43 -0
  169. package/dist/progress-bar.js +1 -0
  170. package/dist/progress-circle.d.ts +50 -0
  171. package/dist/progress-circle.js +1 -0
  172. package/dist/qr-code.d.ts +39 -0
  173. package/dist/qr-code.js +1 -0
  174. package/dist/r/accordion.json +24 -0
  175. package/dist/r/alert.json +24 -0
  176. package/dist/r/animate-presence.json +22 -0
  177. package/dist/r/approval-prompt.json +24 -0
  178. package/dist/r/area-chart.json +27 -0
  179. package/dist/r/badge.json +24 -0
  180. package/dist/r/bar-chart.json +24 -0
  181. package/dist/r/big-text.json +23 -0
  182. package/dist/r/blink-dot.json +24 -0
  183. package/dist/r/breadcrumb.json +24 -0
  184. package/dist/r/button.json +24 -0
  185. package/dist/r/calendar.json +24 -0
  186. package/dist/r/card.json +26 -0
  187. package/dist/r/chart-utils.json +24 -0
  188. package/dist/r/checkbox-group.json +24 -0
  189. package/dist/r/checkbox.json +24 -0
  190. package/dist/r/code.json +23 -0
  191. package/dist/r/collapsible.json +24 -0
  192. package/dist/r/color-picker.json +24 -0
  193. package/dist/r/command-block.json +26 -0
  194. package/dist/r/command-palette.json +24 -0
  195. package/dist/r/confirm-dialog.json +24 -0
  196. package/dist/r/confirm-input.json +22 -0
  197. package/dist/r/console-overlay.json +22 -0
  198. package/dist/r/content-switcher.json +24 -0
  199. package/dist/r/controlled-scroll-view.json +24 -0
  200. package/dist/r/data-grid.json +24 -0
  201. package/dist/r/date-picker.json +26 -0
  202. package/dist/r/definition-list.json +24 -0
  203. package/dist/r/dialog.json +24 -0
  204. package/dist/r/diff-view.json +24 -0
  205. package/dist/r/digits.json +24 -0
  206. package/dist/r/directory-tree.json +24 -0
  207. package/dist/r/divider.json +24 -0
  208. package/dist/r/drawer.json +24 -0
  209. package/dist/r/file-change.json +24 -0
  210. package/dist/r/file-picker.json +44 -0
  211. package/dist/r/form.json +24 -0
  212. package/dist/r/gauge.json +24 -0
  213. package/dist/r/gradient.json +24 -0
  214. package/dist/r/grid.json +22 -0
  215. package/dist/r/heading.json +24 -0
  216. package/dist/r/heatmap.json +24 -0
  217. package/dist/r/help.json +22 -0
  218. package/dist/r/histogram.json +26 -0
  219. package/dist/r/image.json +24 -0
  220. package/dist/r/json.json +24 -0
  221. package/dist/r/kbd.json +24 -0
  222. package/dist/r/line-chart.json +26 -0
  223. package/dist/r/link.json +23 -0
  224. package/dist/r/loading-indicator.json +27 -0
  225. package/dist/r/log.json +24 -0
  226. package/dist/r/markdown.json +29 -0
  227. package/dist/r/masked-input.json +22 -0
  228. package/dist/r/menu.json +24 -0
  229. package/dist/r/message-bubble.json +24 -0
  230. package/dist/r/model-badge.json +24 -0
  231. package/dist/r/model-selector.json +24 -0
  232. package/dist/r/multi-progress.json +24 -0
  233. package/dist/r/multi-select.json +24 -0
  234. package/dist/r/notification-badge.json +24 -0
  235. package/dist/r/number-input.json +24 -0
  236. package/dist/r/operation-tree.json +26 -0
  237. package/dist/r/option-list.json +24 -0
  238. package/dist/r/ordered-list.json +22 -0
  239. package/dist/r/paginator.json +22 -0
  240. package/dist/r/paragraph.json +24 -0
  241. package/dist/r/path-input.json +26 -0
  242. package/dist/r/pie-chart.json +24 -0
  243. package/dist/r/placeholder.json +24 -0
  244. package/dist/r/popover.json +24 -0
  245. package/dist/r/progress-bar.json +22 -0
  246. package/dist/r/progress-circle.json +24 -0
  247. package/dist/r/qr-code.json +24 -0
  248. package/dist/r/radio-group.json +24 -0
  249. package/dist/r/registry.json +1051 -0
  250. package/dist/r/scatter-plot.json +27 -0
  251. package/dist/r/scroll-bar-box.json +24 -0
  252. package/dist/r/scroll-bar.json +24 -0
  253. package/dist/r/scroll-list.json +24 -0
  254. package/dist/r/scroll-view.json +24 -0
  255. package/dist/r/scroll.json +58 -0
  256. package/dist/r/search-input.json +26 -0
  257. package/dist/r/select-input-indicator.json +24 -0
  258. package/dist/r/select-input-item.json +24 -0
  259. package/dist/r/select-input.json +28 -0
  260. package/dist/r/shimmer-text.json +24 -0
  261. package/dist/r/sidebar.json +24 -0
  262. package/dist/r/slider.json +24 -0
  263. package/dist/r/sparkline.json +24 -0
  264. package/dist/r/spinner.json +23 -0
  265. package/dist/r/status-line.json +24 -0
  266. package/dist/r/status-message.json +24 -0
  267. package/dist/r/stepper.json +24 -0
  268. package/dist/r/stopwatch.json +22 -0
  269. package/dist/r/streaming-text.json +24 -0
  270. package/dist/r/switch.json +24 -0
  271. package/dist/r/tab.json +22 -0
  272. package/dist/r/table.json +23 -0
  273. package/dist/r/tabs.json +26 -0
  274. package/dist/r/tag-input.json +24 -0
  275. package/dist/r/tag.json +24 -0
  276. package/dist/r/text-input.json +22 -0
  277. package/dist/r/textarea.json +22 -0
  278. package/dist/r/thinking-block.json +24 -0
  279. package/dist/r/time-picker.json +24 -0
  280. package/dist/r/timer.json +22 -0
  281. package/dist/r/toast.json +24 -0
  282. package/dist/r/token-usage.json +24 -0
  283. package/dist/r/tool-call.json +24 -0
  284. package/dist/r/tooltip.json +24 -0
  285. package/dist/r/transition.json +22 -0
  286. package/dist/r/tree-select.json +24 -0
  287. package/dist/r/tree-view.json +64 -0
  288. package/dist/r/unordered-list.json +22 -0
  289. package/dist/r/use-linked-scroll.json +24 -0
  290. package/dist/r/variant-config.json +22 -0
  291. package/dist/radio-group.d.ts +63 -0
  292. package/dist/radio-group.js +1 -0
  293. package/dist/scatter-plot.d.ts +24 -0
  294. package/dist/scatter-plot.js +1 -0
  295. package/dist/scroll-bar-box.d.ts +3 -0
  296. package/dist/scroll-bar-box.js +1 -0
  297. package/dist/scroll-bar.d.ts +3 -0
  298. package/dist/scroll-bar.js +1 -0
  299. package/dist/scroll-list.d.ts +4 -0
  300. package/dist/scroll-list.js +1 -0
  301. package/dist/scroll-view.d.ts +3 -0
  302. package/dist/scroll-view.js +1 -0
  303. package/dist/search-input.d.ts +46 -0
  304. package/dist/search-input.js +1 -0
  305. package/dist/select-input-indicator.d.ts +28 -0
  306. package/dist/select-input-indicator.js +1 -0
  307. package/dist/select-input-item.d.ts +35 -0
  308. package/dist/select-input-item.js +1 -0
  309. package/dist/select-input.d.ts +97 -0
  310. package/dist/select-input.js +1 -0
  311. package/dist/shimmer-text.d.ts +41 -0
  312. package/dist/shimmer-text.js +1 -0
  313. package/dist/sidebar.d.ts +40 -0
  314. package/dist/sidebar.js +1 -0
  315. package/dist/slider.d.ts +87 -0
  316. package/dist/slider.js +1 -0
  317. package/dist/sparkline.d.ts +36 -0
  318. package/dist/sparkline.js +1 -0
  319. package/dist/spinner.d.ts +15 -0
  320. package/dist/spinner.js +1 -0
  321. package/dist/status-line.d.ts +31 -0
  322. package/dist/status-line.js +1 -0
  323. package/dist/status-message.d.ts +18 -0
  324. package/dist/status-message.js +1 -0
  325. package/dist/stepper.d.ts +42 -0
  326. package/dist/stepper.js +1 -0
  327. package/dist/stopwatch.d.ts +42 -0
  328. package/dist/stopwatch.js +1 -0
  329. package/dist/streaming-text.d.ts +39 -0
  330. package/dist/streaming-text.js +1 -0
  331. package/dist/switch.d.ts +46 -0
  332. package/dist/switch.js +1 -0
  333. package/dist/tab.d.ts +17 -0
  334. package/dist/tab.js +1 -0
  335. package/dist/table.d.ts +118 -0
  336. package/dist/table.js +1 -0
  337. package/dist/tabs.d.ts +96 -0
  338. package/dist/tabs.js +1 -0
  339. package/dist/tag-input.d.ts +59 -0
  340. package/dist/tag-input.js +1 -0
  341. package/dist/tag.d.ts +30 -0
  342. package/dist/tag.js +1 -0
  343. package/dist/text-input.d.ts +40 -0
  344. package/dist/text-input.js +1 -0
  345. package/dist/textarea.d.ts +57 -0
  346. package/dist/textarea.js +2 -0
  347. package/dist/thinking-block.d.ts +48 -0
  348. package/dist/thinking-block.js +1 -0
  349. package/dist/time-picker.d.ts +57 -0
  350. package/dist/time-picker.js +1 -0
  351. package/dist/timer.d.ts +50 -0
  352. package/dist/timer.js +1 -0
  353. package/dist/toast.d.ts +41 -0
  354. package/dist/toast.js +1 -0
  355. package/dist/token-usage.d.ts +43 -0
  356. package/dist/token-usage.js +1 -0
  357. package/dist/tool-call.d.ts +28 -0
  358. package/dist/tool-call.js +1 -0
  359. package/dist/tooltip.d.ts +37 -0
  360. package/dist/tooltip.js +1 -0
  361. package/dist/transition.d.ts +66 -0
  362. package/dist/transition.js +1 -0
  363. package/dist/tree-select.d.ts +55 -0
  364. package/dist/tree-select.js +1 -0
  365. package/dist/tree-view-entry.d.ts +4 -0
  366. package/dist/tree-view-entry.js +1 -0
  367. package/dist/unordered-list.d.ts +27 -0
  368. package/dist/unordered-list.js +1 -0
  369. package/dist/use-linked-scroll.d.ts +42 -0
  370. package/dist/use-linked-scroll.js +1 -0
  371. package/package.json +553 -7
@@ -0,0 +1,32 @@
1
+ import { AnsiColors } from '@visulima/colorize';
2
+ import { ReactNode, ReactElement } from 'react';
3
+ import { LiteralUnion } from 'type-fest';
4
+ type Props = {
5
+ /**
6
+ * Paragraph body.
7
+ */
8
+ readonly children: ReactNode;
9
+ /**
10
+ * Foreground color.
11
+ */
12
+ readonly color?: LiteralUnion<AnsiColors, string>;
13
+ /**
14
+ * Render the paragraph with dim color.
15
+ */
16
+ readonly dimColor?: boolean;
17
+ /**
18
+ * Blank lines to add after the paragraph.
19
+ * @default 1
20
+ */
21
+ readonly marginBottom?: number;
22
+ /**
23
+ * Blank lines to add before the paragraph.
24
+ * @default 0
25
+ */
26
+ readonly marginTop?: number;
27
+ };
28
+ /**
29
+ * Wraps body text with a trailing blank line. Useful for README-style layouts.
30
+ */
31
+ declare function Paragraph({ children, color, dimColor, marginBottom, marginTop }: Props): ReactElement;
32
+ export { Paragraph, type Props as ParagraphProps, Props, Paragraph as default };
@@ -0,0 +1 @@
1
+ import{jsx as o}from"react/jsx-runtime";import a from"@visulima/tui/components/box";import l from"@visulima/tui/components/text";function p({children:r,color:i,dimColor:m=!1,marginBottom:n=1,marginTop:t=0}){return o(a,{flexDirection:"column",marginBottom:n,marginTop:t,children:o(l,{color:i,dimColor:m,children:r})})}export{p as Paragraph,p as default};
@@ -0,0 +1,56 @@
1
+ import { AnsiColors } from '@visulima/colorize';
2
+ import { ReactElement } from 'react';
3
+ import { LiteralUnion } from 'type-fest';
4
+ type Props = {
5
+ /**
6
+ * Accent color for the focused border and caret.
7
+ * @default "blue"
8
+ */
9
+ readonly accentColor?: LiteralUnion<AnsiColors, string>;
10
+ /**
11
+ * Auto-focus on mount.
12
+ */
13
+ readonly autoFocus?: boolean;
14
+ /**
15
+ * Initial path when uncontrolled.
16
+ */
17
+ readonly defaultValue?: string;
18
+ /**
19
+ * Override how completions are produced (e.g. to sandbox or mock the
20
+ * filesystem). Defaults to reading the real filesystem.
21
+ */
22
+ readonly getCompletions?: (input: string) => ReadonlyArray<string>;
23
+ /**
24
+ * Disable input and dim the display.
25
+ */
26
+ readonly isDisabled?: boolean;
27
+ /**
28
+ * Fires whenever the path text changes.
29
+ */
30
+ readonly onChange?: (path: string) => void;
31
+ /**
32
+ * Fires on Enter with the current path.
33
+ */
34
+ readonly onSubmit?: (path: string) => void;
35
+ /**
36
+ * Placeholder shown when empty.
37
+ * @default "Enter a path…"
38
+ */
39
+ readonly placeholder?: string;
40
+ /**
41
+ * Show the first few completion candidates beneath the field.
42
+ * @default true
43
+ */
44
+ readonly showSuggestions?: boolean;
45
+ /**
46
+ * Controlled path. When provided, `defaultValue` is ignored.
47
+ */
48
+ readonly value?: string;
49
+ };
50
+ /**
51
+ * A text input specialised for filesystem paths. Tab completes against the
52
+ * directory being typed (cycling through matches on repeated presses), and a
53
+ * short list of candidates is shown below the field.
54
+ */
55
+ declare function PathInput({ accentColor, autoFocus, defaultValue, getCompletions, isDisabled, onChange, onSubmit, placeholder, showSuggestions, value: controlledValue }: Props): ReactElement;
56
+ export { PathInput, type Props as PathInputProps, Props, PathInput as default };
@@ -0,0 +1 @@
1
+ import{jsxs as f,jsx as a}from"react/jsx-runtime";import{readdirSync as E,statSync as I}from"node:fs";import{basename as j,dirname as w,join as L}from"node:path";import g from"@visulima/tui/components/box";import c from"@visulima/tui/components/text";import M from"@visulima/tui/hooks/use-focus";import R from"@visulima/tui/hooks/use-input";import{useState as V,useRef as D,useMemo as X,useCallback as W}from"react";const _=/\/$/,q=r=>{const u=r.endsWith("/")?r:w(r),p=r.endsWith("/")?"":j(r);try{return E(u.length===0?".":u).filter(o=>o.startsWith(p)).map(o=>{const e=L(u,o);try{return I(e).isDirectory()?`${e}/`:e}catch{return e}}).toSorted((o,e)=>o.localeCompare(e))}catch{return[]}},C=5;function T({accentColor:r="blue",autoFocus:u=!1,defaultValue:p="",getCompletions:o=q,isDisabled:e=!1,onChange:b,onSubmit:v,placeholder:k="Enter a path…",showSuggestions:F=!0,value:S}){const{isFocused:d}=M({autoFocus:u,isActive:!e}),x=S!==void 0,[P,A]=V(p),t=S??P,s=D(null),y=D(b);y.current=b;const i=X(()=>o(t),[o,t]),m=W(n=>{x||A(n),y.current?.(n)},[x]),B=W((n,l)=>{if(l.tab){s.current??={index:0,matches:i};const{index:$,matches:h}=s.current;h.length>0&&(s.current={index:$+1,matches:h},m(h[$%h.length]));return}if(l.return){v?.(t);return}if(l.backspace||l.delete){s.current=null,m(t.slice(0,-1));return}n.length>0&&n.codePointAt(0)>=32&&(s.current=null,m(`${t}${n}`))},[i,v,t,m]);return R(B,{isActive:d&&!e}),f(g,{flexDirection:"column",children:[f(g,{borderColor:d?r:void 0,borderStyle:"round",paddingX:1,children:[a(c,{color:r,children:"📁 "}),t.length===0?a(c,{dimColor:!0,children:k}):a(c,{dimColor:e,children:t}),d&&t.length>0?a(c,{color:r,children:"▏"}):void 0]}),F&&d&&i.length>0?f(g,{flexDirection:"column",marginLeft:1,children:[i.slice(0,C).map((n,l)=>f(c,{dimColor:!0,children:[j(n.replace(_,"")),n.endsWith("/")?"/":""]},l)),i.length>C?a(c,{dimColor:!0,children:`…and ${i.length-C} more`}):void 0]}):void 0]})}export{T as PathInput,T as default};
@@ -0,0 +1,44 @@
1
+ import { AnsiColors } from '@visulima/colorize';
2
+ import { ReactElement } from 'react';
3
+ import { LiteralUnion } from 'type-fest';
4
+ type PieSlice = {
5
+ /**
6
+ * Per-slice color override. Falls back to the chart palette.
7
+ */
8
+ readonly color?: LiteralUnion<AnsiColors, string>;
9
+ readonly label?: string;
10
+ readonly value: number;
11
+ };
12
+ type Props = {
13
+ /**
14
+ * Slices to render. Non-positive values are ignored.
15
+ */
16
+ readonly data: ReadonlyArray<PieSlice>;
17
+ /**
18
+ * Format a slice's legend value. Defaults to `value (percent%)`.
19
+ */
20
+ readonly formatLegend?: (slice: PieSlice, percent: number) => string;
21
+ /**
22
+ * Fallback palette cycled through when a slice has no color.
23
+ * @default ["cyan", "magenta", "yellow", "green", "blue", "red"]
24
+ */
25
+ readonly palette?: ReadonlyArray<LiteralUnion<AnsiColors, string>>;
26
+ /**
27
+ * Render a legend with per-slice values and percentages.
28
+ * @default true
29
+ */
30
+ readonly showLegend?: boolean;
31
+ /**
32
+ * Diameter of the disc in character rows. Width is derived to keep the
33
+ * circle roughly round given the 2x4 braille cell aspect.
34
+ * @default 8
35
+ */
36
+ readonly size?: number;
37
+ };
38
+ /**
39
+ * A filled pie chart rendered at 2x4 braille sub-cell resolution. Each pixel of
40
+ * the disc is assigned to the slice whose angular wedge contains it, so slices
41
+ * keep clean colors. An optional legend lists values and percentages.
42
+ */
43
+ declare function PieChart({ data, formatLegend, palette, showLegend, size }: Props): ReactElement;
44
+ export { PieChart, type Props as PieChartProps, PieSlice, Props, PieChart as default };
@@ -0,0 +1 @@
1
+ import{jsxs as C,jsx as s}from"react/jsx-runtime";import{createBrailleGrid as E}from"@visulima/tui/canvas";import M from"@visulima/tui/components/box";import F from"@visulima/tui/components/canvas";import $ from"@visulima/tui/components/text";import{useMemo as j}from"react";const G=["cyan","magenta","yellow","green","blue","red"],B=(r,i,l)=>l.color??i[r%i.length]??"cyan",S=(r,i)=>`${r.label??""} ${r.value} (${i.toFixed(0)}%)`.trim();function N({data:r,formatLegend:i=S,palette:l=G,showLegend:D=!0,size:L=8}){const c=j(()=>r.filter(t=>t.value>0),[r]),h=j(()=>c.reduce((t,n)=>t+n.value,0),[c]),u=Math.max(2,L),x=Math.max(2,Math.round(u*4/2/2));return C(M,{flexDirection:"row",gap:2,children:[s(F,{draw:t=>{if(t.clear(),h<=0)return;const n=t.width*2,v=t.height*4,w=n/2,P=v/2,y=Math.max(1,Math.min(w,P)-1),m=[];let g=0;c.forEach((o,e)=>{const a=g/h*Math.PI*2;g+=o.value;const d=g/h*Math.PI*2;m.push({color:B(e,l,o),end:d,start:a})});const p=new Map,z=o=>{const e=p.get(o);if(e!==void 0)return e;const a=E(t.width,t.height);return p.set(o,a),a};for(let o=0;o<v;o+=1)for(let e=0;e<n;e+=1){const a=e-w,d=o-P;if(a*a+d*d>y*y)continue;let f=Math.atan2(a,-d);f<0&&(f+=Math.PI*2);const I=m.find(b=>f>=b.start&&f<b.end)??m[m.length-1];I&&z(I.color).plotPoint(e,o)}for(const[o,e]of p)e.flush(t,{color:o})},height:u,version:[r,h,l,u,x],width:x}),D&&c.length>0?s(M,{flexDirection:"column",children:c.map((t,n)=>C(M,{gap:1,children:[s($,{color:B(n,l,t),children:"●"}),s($,{dimColor:!0,children:i(t,t.value/h*100)})]},n))}):void 0]})}export{N as PieChart,N as default};
@@ -0,0 +1,47 @@
1
+ import { AnsiColors } from '@visulima/colorize';
2
+ import { ReactElement } from 'react';
3
+ import { LiteralUnion } from 'type-fest';
4
+ type Props = {
5
+ /**
6
+ * Whether to pulse the skeleton blocks (shimmer).
7
+ * @default true
8
+ */
9
+ readonly animated?: boolean;
10
+ /**
11
+ * Glyph repeated to fill each placeholder block.
12
+ * @default "█" (full block)
13
+ */
14
+ readonly character?: string;
15
+ /**
16
+ * Block color.
17
+ * @default "gray"
18
+ */
19
+ readonly color?: LiteralUnion<AnsiColors, string>;
20
+ /**
21
+ * Milliseconds between pulses.
22
+ * @default 500
23
+ */
24
+ readonly interval?: number;
25
+ /**
26
+ * Number of skeleton rows to render.
27
+ * @default 3
28
+ */
29
+ readonly rows?: number;
30
+ /**
31
+ * Total width reference. When omitted, the block flexes to fill its
32
+ * parent.
33
+ */
34
+ readonly width?: number;
35
+ /**
36
+ * Relative widths of each row (0–1). The list is sampled cyclically.
37
+ * @default [1, 0.85, 0.6]
38
+ */
39
+ readonly widths?: ReadonlyArray<number>;
40
+ };
41
+ /**
42
+ * Skeleton loading indicator. Renders a stack of solid blocks whose widths
43
+ * vary to mimic text placeholders. Pair with `async` data fetching.
44
+ * @returns A `ReactElement` rendering the skeleton rows.
45
+ */
46
+ declare function Placeholder({ animated, character, color, interval, rows, width, widths }: Props): ReactElement;
47
+ export { Placeholder, type Props as PlaceholderProps, Props, Placeholder as default };
@@ -0,0 +1 @@
1
+ import{jsx as t}from"react/jsx-runtime";import n from"@visulima/tui/components/box";import x from"@visulima/tui/components/text";import g from"@visulima/tui/hooks/use-animation";import v from"@visulima/tui/hooks/use-window-size";const y=[1,.85,.6];function E({animated:o=!0,character:l="█",color:m="gray",interval:c=500,rows:h=3,width:e,widths:r=y}){const{frame:d}=g({interval:c,isActive:o}),{columns:f}=v(),s=o&&d%2===0,u=e??Math.max(1,f);return t(n,{flexDirection:"column",width:e,children:Array.from({length:h},(M,a)=>{const p=r.length>0?r[a%r.length]??1:1,i=Math.max(1,Math.round(u*p)),w=l.repeat(i);return t(n,{width:i,children:t(x,{color:m,dimColor:s,wrap:"truncate-end",children:w})},a)})})}export{E as Placeholder,E as default};
@@ -0,0 +1,39 @@
1
+ import { AnsiColors } from '@visulima/colorize';
2
+ import { ReactNode, ReactElement } from 'react';
3
+ import { LiteralUnion } from 'type-fest';
4
+ type Props = {
5
+ /**
6
+ * Accent color for the content border.
7
+ * @default "blue"
8
+ */
9
+ readonly accentColor?: LiteralUnion<AnsiColors, string>;
10
+ /**
11
+ * The trigger element the popover is anchored to.
12
+ */
13
+ readonly anchor: ReactNode;
14
+ /**
15
+ * The floating content, shown in a bordered box while open.
16
+ */
17
+ readonly children: ReactNode;
18
+ /**
19
+ * Whether the popover content is shown.
20
+ */
21
+ readonly isOpen: boolean;
22
+ /**
23
+ * Fires when Escape is pressed while open.
24
+ */
25
+ readonly onClose?: () => void;
26
+ /**
27
+ * Whether the content sits above or below the anchor.
28
+ * @default "bottom"
29
+ */
30
+ readonly placement?: "bottom" | "top";
31
+ };
32
+ /**
33
+ * Floating content anchored to a trigger element. The anchor always renders;
34
+ * the bordered content appears above or below it while `isOpen`, and Escape
35
+ * fires `onClose`. Terminals have no true overlay layer, so the content takes
36
+ * layout space rather than floating over other content.
37
+ */
38
+ declare function Popover({ accentColor, anchor, children, isOpen, onClose, placement }: Props): ReactElement;
39
+ export { Popover, type Props as PopoverProps, Props, Popover as default };
@@ -0,0 +1 @@
1
+ import{jsx as a,jsxs as m}from"react/jsx-runtime";import i from"@visulima/tui/components/box";import p from"@visulima/tui/hooks/use-input";import{useCallback as f}from"react";function C({accentColor:n="blue",anchor:l,children:c,isOpen:o,onClose:r,placement:e="bottom"}){const d=f((u,s)=>{s.escape&&r?.()},[r]);p(d,{isActive:o});const t=o?a(i,{alignSelf:"flex-start",borderColor:n,borderStyle:"round",paddingX:1,children:c}):void 0;return m(i,{flexDirection:"column",children:[e==="top"?t:void 0,l,e==="bottom"?t:void 0]})}export{C as Popover,C as default};
@@ -0,0 +1,43 @@
1
+ import { Props as Props$1 } from '@visulima/tui/components/text';
2
+ import { ReactElement } from 'react';
3
+ type Props = {
4
+ /** Fill glyph repeated across the completed portion of the progress bar. Defaults to "█". */
5
+ readonly character?: string;
6
+ /**
7
+ * Override the terminal width used for calculation.
8
+ * When 0, uses the actual terminal width.
9
+ * @default 0
10
+ */
11
+ readonly columns?: number;
12
+ /**
13
+ * Number of columns reserved on the left side (e.g. for labels).
14
+ * @default 0
15
+ */
16
+ readonly left?: number;
17
+ /**
18
+ * Completion percentage between 0 and 1.
19
+ * @default 1
20
+ */
21
+ readonly percent?: number;
22
+ /**
23
+ * Number of columns reserved on the right side (e.g. for percentage text).
24
+ * @default 0
25
+ */
26
+ readonly right?: number;
27
+ /**
28
+ * Whether to pad the remaining space with whitespace.
29
+ * Useful when rendering the bar over a background color.
30
+ * @default false
31
+ */
32
+ readonly rightPad?: boolean;
33
+ } & Omit<Props$1, "children">;
34
+ /**
35
+ * A terminal progress bar that fills proportionally to a percentage value.
36
+ *
37
+ * ```tsx
38
+ * &lt;ProgressBar percent={0.5} />
39
+ * &lt;ProgressBar percent={0.75} color="green" left={10} right={5} />
40
+ * ```
41
+ */
42
+ declare function ProgressBar({ character, columns: columnsProp, left, percent, right, rightPad, ...textProps }: Props): ReactElement;
43
+ export { ProgressBar, type Props as ProgressBarProps, Props, ProgressBar as default };
@@ -0,0 +1 @@
1
+ import{jsx as f}from"react/jsx-runtime";import l from"@visulima/tui/components/text";import p from"@visulima/tui/hooks/use-window-size";function d({character:a="█",columns:e=0,left:o=0,percent:m=1,right:i=0,rightPad:n=!1,...c}){const{columns:h}=p(),r=Math.max(0,(e||h||80)-i-o),t=Math.min(Math.floor(r*Math.max(0,Math.min(1,m))),r),s=a.repeat(t)+(n?" ".repeat(r-t):"");return f(l,{...c,children:s})}export{d as ProgressBar,d as default};
@@ -0,0 +1,50 @@
1
+ import { AnsiColors } from '@visulima/colorize';
2
+ import { ReactElement } from 'react';
3
+ import { LiteralUnion } from 'type-fest';
4
+ type Props = {
5
+ /**
6
+ * Color of the filled portion of the ring.
7
+ * @default "cyan"
8
+ */
9
+ readonly color?: LiteralUnion<AnsiColors, string>;
10
+ /**
11
+ * Format the centered readout. Defaults to a rounded percentage.
12
+ */
13
+ readonly formatValue?: (ratio: number) => string;
14
+ /**
15
+ * Upper bound of the range.
16
+ * @default 100
17
+ */
18
+ readonly max?: number;
19
+ /**
20
+ * Lower bound of the range.
21
+ * @default 0
22
+ */
23
+ readonly min?: number;
24
+ /**
25
+ * Show the readout in the middle of the ring.
26
+ * @default true
27
+ */
28
+ readonly showValue?: boolean;
29
+ /**
30
+ * Diameter of the ring in character rows.
31
+ * @default 8
32
+ */
33
+ readonly size?: number;
34
+ /**
35
+ * Color of the unfilled track behind the ring.
36
+ * @default "gray"
37
+ */
38
+ readonly trackColor?: LiteralUnion<AnsiColors, string>;
39
+ /**
40
+ * Current value, clamped into `[min, max]`.
41
+ */
42
+ readonly value: number;
43
+ };
44
+ /**
45
+ * A circular progress ring rendered at 2x4 braille sub-cell resolution. The
46
+ * filled arc grows clockwise from the top in proportion to `value`, with an
47
+ * optional centered readout.
48
+ */
49
+ declare function ProgressCircle({ color, formatValue, max, min, showValue, size, trackColor, value }: Props): ReactElement;
50
+ export { ProgressCircle, type Props as ProgressCircleProps, Props, ProgressCircle as default };
@@ -0,0 +1 @@
1
+ import{jsxs as V,jsx as e}from"react/jsx-runtime";import{createBrailleGrid as p}from"@visulima/tui/canvas";import w from"@visulima/tui/components/box";import b from"@visulima/tui/components/canvas";import z from"@visulima/tui/components/text";const A=o=>`${Math.round(o*100)}%`;function E({color:o="cyan",formatValue:P=A,max:l=100,min:r=0,showValue:I=!0,size:m=8,trackColor:c="gray",value:M}){const v=l-r||1,s=Math.max(0,Math.min(1,(M-r)/v)),h=Math.max(3,m),y=Math.max(3,Math.round(h*4/2/2));return V(w,{alignItems:"center",flexDirection:"column",children:[e(b,{draw:t=>{t.clear();const C=t.width*2,j=t.height*4,u=C/2,d=j/2,i=Math.max(1,Math.min(u,d)-1),f=p(t.width,t.height),x=p(t.width,t.height),n=Math.max(64,Math.round(Math.PI*2*i)),k=Math.round(s*n);for(let a=0;a<n;a+=1){const g=a/n*Math.PI*2-Math.PI/2,B=Math.round(u+i*Math.cos(g)),G=Math.round(d+i*Math.sin(g));(a<k?x:f).plotPoint(B,G)}f.flush(t,{color:c}),x.flush(t,{color:o})},height:h,version:[M,r,l,m,o,c],width:y}),I?e(w,{marginTop:-Math.ceil(h/2)-1,children:e(z,{bold:!0,color:o,children:P(s)})}):void 0]})}export{E as ProgressCircle,E as default};
@@ -0,0 +1,39 @@
1
+ import { AnsiColors } from '@visulima/colorize';
2
+ import { ReactElement } from 'react';
3
+ import { LiteralUnion } from 'type-fest';
4
+ type Color = LiteralUnion<AnsiColors, string>;
5
+ type Props = {
6
+ /**
7
+ * Color of the "dark" modules.
8
+ * @default "black"
9
+ */
10
+ readonly darkColor?: Color;
11
+ /**
12
+ * Color of the "light" modules and quiet zone.
13
+ * @default "white"
14
+ */
15
+ readonly lightColor?: Color;
16
+ /**
17
+ * A pre-encoded square grid of modules (`true` = dark). Provide this to
18
+ * render without the optional `qrcode` peer; it takes precedence over
19
+ * `value`.
20
+ */
21
+ readonly matrix?: ReadonlyArray<ReadonlyArray<boolean>>;
22
+ /**
23
+ * Width of the light border around the code, in modules.
24
+ * @default 2
25
+ */
26
+ readonly quietZone?: number;
27
+ /**
28
+ * A string to encode. Requires the optional `qrcode` peer dependency; when
29
+ * absent, provide `matrix` instead.
30
+ */
31
+ readonly value?: string;
32
+ };
33
+ /**
34
+ * Render a QR code from a module matrix using the upper-half-block technique,
35
+ * so two module rows share one character row. Pass `matrix` for a self-contained
36
+ * render, or `value` to encode a string via the optional `qrcode` peer.
37
+ */
38
+ declare function QrCode({ darkColor, lightColor, matrix, quietZone, value }: Props): ReactElement;
39
+ export { Props, QrCode, type Props as QrCodeProps, QrCode as default };
@@ -0,0 +1 @@
1
+ import{jsx as h}from"react/jsx-runtime";import g from"@visulima/tui/components/box";import C from"@visulima/tui/components/text";import{useState as k,useEffect as y,useMemo as z}from"react";const E="▀",b=(s,e)=>{if(e<=0)return s.map(o=>[...o]);let c=0;for(const o of s)c=Math.max(c,o.length);const m=c+e*2,t=()=>{const o=[];for(let r=0;r<m;r+=1)o.push(!1);return o},n=[];for(let o=0;o<e;o+=1)n.push(t());for(const o of s){const r=t();o.forEach((l,d)=>{r[d+e]=l}),n.push(r)}for(let o=0;o<e;o+=1)n.push(t());return n};function q({darkColor:s="black",lightColor:e="white",matrix:c,quietZone:m=2,value:t}){const[n,o]=k(void 0);y(()=>{if(c!==void 0||t===void 0)return;let i=!1;return(async()=>{try{const v=await import("qrcode"),{modules:u}=v.create(t),a=[];for(let f=0;f<u.size;f+=1){const p=[];for(let x=0;x<u.size;x+=1)p.push(!!u.data[f*u.size+x]);a.push(p)}i||o(a)}catch{}})().catch(()=>{}),()=>{i=!0}},[c,t]);const r=c??n,l=z(()=>{if(r!==void 0)return b(r,m)},[m,r]);if(l===void 0)return h(g,{children:h(C,{dimColor:!0,children:t===void 0?"":"Encoding…"})});const d=[];for(let i=0;i<l.length;i+=2){const v=l[i]??[],u=l[i+1]??[],a=[];for(const[f,p]of v.entries())a.push(h(C,{backgroundColor:u[f]?s:e,color:p?s:e,children:E},f));d.push(h(g,{children:a},i))}return h(g,{flexDirection:"column",children:d})}export{q as QrCode,q as default};
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "accordion",
4
+ "type": "registry:ui",
5
+ "title": "Accordion",
6
+ "description": "Accordion component for @visulima/tui.",
7
+ "categories": [
8
+ "ui"
9
+ ],
10
+ "dependencies": [
11
+ "@visulima/colorize",
12
+ "@visulima/tui",
13
+ "type-fest"
14
+ ],
15
+ "registryDependencies": [],
16
+ "files": [
17
+ {
18
+ "content": "/* eslint-disable react/function-component-definition */\nimport type { AnsiColors } from \"@visulima/colorize\";\nimport Box from \"@visulima/tui/components/box\";\nimport Text from \"@visulima/tui/components/text\";\nimport useFocus from \"@visulima/tui/hooks/use-focus\";\nimport useInput from \"@visulima/tui/hooks/use-input\";\nimport type { ReactElement, ReactNode } from \"react\";\nimport { useCallback, useRef, useState } from \"react\";\nimport type { LiteralUnion } from \"type-fest\";\n\nexport type AccordionItem = {\n /**\n * Content rendered when the item is expanded.\n */\n readonly content: ReactNode;\n\n /**\n * Unique identifier.\n */\n readonly id: string;\n\n /**\n * Header label.\n */\n readonly title: ReactNode;\n};\n\nexport type Props = {\n /**\n * Accent color for the focused header.\n * @default \"blue\"\n */\n readonly accentColor?: LiteralUnion<AnsiColors, string>;\n\n /**\n * When true, multiple panels can be open at once.\n * @default false\n */\n readonly allowMultiple?: boolean;\n\n /**\n * Auto-focus the first item on mount.\n */\n readonly autoFocus?: boolean;\n\n /**\n * Initially open item ids.\n */\n readonly defaultExpanded?: ReadonlyArray<string>;\n\n /**\n * Disable interaction.\n */\n readonly isDisabled?: boolean;\n\n /**\n * List of panels.\n */\n readonly items: ReadonlyArray<AccordionItem>;\n};\n\n/**\n * Keyboard-navigable accordion. Use arrow keys to move between panels and\n * Enter / Space to toggle.\n * @returns A `ReactElement` rendering the accordion column.\n */\nexport default function Accordion({\n accentColor = \"blue\",\n allowMultiple = false,\n autoFocus = false,\n defaultExpanded,\n isDisabled = false,\n items,\n}: Props): ReactElement {\n const { isFocused } = useFocus({ autoFocus, isActive: !isDisabled });\n const [expanded, setExpanded] = useState<ReadonlyArray<string>>(() => {\n if (defaultExpanded === undefined || defaultExpanded.length === 0) {\n return [];\n }\n\n // When `allowMultiple` is false the accordion only ever shows one open\n // panel — clamp the seed so the initial render matches that contract.\n if (!allowMultiple) {\n return defaultExpanded.slice(0, 1);\n }\n\n return defaultExpanded;\n });\n const [focusedIndex, setFocusedIndex] = useState(0);\n const focusedIndexRef = useRef(focusedIndex);\n\n focusedIndexRef.current = focusedIndex;\n\n const toggle = useCallback(\n (id: string) => {\n setExpanded((previous) => {\n const isOpen = previous.includes(id);\n\n if (allowMultiple) {\n return isOpen ? previous.filter((x) => x !== id) : [...previous, id];\n }\n\n return isOpen ? [] : [id];\n });\n },\n [allowMultiple],\n );\n\n useInput(\n useCallback(\n (input, key) => {\n if (!isFocused) {\n return;\n }\n\n const total = items.length;\n\n if ((key.downArrow || input === \"j\") && focusedIndexRef.current < total - 1) {\n setFocusedIndex(focusedIndexRef.current + 1);\n } else if ((key.upArrow || input === \"k\") && focusedIndexRef.current > 0) {\n setFocusedIndex(focusedIndexRef.current - 1);\n } else if (key.return || input === \" \") {\n const target = items[focusedIndexRef.current];\n\n if (target) {\n toggle(target.id);\n }\n }\n },\n [isFocused, items, toggle],\n ),\n { isActive: !isDisabled && isFocused },\n );\n\n return (\n <Box flexDirection=\"column\">\n {items.map((item, index) => {\n const isOpen = expanded.includes(item.id);\n const isItemFocused = isFocused && index === focusedIndex;\n const color = isItemFocused ? accentColor : undefined;\n\n return (\n <Box flexDirection=\"column\" key={item.id}>\n <Box>\n <Text color={color} dimColor={isDisabled}>\n {isItemFocused ? \"▸ \" : \" \"}\n {isOpen ? \"▼ \" : \"▶ \"}\n </Text>\n <Text bold={isItemFocused} color={color} dimColor={isDisabled}>\n {item.title}\n </Text>\n </Box>\n {isOpen\n ? (\n <Box flexDirection=\"column\" marginBottom={1} marginLeft={4}>\n {item.content}\n </Box>\n )\n : undefined}\n </Box>\n );\n })}\n </Box>\n );\n}\n\nexport { Accordion };\nexport type { Props as AccordionProps };\n",
19
+ "path": "registry/ui/accordion.tsx",
20
+ "target": "components/ui/accordion.tsx",
21
+ "type": "registry:ui"
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "alert",
4
+ "type": "registry:ui",
5
+ "title": "Alert",
6
+ "description": "Alert component for @visulima/tui.",
7
+ "categories": [
8
+ "ui"
9
+ ],
10
+ "dependencies": [
11
+ "@visulima/tui"
12
+ ],
13
+ "registryDependencies": [
14
+ "https://visulima.com/r/variant-config.json"
15
+ ],
16
+ "files": [
17
+ {
18
+ "content": "/* eslint-disable react/function-component-definition */\n\n/**\n * Alert component for Ink.\n *\n * Inspired by ink-ui Alert by Vadim Demedes.\n * @see https://github.com/vadimdemedes/ink-ui\n *\n * MIT License\n * Copyright (c) Vadym Demedes (github.com/vadimdemedes)\n */\nimport Box from \"@visulima/tui/components/box\";\nimport Text from \"@visulima/tui/components/text\";\nimport type { ReactElement, ReactNode } from \"react\";\n\nimport type { Variant } from \"@/lib/variant-config\";\nimport VARIANT_CONFIG from \"@/lib/variant-config\";\n\nexport type AlertVariant = Variant;\n\nexport type Props = {\n /**\n * Alert message content.\n */\n readonly children: ReactNode;\n\n /**\n * Optional heading displayed above the message.\n */\n readonly title?: string;\n\n /**\n * Determines the border color and icon.\n */\n readonly variant: AlertVariant;\n};\n\n/**\n * Renders a bordered alert box with a variant-specific icon, optional title, and message.\n */\nexport default function Alert({ children, title, variant }: Props): ReactElement {\n const { color, icon } = VARIANT_CONFIG[variant];\n\n return (\n <Box borderColor={color} borderStyle=\"round\" flexGrow={1} gap={1} paddingX={1}>\n <Box flexShrink={0}>\n <Text color={color}>{icon}</Text>\n </Box>\n <Box flexDirection=\"column\" flexGrow={1} flexShrink={1} gap={1} minWidth={0}>\n {title ? <Text bold>{title}</Text> : undefined}\n <Text>{children}</Text>\n </Box>\n </Box>\n );\n}\n\nexport { Alert };\nexport type { Props as AlertProps };\n",
19
+ "path": "registry/ui/alert.tsx",
20
+ "target": "components/ui/alert.tsx",
21
+ "type": "registry:ui"
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "animate-presence",
4
+ "type": "registry:ui",
5
+ "title": "Animate Presence",
6
+ "description": "Animate Presence component for @visulima/tui.",
7
+ "categories": [
8
+ "ui"
9
+ ],
10
+ "dependencies": [
11
+ "@visulima/tui"
12
+ ],
13
+ "registryDependencies": [],
14
+ "files": [
15
+ {
16
+ "content": "/* eslint-disable react/function-component-definition */\nimport Box from \"@visulima/tui/components/box\";\nimport type { ReactElement, ReactNode } from \"react\";\nimport { Children, cloneElement, isValidElement, useCallback, useEffect, useState } from \"react\";\n\ntype AnimatableProps = {\n readonly onExit?: () => void;\n readonly show?: boolean;\n};\n\nexport type Props = {\n /**\n * Keyed children. Any child that accepts a `show: boolean` prop and calls\n * `onExit` when its exit transition finishes (such as `&lt;Transition />`) is\n * orchestrated automatically. Other children render as-is.\n */\n readonly children: ReactNode;\n};\n\ntype Slot = {\n readonly element: ReactElement<AnimatableProps>;\n readonly key: string;\n readonly show: boolean;\n};\n\n/**\n * Normalize a ReactElement's `key` to a string (or `undefined` when the\n * caller forgot to set one). AnimatePresence ignores children without a key\n * because tracking identity across renders requires it.\n */\nconst getKey = (element: ReactElement): string | undefined => {\n const { key } = element;\n\n if (key === null) {\n return undefined;\n }\n\n return key;\n};\n\n/**\n * Type-guard used to narrow a child to something AnimatePresence can drive.\n * Matches two cases.\n *\n * 1. The element's component type carries a static `isAnimatable = true`\n * marker (this is how built-in `&lt;Transition />` is recognized — consumers\n * can opt in their own wrappers the same way).\n * 2. The element's props object already declares a `show` key, letting\n * inline components participate without a marker.\n */\nconst isAnimatable = (element: ReactElement): element is ReactElement<AnimatableProps> => {\n const type = element.type as { isAnimatable?: boolean } | undefined;\n\n if (type?.isAnimatable === true) {\n return true;\n }\n\n return typeof element.props === \"object\" && element.props !== null && \"show\" in element.props;\n};\n\n/* eslint-disable react-x/no-children-for-each, react-x/no-clone-element -- AnimatePresence intentionally walks and clones React children to track keyed enter/exit */\n\n/**\n * Orchestrates enter / exit animations for keyed children. Wrap a dynamic\n * list of `&lt;Transition />` (or any component accepting `show` + `onExit`) to\n * keep outgoing children mounted until their exit animation finishes.\n */\nexport default function AnimatePresence({ children }: Props): ReactElement {\n const [slots, setSlots] = useState<ReadonlyArray<Slot>>(() => {\n const collected: Slot[] = [];\n\n Children.forEach(children, (child) => {\n if (!isValidElement(child)) {\n return;\n }\n\n const key = getKey(child);\n\n if (key === undefined) {\n return;\n }\n\n collected.push({\n element: isAnimatable(child) ? child : (child as ReactElement<AnimatableProps>),\n key,\n show: true,\n });\n });\n\n return collected;\n });\n\n // Reconcile children on every render — additions marked show=true,\n // removals flipped to show=false so they can animate out. Persisting\n // keys simply get their element updated.\n useEffect(() => {\n const incoming = new Map<string, ReactElement<AnimatableProps>>();\n\n Children.forEach(children, (child) => {\n if (!isValidElement(child)) {\n return;\n }\n\n const key = getKey(child);\n\n if (key === undefined) {\n return;\n }\n\n incoming.set(key, isAnimatable(child) ? child : (child as ReactElement<AnimatableProps>));\n });\n\n // eslint-disable-next-line react-x/set-state-in-effect -- AnimatePresence reconciles slots from children in an effect; \"removed\" slots persist with show=false until exit animation completes, so this is not pure derivation\n setSlots((previous) => {\n const next: Slot[] = [];\n const seen = new Set<string>();\n let changed = false;\n\n for (const slot of previous) {\n if (incoming.has(slot.key)) {\n const nextElement = incoming.get(slot.key)!;\n\n if (!slot.show || slot.element !== nextElement) {\n changed = true;\n }\n\n next.push({\n element: nextElement,\n key: slot.key,\n show: true,\n });\n seen.add(slot.key);\n } else {\n if (slot.show) {\n changed = true;\n }\n\n // Removed in the incoming render; keep rendering with show=false.\n next.push({ ...slot, show: false });\n }\n }\n\n for (const [key, element] of incoming) {\n if (!seen.has(key)) {\n changed = true;\n next.push({ element, key, show: true });\n }\n }\n\n // React bails out of the update if we return the same reference,\n // preventing an infinite effect/setState loop when `children` gets\n // a fresh JSX identity each parent render but is functionally\n // unchanged.\n return changed ? next : previous;\n });\n }, [children]);\n\n const removeSlot = useCallback((key: string) => {\n setSlots((previous) => previous.filter((slot) => slot.key !== key));\n }, []);\n\n return (\n <Box flexDirection=\"column\">\n {slots.map((slot) => {\n if (!isAnimatable(slot.element)) {\n // Still clone so React sees the exact key we stored in\n // the slot (our reconciler and React's key-tracking now\n // agree even if the original element carried a different\n // one).\n return cloneElement(slot.element, { key: slot.key });\n }\n\n const originalOnExit = slot.element.props.onExit;\n\n return cloneElement(slot.element, {\n key: slot.key,\n onExit: () => {\n originalOnExit?.();\n removeSlot(slot.key);\n },\n show: slot.show,\n });\n })}\n </Box>\n );\n}\n\nexport { AnimatePresence };\nexport type { Props as AnimatePresenceProps };\n",
17
+ "path": "registry/ui/animate-presence.tsx",
18
+ "target": "components/ui/animate-presence.tsx",
19
+ "type": "registry:ui"
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "approval-prompt",
4
+ "type": "registry:ui",
5
+ "title": "Approval Prompt",
6
+ "description": "Approval Prompt component for @visulima/tui.",
7
+ "categories": [
8
+ "ui"
9
+ ],
10
+ "dependencies": [
11
+ "@visulima/colorize",
12
+ "@visulima/tui",
13
+ "type-fest"
14
+ ],
15
+ "registryDependencies": [],
16
+ "files": [
17
+ {
18
+ "content": "/* eslint-disable react/function-component-definition */\nimport type { AnsiColors } from \"@visulima/colorize\";\nimport Box from \"@visulima/tui/components/box\";\nimport Text from \"@visulima/tui/components/text\";\nimport useFocus from \"@visulima/tui/hooks/use-focus\";\nimport useInput from \"@visulima/tui/hooks/use-input\";\nimport type { ReactElement, ReactNode } from \"react\";\nimport { useCallback } from \"react\";\nimport type { LiteralUnion } from \"type-fest\";\n\nexport type ApprovalRisk = \"high\" | \"low\" | \"medium\";\n\nexport type ApprovalDecision = \"allow-always\" | \"allow-once\" | \"deny\";\n\nexport type Props = {\n /**\n * Accent color for the highlighted option letters. Overrides the risk color.\n */\n readonly accentColor?: LiteralUnion<AnsiColors, string>;\n\n /**\n * Auto-focus the prompt on mount. Set to `false` if a parent already\n * owns focus and routes input to this component.\n * @default true\n */\n readonly autoFocus?: boolean;\n\n /**\n * Optional body rendered between the header and the prompt line (e.g. a\n * rendered diff or command preview).\n */\n readonly children?: ReactNode;\n\n /**\n * Accessible description of what approving will do.\n */\n readonly description?: string;\n\n /**\n * Disable input; use when a parent manages focus and input routing.\n */\n readonly isDisabled?: boolean;\n\n /**\n * Resolution callback. Called once the user answers.\n */\n readonly onDecision: (decision: ApprovalDecision) => void;\n\n /**\n * Params shown as a compact key=value preview.\n */\n readonly params?: Readonly<Record<string, unknown>>;\n\n /**\n * Severity badge; controls the border color.\n * @default \"medium\"\n */\n readonly risk?: ApprovalRisk;\n\n /**\n * Name of the tool / action under review.\n */\n readonly tool: string;\n};\n\nconst RISK_COLOR: Record<ApprovalRisk, LiteralUnion<AnsiColors, string>> = {\n high: \"red\",\n low: \"green\",\n medium: \"yellow\",\n};\n\nconst RISK_LABEL: Record<ApprovalRisk, string> = {\n high: \"HIGH RISK\",\n low: \"LOW RISK\",\n medium: \"MEDIUM RISK\",\n};\n\nconst formatParameterValue = (value: unknown): string => {\n if (value === null) {\n return \"null\";\n }\n\n if (typeof value === \"string\") {\n return value;\n }\n\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return String(value);\n }\n\n try {\n return JSON.stringify(value);\n } catch {\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- last-resort fallback when value isn't JSON-serializable\n return String(value);\n }\n};\n\n/**\n * Three-way approval prompt for tool calls. Resolves via `onDecision`:\n * `y` / Enter → allow-once, `a` → allow-always, `n` / Esc → deny.\n * @returns A bordered `ReactElement` containing the risk banner, params\n * preview, optional body, and the prompt line.\n */\nexport default function ApprovalPrompt({\n accentColor,\n autoFocus = true,\n children,\n description,\n isDisabled = false,\n onDecision,\n params,\n risk = \"medium\",\n tool,\n}: Props): ReactElement {\n const riskColor = RISK_COLOR[risk];\n const color = accentColor ?? riskColor;\n const { isFocused } = useFocus({ autoFocus, isActive: !isDisabled });\n\n useInput(\n useCallback(\n (input, key) => {\n const typed = input.toLowerCase();\n\n if (typed === \"y\" || key.return) {\n onDecision(\"allow-once\");\n\n return;\n }\n\n if (typed === \"a\") {\n onDecision(\"allow-always\");\n\n return;\n }\n\n if (typed === \"n\" || key.escape) {\n onDecision(\"deny\");\n }\n },\n [onDecision],\n ),\n { isActive: !isDisabled && isFocused },\n );\n\n const parameterEntries = params === undefined ? [] : Object.entries(params);\n\n return (\n <Box borderColor={riskColor} borderStyle=\"round\" flexDirection=\"column\" paddingX={1}>\n <Box>\n <Text backgroundColor={riskColor} color=\"black\">\n {\" \"}\n {RISK_LABEL[risk]}\n{\" \"}\n </Text>\n <Text>\n {\" \"}\n <Text bold>{tool}</Text>\n </Text>\n </Box>\n {description === undefined\n ? undefined\n : (\n <Box marginTop={1}>\n <Text>{description}</Text>\n </Box>\n )}\n {parameterEntries.length === 0\n ? undefined\n : (\n <Box flexDirection=\"column\" marginTop={1}>\n {parameterEntries.map(([name, value]) => (\n <Box key={name}>\n <Text color=\"cyan\">\n{name}\n=\n </Text>\n <Text wrap=\"truncate-end\">{formatParameterValue(value)}</Text>\n </Box>\n ))}\n </Box>\n )}\n {children === undefined ? undefined : <Box marginTop={1}>{children}</Box>}\n <Box marginTop={1}>\n <Text>\n Allow? [\n <Text bold color={color}>\n y\n </Text>\n es /\n{\" \"}\n <Text bold color={color}>\n a\n </Text>\n lways /\n{\" \"}\n <Text bold color={color}>\n n\n </Text>\n o ]\n </Text>\n </Box>\n </Box>\n );\n}\n\nexport { ApprovalPrompt };\nexport type { Props as ApprovalPromptProps };\n",
19
+ "path": "registry/ui/approval-prompt.tsx",
20
+ "target": "components/ui/approval-prompt.tsx",
21
+ "type": "registry:ui"
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "area-chart",
4
+ "type": "registry:ui",
5
+ "title": "Area Chart",
6
+ "description": "Area Chart component for @visulima/tui.",
7
+ "categories": [
8
+ "ui"
9
+ ],
10
+ "dependencies": [
11
+ "@visulima/colorize",
12
+ "@visulima/tui",
13
+ "type-fest"
14
+ ],
15
+ "registryDependencies": [
16
+ "https://visulima.com/r/chart-utils.json",
17
+ "https://visulima.com/r/line-chart.json"
18
+ ],
19
+ "files": [
20
+ {
21
+ "content": "/* eslint-disable react/function-component-definition */\nimport type { AnsiColors } from \"@visulima/colorize\";\nimport type { CanvasContext } from \"@visulima/tui/canvas\";\nimport { createBrailleGrid } from \"@visulima/tui/canvas\";\nimport Box from \"@visulima/tui/components/box\";\nimport Canvas from \"@visulima/tui/components/canvas\";\nimport Text from \"@visulima/tui/components/text\";\nimport type { ReactElement } from \"react\";\nimport { useMemo } from \"react\";\nimport type { LiteralUnion } from \"type-fest\";\n\nimport { computeExtents, DEFAULT_CHART_PALETTE, pickSeriesColor, toPoints } from \"@/lib/chart-utils\";\nimport type { LineSeries } from \"@/components/ui/line-chart\";\nimport { projectPoint } from \"@/components/ui/line-chart\";\n\nexport type Props = {\n /**\n * Axis label color.\n * @default \"gray\"\n */\n readonly axisColor?: LiteralUnion<AnsiColors, string>;\n\n /**\n * Density of the shaded fill below each line.\n * - `light` → `░`\n * - `medium` → `▒` (default)\n * - `heavy` → `▓`\n */\n readonly fillDensity?: \"heavy\" | \"light\" | \"medium\";\n\n /**\n * Chart body height in character rows.\n * @default 10\n */\n readonly height?: number;\n\n /**\n * Upper bound for the y-axis.\n */\n readonly maxY?: number;\n\n /**\n * Lower bound for the y-axis.\n */\n readonly minY?: number;\n\n /**\n * Fallback palette.\n */\n readonly palette?: ReadonlyArray<LiteralUnion<AnsiColors, string>>;\n\n /**\n * Series to plot.\n */\n readonly series: ReadonlyArray<LineSeries>;\n\n /**\n * Render the legend below the plot.\n * @default true\n */\n readonly showLegend?: boolean;\n\n /**\n * Chart body width in character columns.\n * @default 40\n */\n readonly width?: number;\n};\n\nconst DEFAULT_PALETTE = DEFAULT_CHART_PALETTE;\n\nconst FILL_GLYPH: Record<Required<Props>[\"fillDensity\"], string> = {\n heavy: \"▓\",\n light: \"░\",\n medium: \"▒\",\n};\n\n/**\n * Area chart: line chart with the area between the line and the baseline\n * filled with shaded blocks.\n */\nexport default function AreaChart({\n axisColor = \"gray\",\n fillDensity = \"medium\",\n height = 10,\n maxY,\n minY,\n palette = DEFAULT_PALETTE,\n series,\n showLegend = true,\n width = 40,\n}: Props): ReactElement {\n const prepared = useMemo(() => {\n const list = series.map((input, index) => {\n return {\n color: pickSeriesColor(input, index, palette),\n points: toPoints(input.data),\n series: input,\n };\n });\n\n const extents = computeExtents(list);\n\n return {\n list,\n xMax: extents.xMax === Number.NEGATIVE_INFINITY ? 1 : extents.xMax,\n xMin: extents.xMin === Number.POSITIVE_INFINITY ? 0 : extents.xMin,\n yMax: maxY ?? (extents.yMax === Number.NEGATIVE_INFINITY ? 1 : extents.yMax),\n yMin: minY ?? (extents.yMin === Number.POSITIVE_INFINITY ? 0 : extents.yMin),\n };\n }, [series, palette, minY, maxY]);\n\n const glyph = FILL_GLYPH[fillDensity];\n\n return (\n <Box flexDirection=\"column\">\n <Canvas\n // eslint-disable-next-line react-perf/jsx-no-new-function-as-prop -- canvas re-renders on `version` change, not draw identity\n draw={(context: CanvasContext) => {\n context.clear();\n\n const pixelWidth = context.width * 2;\n const pixelHeight = context.height * 4;\n const config = {\n xMax: prepared.xMax,\n xMin: prepared.xMin,\n yMax: prepared.yMax,\n yMin: prepared.yMin,\n };\n\n // Fill layer — drawn below the line so the braille overlay stays crisp.\n for (const { color, points } of prepared.list) {\n if (points.length === 0) {\n continue;\n }\n\n // Sample the line once per character column and fill from\n // that row down to the baseline.\n for (let cellX = 0; cellX < context.width; cellX += 1) {\n // Find the data point that projects into this column.\n // Linear scan is fine for reasonable point counts;\n // larger datasets should pre-resample. One\n // projection per point — reuse px AND py.\n let sampleY: number | undefined;\n\n for (const point of points) {\n const { px, py } = projectPoint(point, config, pixelWidth, pixelHeight);\n const candidateCellX = Math.floor(px / 2);\n\n if (candidateCellX === cellX) {\n const candidateCellY = Math.floor(py / 4);\n\n if (sampleY === undefined || candidateCellY < sampleY) {\n sampleY = candidateCellY;\n }\n }\n }\n\n if (sampleY === undefined) {\n continue;\n }\n\n for (let cellY = sampleY + 1; cellY < context.height; cellY += 1) {\n context.setCell(cellX, cellY, glyph, { color, dim: true });\n }\n }\n }\n\n // Dashed baseline.\n for (let cellX = 0; cellX < context.width; cellX += 2) {\n context.setCell(cellX, context.height - 1, \"·\", { color: axisColor, dim: true });\n }\n\n // Line overlay using braille.\n for (const { color, points } of prepared.list) {\n if (points.length === 0) {\n continue;\n }\n\n const grid = createBrailleGrid(context.width, context.height);\n let previous = projectPoint(points[0]!, config, pixelWidth, pixelHeight);\n\n grid.plotPoint(previous.px, previous.py);\n\n for (let index = 1; index < points.length; index += 1) {\n const current = projectPoint(points[index]!, config, pixelWidth, pixelHeight);\n\n grid.plotLine(previous.px, previous.py, current.px, current.py);\n previous = current;\n }\n\n grid.flush(context, { bold: true, color });\n }\n }}\n height={height}\n // eslint-disable-next-line react-perf/jsx-no-new-array-as-prop -- version array is the canvas redraw key\n version={[series, width, height, minY, maxY, fillDensity, axisColor, palette]}\n width={width}\n />\n {/* eslint-disable-next-line @stylistic/multiline-ternary -- prettier formats JSX ternaries on one line */}\n {showLegend ? (\n <Box gap={2} marginTop={1}>\n {prepared.list.map(({ color, series: input }, index) => (\n // Composite key: series index plus label so two\n // series sharing a label don't collide.\n // eslint-disable-next-line react-x/no-array-index-key -- series index is stable for the render\n <Box gap={1} key={`${index}:${input.label ?? \"\"}`}>\n <Text color={color}>{glyph}</Text>\n <Text>{input.label ?? `Series ${index + 1}`}</Text>\n </Box>\n ))}\n </Box>\n ) : undefined}\n </Box>\n );\n}\n\nexport { AreaChart };\nexport type { Props as AreaChartProps };\n",
22
+ "path": "registry/ui/area-chart.tsx",
23
+ "target": "components/ui/area-chart.tsx",
24
+ "type": "registry:ui"
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "badge",
4
+ "type": "registry:ui",
5
+ "title": "Badge",
6
+ "description": "Badge component for @visulima/tui.",
7
+ "categories": [
8
+ "ui"
9
+ ],
10
+ "dependencies": [
11
+ "@visulima/colorize",
12
+ "@visulima/tui",
13
+ "type-fest"
14
+ ],
15
+ "registryDependencies": [],
16
+ "files": [
17
+ {
18
+ "content": "/* eslint-disable react/function-component-definition */\n\n/**\n * Badge component for Ink.\n *\n * Inspired by ink-ui Badge by Vadim Demedes.\n * @see https://github.com/vadimdemedes/ink-ui\n *\n * MIT License\n * Copyright (c) Vadym Demedes (github.com/vadimdemedes)\n */\nimport type { AnsiColors } from \"@visulima/colorize\";\nimport Text from \"@visulima/tui/components/text\";\nimport type { ReactElement, ReactNode } from \"react\";\nimport type { LiteralUnion } from \"type-fest\";\n\nexport type Props = {\n /**\n * Label content. Strings are automatically uppercased.\n */\n readonly children: ReactNode;\n\n /**\n * Background color for the badge.\n * @default \"magenta\"\n */\n readonly color?: LiteralUnion<AnsiColors, string>;\n};\n\n/**\n * Renders an uppercase colored label badge, useful for status indicators.\n */\nexport default function Badge({ children, color = \"magenta\" }: Props): ReactElement {\n const formattedChildren = typeof children === \"string\" ? children.toUpperCase() : children;\n\n return (\n <Text backgroundColor={color}>\n {\" \"}\n <Text color=\"black\">{formattedChildren}</Text>\n{\" \"}\n </Text>\n );\n}\n\nexport { Badge };\nexport type { Props as BadgeProps };\n",
19
+ "path": "registry/ui/badge.tsx",
20
+ "target": "components/ui/badge.tsx",
21
+ "type": "registry:ui"
22
+ }
23
+ ]
24
+ }