@tai42/studio-sdk 0.2.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 (422) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +15 -0
  3. package/README.md +318 -0
  4. package/SECURITY.md +51 -0
  5. package/dist/components/badge.d.ts +13 -0
  6. package/dist/components/badge.d.ts.map +1 -0
  7. package/dist/components/badge.js +14 -0
  8. package/dist/components/badge.js.map +1 -0
  9. package/dist/components/checkbox.d.ts +24 -0
  10. package/dist/components/checkbox.d.ts.map +1 -0
  11. package/dist/components/checkbox.js +31 -0
  12. package/dist/components/checkbox.js.map +1 -0
  13. package/dist/components/code-block.d.ts +7 -0
  14. package/dist/components/code-block.d.ts.map +1 -0
  15. package/dist/components/code-block.js +23 -0
  16. package/dist/components/code-block.js.map +1 -0
  17. package/dist/components/completion-input.d.ts +31 -0
  18. package/dist/components/completion-input.d.ts.map +1 -0
  19. package/dist/components/completion-input.js +61 -0
  20. package/dist/components/completion-input.js.map +1 -0
  21. package/dist/components/components.css +2212 -0
  22. package/dist/components/confirm-dialog.d.ts +40 -0
  23. package/dist/components/confirm-dialog.d.ts.map +1 -0
  24. package/dist/components/confirm-dialog.js +11 -0
  25. package/dist/components/confirm-dialog.js.map +1 -0
  26. package/dist/components/control-styles.d.ts +12 -0
  27. package/dist/components/control-styles.d.ts.map +1 -0
  28. package/dist/components/control-styles.js +14 -0
  29. package/dist/components/control-styles.js.map +1 -0
  30. package/dist/components/copy-field.d.ts +8 -0
  31. package/dist/components/copy-field.d.ts.map +1 -0
  32. package/dist/components/copy-field.js +106 -0
  33. package/dist/components/copy-field.js.map +1 -0
  34. package/dist/components/dialog.d.ts +21 -0
  35. package/dist/components/dialog.d.ts.map +1 -0
  36. package/dist/components/dialog.js +25 -0
  37. package/dist/components/dialog.js.map +1 -0
  38. package/dist/components/drawer.d.ts +22 -0
  39. package/dist/components/drawer.d.ts.map +1 -0
  40. package/dist/components/drawer.js +32 -0
  41. package/dist/components/drawer.js.map +1 -0
  42. package/dist/components/error-boundary.d.ts +33 -0
  43. package/dist/components/error-boundary.d.ts.map +1 -0
  44. package/dist/components/error-boundary.js +36 -0
  45. package/dist/components/error-boundary.js.map +1 -0
  46. package/dist/components/extension-combo-builder.d.ts +70 -0
  47. package/dist/components/extension-combo-builder.d.ts.map +1 -0
  48. package/dist/components/extension-combo-builder.js +154 -0
  49. package/dist/components/extension-combo-builder.js.map +1 -0
  50. package/dist/components/extension-grouping.d.ts +65 -0
  51. package/dist/components/extension-grouping.d.ts.map +1 -0
  52. package/dist/components/extension-grouping.js +78 -0
  53. package/dist/components/extension-grouping.js.map +1 -0
  54. package/dist/components/extension-picker.d.ts +35 -0
  55. package/dist/components/extension-picker.d.ts.map +1 -0
  56. package/dist/components/extension-picker.js +31 -0
  57. package/dist/components/extension-picker.js.map +1 -0
  58. package/dist/components/external-link.d.ts +19 -0
  59. package/dist/components/external-link.d.ts.map +1 -0
  60. package/dist/components/external-link.js +12 -0
  61. package/dist/components/external-link.js.map +1 -0
  62. package/dist/components/field.d.ts +64 -0
  63. package/dist/components/field.d.ts.map +1 -0
  64. package/dist/components/field.js +56 -0
  65. package/dist/components/field.js.map +1 -0
  66. package/dist/components/fleet-report.d.ts +39 -0
  67. package/dist/components/fleet-report.d.ts.map +1 -0
  68. package/dist/components/fleet-report.js +50 -0
  69. package/dist/components/fleet-report.js.map +1 -0
  70. package/dist/components/fonts.css +22 -0
  71. package/dist/components/icons.d.ts +142 -0
  72. package/dist/components/icons.d.ts.map +1 -0
  73. package/dist/components/icons.js +139 -0
  74. package/dist/components/icons.js.map +1 -0
  75. package/dist/components/inputs.d.ts +52 -0
  76. package/dist/components/inputs.d.ts.map +1 -0
  77. package/dist/components/inputs.js +35 -0
  78. package/dist/components/inputs.js.map +1 -0
  79. package/dist/components/json-diff.d.ts +40 -0
  80. package/dist/components/json-diff.d.ts.map +1 -0
  81. package/dist/components/json-diff.js +119 -0
  82. package/dist/components/json-diff.js.map +1 -0
  83. package/dist/components/json-tree.d.ts +8 -0
  84. package/dist/components/json-tree.d.ts.map +1 -0
  85. package/dist/components/json-tree.js +70 -0
  86. package/dist/components/json-tree.js.map +1 -0
  87. package/dist/components/modal-focus.d.ts +15 -0
  88. package/dist/components/modal-focus.d.ts.map +1 -0
  89. package/dist/components/modal-focus.js +136 -0
  90. package/dist/components/modal-focus.js.map +1 -0
  91. package/dist/components/page-header.d.ts +56 -0
  92. package/dist/components/page-header.d.ts.map +1 -0
  93. package/dist/components/page-header.js +24 -0
  94. package/dist/components/page-header.js.map +1 -0
  95. package/dist/components/primitives.d.ts +186 -0
  96. package/dist/components/primitives.d.ts.map +1 -0
  97. package/dist/components/primitives.js +204 -0
  98. package/dist/components/primitives.js.map +1 -0
  99. package/dist/components/progress-bar.d.ts +29 -0
  100. package/dist/components/progress-bar.d.ts.map +1 -0
  101. package/dist/components/progress-bar.js +48 -0
  102. package/dist/components/progress-bar.js.map +1 -0
  103. package/dist/components/radio-group.d.ts +37 -0
  104. package/dist/components/radio-group.d.ts.map +1 -0
  105. package/dist/components/radio-group.js +56 -0
  106. package/dist/components/radio-group.js.map +1 -0
  107. package/dist/components/reveal-input.d.ts +13 -0
  108. package/dist/components/reveal-input.d.ts.map +1 -0
  109. package/dist/components/reveal-input.js +31 -0
  110. package/dist/components/reveal-input.js.map +1 -0
  111. package/dist/components/scroll-region.d.ts +85 -0
  112. package/dist/components/scroll-region.d.ts.map +1 -0
  113. package/dist/components/scroll-region.js +574 -0
  114. package/dist/components/scroll-region.js.map +1 -0
  115. package/dist/components/select.d.ts +53 -0
  116. package/dist/components/select.d.ts.map +1 -0
  117. package/dist/components/select.js +33 -0
  118. package/dist/components/select.js.map +1 -0
  119. package/dist/components/table.d.ts +59 -0
  120. package/dist/components/table.d.ts.map +1 -0
  121. package/dist/components/table.js +20 -0
  122. package/dist/components/table.js.map +1 -0
  123. package/dist/components/tabs.d.ts +15 -0
  124. package/dist/components/tabs.d.ts.map +1 -0
  125. package/dist/components/tabs.js +13 -0
  126. package/dist/components/tabs.js.map +1 -0
  127. package/dist/components/tags.d.ts +46 -0
  128. package/dist/components/tags.d.ts.map +1 -0
  129. package/dist/components/tags.js +68 -0
  130. package/dist/components/tags.js.map +1 -0
  131. package/dist/components/tokens.css +392 -0
  132. package/dist/components/tokens.d.ts +15 -0
  133. package/dist/components/tokens.d.ts.map +1 -0
  134. package/dist/components/tokens.js +101 -0
  135. package/dist/components/tokens.js.map +1 -0
  136. package/dist/components/tool-picker.d.ts +24 -0
  137. package/dist/components/tool-picker.d.ts.map +1 -0
  138. package/dist/components/tool-picker.js +97 -0
  139. package/dist/components/tool-picker.js.map +1 -0
  140. package/dist/components/tooltip.d.ts +17 -0
  141. package/dist/components/tooltip.d.ts.map +1 -0
  142. package/dist/components/tooltip.js +18 -0
  143. package/dist/components/tooltip.js.map +1 -0
  144. package/dist/components/version-history-panel.d.ts +71 -0
  145. package/dist/components/version-history-panel.d.ts.map +1 -0
  146. package/dist/components/version-history-panel.js +148 -0
  147. package/dist/components/version-history-panel.js.map +1 -0
  148. package/dist/download.d.ts +7 -0
  149. package/dist/download.d.ts.map +1 -0
  150. package/dist/download.js +18 -0
  151. package/dist/download.js.map +1 -0
  152. package/dist/element-slot.d.ts +4 -0
  153. package/dist/element-slot.d.ts.map +1 -0
  154. package/dist/element-slot.js +20 -0
  155. package/dist/element-slot.js.map +1 -0
  156. package/dist/elicitation/ElicitationForm.d.ts +34 -0
  157. package/dist/elicitation/ElicitationForm.d.ts.map +1 -0
  158. package/dist/elicitation/ElicitationForm.js +24 -0
  159. package/dist/elicitation/ElicitationForm.js.map +1 -0
  160. package/dist/errors.d.ts +9 -0
  161. package/dist/errors.d.ts.map +1 -0
  162. package/dist/errors.js +15 -0
  163. package/dist/errors.js.map +1 -0
  164. package/dist/extension-combos.d.ts +13 -0
  165. package/dist/extension-combos.d.ts.map +1 -0
  166. package/dist/extension-combos.js +9 -0
  167. package/dist/extension-combos.js.map +1 -0
  168. package/dist/hooks/useApi.d.ts +1222 -0
  169. package/dist/hooks/useApi.d.ts.map +1 -0
  170. package/dist/hooks/useApi.js +22 -0
  171. package/dist/hooks/useApi.js.map +1 -0
  172. package/dist/hooks/useAuth.d.ts +13 -0
  173. package/dist/hooks/useAuth.d.ts.map +1 -0
  174. package/dist/hooks/useAuth.js +53 -0
  175. package/dist/hooks/useAuth.js.map +1 -0
  176. package/dist/hooks/useBreakpoint.d.ts +10 -0
  177. package/dist/hooks/useBreakpoint.d.ts.map +1 -0
  178. package/dist/hooks/useBreakpoint.js +66 -0
  179. package/dist/hooks/useBreakpoint.js.map +1 -0
  180. package/dist/hooks/useCapabilities.d.ts +88 -0
  181. package/dist/hooks/useCapabilities.d.ts.map +1 -0
  182. package/dist/hooks/useCapabilities.js +203 -0
  183. package/dist/hooks/useCapabilities.js.map +1 -0
  184. package/dist/hooks/useSse.d.ts +13 -0
  185. package/dist/hooks/useSse.d.ts.map +1 -0
  186. package/dist/hooks/useSse.js +298 -0
  187. package/dist/hooks/useSse.js.map +1 -0
  188. package/dist/hooks/useTheme.d.ts +20 -0
  189. package/dist/hooks/useTheme.d.ts.map +1 -0
  190. package/dist/hooks/useTheme.js +97 -0
  191. package/dist/hooks/useTheme.js.map +1 -0
  192. package/dist/hooks/useUnauthorized.d.ts +3 -0
  193. package/dist/hooks/useUnauthorized.d.ts.map +1 -0
  194. package/dist/hooks/useUnauthorized.js +18 -0
  195. package/dist/hooks/useUnauthorized.js.map +1 -0
  196. package/dist/host.d.ts +11 -0
  197. package/dist/host.d.ts.map +1 -0
  198. package/dist/host.js +10 -0
  199. package/dist/host.js.map +1 -0
  200. package/dist/index.d.ts +118 -0
  201. package/dist/index.d.ts.map +1 -0
  202. package/dist/index.js +91 -0
  203. package/dist/index.js.map +1 -0
  204. package/dist/navigation/context.d.ts +25 -0
  205. package/dist/navigation/context.d.ts.map +1 -0
  206. package/dist/navigation/context.js +45 -0
  207. package/dist/navigation/context.js.map +1 -0
  208. package/dist/navigation/index.d.ts +4 -0
  209. package/dist/navigation/index.d.ts.map +1 -0
  210. package/dist/navigation/index.js +2 -0
  211. package/dist/navigation/index.js.map +1 -0
  212. package/dist/navigation/types.d.ts +97 -0
  213. package/dist/navigation/types.d.ts.map +1 -0
  214. package/dist/navigation/types.js +16 -0
  215. package/dist/navigation/types.js.map +1 -0
  216. package/dist/plugin/host-state.d.ts +45 -0
  217. package/dist/plugin/host-state.d.ts.map +1 -0
  218. package/dist/plugin/host-state.js +62 -0
  219. package/dist/plugin/host-state.js.map +1 -0
  220. package/dist/plugin/registry.d.ts +35 -0
  221. package/dist/plugin/registry.d.ts.map +1 -0
  222. package/dist/plugin/registry.js +129 -0
  223. package/dist/plugin/registry.js.map +1 -0
  224. package/dist/plugin/types.d.ts +165 -0
  225. package/dist/plugin/types.d.ts.map +1 -0
  226. package/dist/plugin/types.js +2 -0
  227. package/dist/plugin/types.js.map +1 -0
  228. package/dist/plugin/version.d.ts +34 -0
  229. package/dist/plugin/version.d.ts.map +1 -0
  230. package/dist/plugin/version.js +37 -0
  231. package/dist/plugin/version.js.map +1 -0
  232. package/dist/query-keys.d.ts +31 -0
  233. package/dist/query-keys.d.ts.map +1 -0
  234. package/dist/query-keys.js +31 -0
  235. package/dist/query-keys.js.map +1 -0
  236. package/dist/schema-editor/SchemaEditor.d.ts +43 -0
  237. package/dist/schema-editor/SchemaEditor.d.ts.map +1 -0
  238. package/dist/schema-editor/SchemaEditor.js +106 -0
  239. package/dist/schema-editor/SchemaEditor.js.map +1 -0
  240. package/dist/schema-editor/index.d.ts +10 -0
  241. package/dist/schema-editor/index.d.ts.map +1 -0
  242. package/dist/schema-editor/index.js +8 -0
  243. package/dist/schema-editor/index.js.map +1 -0
  244. package/dist/schema-editor/lint.d.ts +37 -0
  245. package/dist/schema-editor/lint.d.ts.map +1 -0
  246. package/dist/schema-editor/lint.js +49 -0
  247. package/dist/schema-editor/lint.js.map +1 -0
  248. package/dist/schema-form/SchemaForm.d.ts +70 -0
  249. package/dist/schema-form/SchemaForm.d.ts.map +1 -0
  250. package/dist/schema-form/SchemaForm.js +13 -0
  251. package/dist/schema-form/SchemaForm.js.map +1 -0
  252. package/dist/schema-form/array-field.d.ts +20 -0
  253. package/dist/schema-form/array-field.d.ts.map +1 -0
  254. package/dist/schema-form/array-field.js +39 -0
  255. package/dist/schema-form/array-field.js.map +1 -0
  256. package/dist/schema-form/classify.d.ts +87 -0
  257. package/dist/schema-form/classify.d.ts.map +1 -0
  258. package/dist/schema-form/classify.js +199 -0
  259. package/dist/schema-form/classify.js.map +1 -0
  260. package/dist/schema-form/context.d.ts +4 -0
  261. package/dist/schema-form/context.d.ts.map +1 -0
  262. package/dist/schema-form/context.js +14 -0
  263. package/dist/schema-form/context.js.map +1 -0
  264. package/dist/schema-form/default-value.d.ts +8 -0
  265. package/dist/schema-form/default-value.d.ts.map +1 -0
  266. package/dist/schema-form/default-value.js +64 -0
  267. package/dist/schema-form/default-value.js.map +1 -0
  268. package/dist/schema-form/enum-field.d.ts +19 -0
  269. package/dist/schema-form/enum-field.d.ts.map +1 -0
  270. package/dist/schema-form/enum-field.js +26 -0
  271. package/dist/schema-form/enum-field.js.map +1 -0
  272. package/dist/schema-form/field-group.d.ts +9 -0
  273. package/dist/schema-form/field-group.d.ts.map +1 -0
  274. package/dist/schema-form/field-group.js +32 -0
  275. package/dist/schema-form/field-group.js.map +1 -0
  276. package/dist/schema-form/field-node.d.ts +24 -0
  277. package/dist/schema-form/field-node.d.ts.map +1 -0
  278. package/dist/schema-form/field-node.js +51 -0
  279. package/dist/schema-form/field-node.js.map +1 -0
  280. package/dist/schema-form/index.d.ts +13 -0
  281. package/dist/schema-form/index.d.ts.map +1 -0
  282. package/dist/schema-form/index.js +11 -0
  283. package/dist/schema-form/index.js.map +1 -0
  284. package/dist/schema-form/media-field.d.ts +24 -0
  285. package/dist/schema-form/media-field.d.ts.map +1 -0
  286. package/dist/schema-form/media-field.js +169 -0
  287. package/dist/schema-form/media-field.js.map +1 -0
  288. package/dist/schema-form/media.d.ts +40 -0
  289. package/dist/schema-form/media.d.ts.map +1 -0
  290. package/dist/schema-form/media.js +65 -0
  291. package/dist/schema-form/media.js.map +1 -0
  292. package/dist/schema-form/object-fields.d.ts +20 -0
  293. package/dist/schema-form/object-fields.d.ts.map +1 -0
  294. package/dist/schema-form/object-fields.js +24 -0
  295. package/dist/schema-form/object-fields.js.map +1 -0
  296. package/dist/schema-form/resolve.d.ts +41 -0
  297. package/dist/schema-form/resolve.d.ts.map +1 -0
  298. package/dist/schema-form/resolve.js +102 -0
  299. package/dist/schema-form/resolve.js.map +1 -0
  300. package/dist/schema-form/string-field.d.ts +20 -0
  301. package/dist/schema-form/string-field.d.ts.map +1 -0
  302. package/dist/schema-form/string-field.js +68 -0
  303. package/dist/schema-form/string-field.js.map +1 -0
  304. package/dist/schema-form/styles.d.ts +18 -0
  305. package/dist/schema-form/styles.d.ts.map +1 -0
  306. package/dist/schema-form/styles.js +18 -0
  307. package/dist/schema-form/styles.js.map +1 -0
  308. package/dist/schema-form/types.d.ts +67 -0
  309. package/dist/schema-form/types.d.ts.map +1 -0
  310. package/dist/schema-form/types.js +10 -0
  311. package/dist/schema-form/types.js.map +1 -0
  312. package/dist/schema-form/union-field.d.ts +24 -0
  313. package/dist/schema-form/union-field.d.ts.map +1 -0
  314. package/dist/schema-form/union-field.js +55 -0
  315. package/dist/schema-form/union-field.js.map +1 -0
  316. package/dist/schema-form/union.d.ts +4 -0
  317. package/dist/schema-form/union.d.ts.map +1 -0
  318. package/dist/schema-form/union.js +68 -0
  319. package/dist/schema-form/union.js.map +1 -0
  320. package/dist/schema-form/unsupported-notice.d.ts +18 -0
  321. package/dist/schema-form/unsupported-notice.d.ts.map +1 -0
  322. package/dist/schema-form/unsupported-notice.js +8 -0
  323. package/dist/schema-form/unsupported-notice.js.map +1 -0
  324. package/dist/schema-form/validate.d.ts +18 -0
  325. package/dist/schema-form/validate.d.ts.map +1 -0
  326. package/dist/schema-form/validate.js +162 -0
  327. package/dist/schema-form/validate.js.map +1 -0
  328. package/dist/structured-output/StructuredOutput.d.ts +27 -0
  329. package/dist/structured-output/StructuredOutput.d.ts.map +1 -0
  330. package/dist/structured-output/StructuredOutput.js +41 -0
  331. package/dist/structured-output/StructuredOutput.js.map +1 -0
  332. package/dist/testing.d.ts +80 -0
  333. package/dist/testing.d.ts.map +1 -0
  334. package/dist/testing.js +207 -0
  335. package/dist/testing.js.map +1 -0
  336. package/package.json +101 -0
  337. package/src/components/badge.tsx +31 -0
  338. package/src/components/checkbox.tsx +93 -0
  339. package/src/components/code-block.tsx +37 -0
  340. package/src/components/completion-input.tsx +137 -0
  341. package/src/components/components.css +2212 -0
  342. package/src/components/confirm-dialog.tsx +77 -0
  343. package/src/components/control-styles.ts +14 -0
  344. package/src/components/copy-field.tsx +178 -0
  345. package/src/components/dialog.tsx +76 -0
  346. package/src/components/drawer.tsx +83 -0
  347. package/src/components/error-boundary.tsx +56 -0
  348. package/src/components/extension-combo-builder.tsx +305 -0
  349. package/src/components/extension-grouping.ts +116 -0
  350. package/src/components/extension-picker.tsx +97 -0
  351. package/src/components/external-link.tsx +40 -0
  352. package/src/components/field.tsx +169 -0
  353. package/src/components/fleet-report.tsx +136 -0
  354. package/src/components/fonts.css +22 -0
  355. package/src/components/icons.tsx +504 -0
  356. package/src/components/inputs.tsx +125 -0
  357. package/src/components/json-diff.tsx +192 -0
  358. package/src/components/json-tree.tsx +129 -0
  359. package/src/components/modal-focus.ts +150 -0
  360. package/src/components/page-header.tsx +107 -0
  361. package/src/components/primitives.tsx +489 -0
  362. package/src/components/progress-bar.tsx +100 -0
  363. package/src/components/radio-group.tsx +160 -0
  364. package/src/components/reveal-input.tsx +87 -0
  365. package/src/components/scroll-region.tsx +703 -0
  366. package/src/components/select.tsx +149 -0
  367. package/src/components/table.tsx +94 -0
  368. package/src/components/tabs.tsx +51 -0
  369. package/src/components/tags.tsx +137 -0
  370. package/src/components/tokens.css +392 -0
  371. package/src/components/tokens.ts +102 -0
  372. package/src/components/tool-picker.tsx +175 -0
  373. package/src/components/tooltip.tsx +46 -0
  374. package/src/components/version-history-panel.tsx +407 -0
  375. package/src/css.d.ts +2 -0
  376. package/src/download.ts +17 -0
  377. package/src/element-slot.ts +22 -0
  378. package/src/elicitation/ElicitationForm.tsx +87 -0
  379. package/src/errors.ts +12 -0
  380. package/src/extension-combos.ts +18 -0
  381. package/src/hooks/useApi.ts +27 -0
  382. package/src/hooks/useAuth.ts +67 -0
  383. package/src/hooks/useBreakpoint.ts +78 -0
  384. package/src/hooks/useCapabilities.ts +242 -0
  385. package/src/hooks/useSse.ts +314 -0
  386. package/src/hooks/useTheme.ts +133 -0
  387. package/src/hooks/useUnauthorized.ts +20 -0
  388. package/src/host.ts +15 -0
  389. package/src/index.ts +267 -0
  390. package/src/navigation/context.tsx +90 -0
  391. package/src/navigation/index.ts +9 -0
  392. package/src/navigation/types.ts +90 -0
  393. package/src/plugin/host-state.ts +93 -0
  394. package/src/plugin/registry.ts +174 -0
  395. package/src/plugin/types.ts +178 -0
  396. package/src/plugin/version.ts +44 -0
  397. package/src/query-keys.ts +34 -0
  398. package/src/schema-editor/SchemaEditor.tsx +186 -0
  399. package/src/schema-editor/index.ts +9 -0
  400. package/src/schema-editor/lint.ts +70 -0
  401. package/src/schema-form/SchemaForm.tsx +107 -0
  402. package/src/schema-form/array-field.tsx +110 -0
  403. package/src/schema-form/classify.ts +302 -0
  404. package/src/schema-form/context.ts +17 -0
  405. package/src/schema-form/default-value.ts +65 -0
  406. package/src/schema-form/enum-field.tsx +65 -0
  407. package/src/schema-form/field-group.tsx +71 -0
  408. package/src/schema-form/field-node.tsx +161 -0
  409. package/src/schema-form/index.ts +12 -0
  410. package/src/schema-form/media-field.tsx +281 -0
  411. package/src/schema-form/media.ts +69 -0
  412. package/src/schema-form/object-fields.tsx +74 -0
  413. package/src/schema-form/resolve.ts +114 -0
  414. package/src/schema-form/string-field.tsx +161 -0
  415. package/src/schema-form/styles.ts +20 -0
  416. package/src/schema-form/types.ts +71 -0
  417. package/src/schema-form/union-field.tsx +122 -0
  418. package/src/schema-form/union.ts +78 -0
  419. package/src/schema-form/unsupported-notice.tsx +39 -0
  420. package/src/schema-form/validate.ts +183 -0
  421. package/src/structured-output/StructuredOutput.tsx +81 -0
  422. package/src/testing.ts +235 -0
@@ -0,0 +1,192 @@
1
+ /**
2
+ * `JsonDiff` — a GENERIC, kind-agnostic structural diff of two JSON values,
3
+ * hand-rolled (no diff library). It recursively walks OBJECT keys and emits one row
4
+ * per leaf difference: a key present only on one side is `added`/`removed`, a key
5
+ * whose value differs is `changed`.
6
+ *
7
+ * ARRAYS ARE LEAVES: an unequal array (by deep compare) emits exactly ONE `changed`
8
+ * row at the array's path carrying both whole array values — there are no per-index
9
+ * rows and no LCS alignment. A container object whose descendants differ emits rows
10
+ * only for the differing leaves, never for the container itself.
11
+ *
12
+ * Rendered as a table: the dotted `path` (e.g. `fixed_kwargs.city`) as code, the
13
+ * kind as a Badge (removed → danger, added → success, changed → neutral), and the
14
+ * before/after values as a `JsonTree` for objects/arrays or escaped text for
15
+ * scalars. Every value renders as React TEXT, so payload markup is escaped and this
16
+ * is never an HTML sink.
17
+ *
18
+ * Each row also carries its `tai-diff-*` tint, but the tint only REINFORCES the
19
+ * kind — the Badge spells the kind out in words, so a row is never identified by
20
+ * color alone.
21
+ */
22
+ import { type ReactNode } from 'react';
23
+
24
+ import { Badge } from './badge';
25
+ import { JsonTree } from './json-tree';
26
+ import { Table, TBody, TD, TH, THead, TR } from './table';
27
+
28
+ /** One leaf difference between the two JSON values. */
29
+ export interface JsonDiffRow {
30
+ /** The dotted path to the differing key, e.g. `fixed_kwargs.city` (`''` = root). */
31
+ readonly path: string;
32
+ readonly kind: 'added' | 'removed' | 'changed';
33
+ /** The value on the LEFT side — set for `removed` and `changed` rows. */
34
+ readonly before?: unknown;
35
+ /** The value on the RIGHT side — set for `added` and `changed` rows. */
36
+ readonly after?: unknown;
37
+ }
38
+
39
+ function isRecord(value: unknown): value is Record<string, unknown> {
40
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
41
+ }
42
+
43
+ /** Structural deep-equality over JSON values (arrays compared position-wise). */
44
+ function deepEqual(a: unknown, b: unknown): boolean {
45
+ if (a === b) return true;
46
+ if (Array.isArray(a) && Array.isArray(b)) {
47
+ return a.length === b.length && a.every((item, index) => deepEqual(item, b[index]));
48
+ }
49
+ if (isRecord(a) && isRecord(b)) {
50
+ const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
51
+ for (const key of keys) {
52
+ if (!deepEqual(a[key], b[key])) return false;
53
+ }
54
+ return true;
55
+ }
56
+ return false;
57
+ }
58
+
59
+ function childPath(path: string, key: string): string {
60
+ return path === '' ? key : `${path}.${key}`;
61
+ }
62
+
63
+ /**
64
+ * Recursively collect the leaf differences. Only when BOTH sides are plain objects
65
+ * does the walk descend by key; anything else (arrays, scalars, a type mismatch) is
66
+ * compared as a single leaf.
67
+ */
68
+ function collect(path: string, before: unknown, after: unknown, rows: JsonDiffRow[]): void {
69
+ if (isRecord(before) && isRecord(after)) {
70
+ const keys = [...new Set([...Object.keys(before), ...Object.keys(after)])].sort((a, b) =>
71
+ a.localeCompare(b),
72
+ );
73
+ for (const key of keys) {
74
+ const next = childPath(path, key);
75
+ const inBefore = key in before;
76
+ const inAfter = key in after;
77
+ if (inBefore && !inAfter) {
78
+ rows.push({ path: next, kind: 'removed', before: before[key] });
79
+ } else if (!inBefore && inAfter) {
80
+ rows.push({ path: next, kind: 'added', after: after[key] });
81
+ } else {
82
+ collect(next, before[key], after[key], rows);
83
+ }
84
+ }
85
+ return;
86
+ }
87
+ if (!deepEqual(before, after)) {
88
+ rows.push({ path, kind: 'changed', before, after });
89
+ }
90
+ }
91
+
92
+ /** The pure structural diff — every leaf difference between `before` and `after`. */
93
+ export function diffJson(before: unknown, after: unknown): JsonDiffRow[] {
94
+ const rows: JsonDiffRow[] = [];
95
+ collect('', before, after, rows);
96
+ return rows;
97
+ }
98
+
99
+ const KIND_VARIANT = {
100
+ added: 'success',
101
+ removed: 'danger',
102
+ changed: 'neutral',
103
+ } as const;
104
+
105
+ /** The row tint for each kind — a reinforcement of the Badge, never the sole cue. */
106
+ const KIND_ROW_CLASS = {
107
+ added: 'tai-diff-added',
108
+ removed: 'tai-diff-removed',
109
+ changed: 'tai-diff-changed',
110
+ } as const;
111
+
112
+ /**
113
+ * Render one side's value: a `JsonTree` for objects/arrays, escaped text for
114
+ * scalars. The tree is a scroll region once it overflows, so it is named from
115
+ * the row's path and its side — a table of rows all announcing "JSON" tells a
116
+ * reader which pane they are in for none of them.
117
+ */
118
+ function DiffValue({
119
+ value,
120
+ label,
121
+ }: {
122
+ readonly value: unknown;
123
+ readonly label: string;
124
+ }): ReactNode {
125
+ if (Array.isArray(value) || isRecord(value)) {
126
+ return <JsonTree data={value} defaultExpanded={false} label={label} />;
127
+ }
128
+ return <code className="tai-code">{JSON.stringify(value)}</code>;
129
+ }
130
+
131
+ /** The row's path as it reads on screen; the root diff has no path of its own. */
132
+ function pathLabel(path: string): string {
133
+ return path === '' ? '(root)' : path;
134
+ }
135
+
136
+ const emptyCell = <span className="tai-muted">—</span>;
137
+
138
+ export interface JsonDiffProps {
139
+ readonly before: unknown;
140
+ readonly after: unknown;
141
+ }
142
+
143
+ export function JsonDiff({ before, after }: JsonDiffProps): ReactNode {
144
+ const rows = diffJson(before, after);
145
+
146
+ if (rows.length === 0) {
147
+ return <p className="tai-muted">No differences between the two values.</p>;
148
+ }
149
+
150
+ return (
151
+ <Table>
152
+ <THead>
153
+ <TR>
154
+ <TH>Path</TH>
155
+ <TH>Change</TH>
156
+ <TH>Before</TH>
157
+ <TH>After</TH>
158
+ </TR>
159
+ </THead>
160
+ <TBody>
161
+ {rows.map((row, index) => (
162
+ <TR
163
+ key={`${row.kind}-${row.path}-${String(index)}`}
164
+ className={KIND_ROW_CLASS[row.kind]}
165
+ data-testid={`diff-row-${row.path}`}
166
+ >
167
+ <TD className="tai-table-id" style={{ wordBreak: 'break-all' }}>
168
+ {pathLabel(row.path)}
169
+ </TD>
170
+ <TD>
171
+ <Badge variant={KIND_VARIANT[row.kind]}>{row.kind}</Badge>
172
+ </TD>
173
+ <TD>
174
+ {row.kind === 'added' ? (
175
+ emptyCell
176
+ ) : (
177
+ <DiffValue value={row.before} label={`${pathLabel(row.path)} before`} />
178
+ )}
179
+ </TD>
180
+ <TD>
181
+ {row.kind === 'removed' ? (
182
+ emptyCell
183
+ ) : (
184
+ <DiffValue value={row.after} label={`${pathLabel(row.path)} after`} />
185
+ )}
186
+ </TD>
187
+ </TR>
188
+ ))}
189
+ </TBody>
190
+ </Table>
191
+ );
192
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * `JsonTree` — a collapsible viewer for arbitrary JSON on the design system's
3
+ * terminal ground (`tai-code-block`, which also supplies the mono face, the code
4
+ * size and the line rhythm every row inherits). Objects and arrays are native
5
+ * `<details>`/`<summary>` disclosures (keyboard-accessible, no custom ARIA
6
+ * needed); primitives render inline, tinted by the `tai-syntax-*` class for their
7
+ * type so a value's kind is readable at a glance. All values render as React TEXT
8
+ * children, so a payload containing markup (e.g. `<script>`) is escaped by React
9
+ * and never interpreted — this component is never an HTML sink.
10
+ *
11
+ * The pane IS the scrolling box, so it carries the region attributes itself
12
+ * rather than sitting inside a `ScrollRegion` wrapper that would add a second
13
+ * scroller: a deeply indented value makes it a named keyboard target, a shallow
14
+ * one leaves it an ordinary block.
15
+ */
16
+ import type { CSSProperties, ReactElement } from 'react';
17
+
18
+ import { useOverflowRegion } from './scroll-region';
19
+
20
+ export interface JsonTreeProps {
21
+ readonly data: unknown;
22
+ readonly defaultExpanded?: boolean;
23
+ /** The region's accessible name, applied only while the pane actually scrolls. */
24
+ readonly label?: string;
25
+ }
26
+
27
+ /** The region's name when the caller supplies none. */
28
+ const DEFAULT_LABEL = 'JSON';
29
+
30
+ function isRecord(value: unknown): value is Record<string, unknown> {
31
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
32
+ }
33
+
34
+ function primitiveText(value: unknown): string {
35
+ if (value === null) return 'null';
36
+ switch (typeof value) {
37
+ case 'undefined':
38
+ return 'undefined';
39
+ case 'string':
40
+ return `"${value}"`;
41
+ case 'number':
42
+ case 'bigint':
43
+ case 'boolean':
44
+ return String(value);
45
+ case 'symbol':
46
+ return value.toString();
47
+ case 'function':
48
+ return '[Function]';
49
+ default:
50
+ return '[object]';
51
+ }
52
+ }
53
+
54
+ /**
55
+ * One nesting level's indent: the child block insets and draws its own guide
56
+ * rail. The depth is expressed by the DOM nesting, so each level repeats this
57
+ * single step rather than computing an absolute offset.
58
+ */
59
+ const childrenStyle: CSSProperties = {
60
+ paddingLeft: 'var(--tai-space-4)',
61
+ marginLeft: 'var(--tai-space-1)',
62
+ borderLeft: '1px solid var(--tai-color-decor)',
63
+ };
64
+
65
+ /** A `<summary>` is a click target; browsers do not style it as one by default. */
66
+ const summaryStyle: CSSProperties = { cursor: 'pointer' };
67
+
68
+ interface NodeProps {
69
+ readonly name?: string;
70
+ readonly value: unknown;
71
+ readonly defaultExpanded: boolean;
72
+ }
73
+
74
+ /** The syntax class for a primitive's type; anything outside JSON reads as muted. */
75
+ function primitiveClass(value: unknown): string {
76
+ if (typeof value === 'string') return 'tai-syntax-string';
77
+ if (typeof value === 'number' || typeof value === 'bigint') return 'tai-syntax-number';
78
+ if (typeof value === 'boolean') return 'tai-syntax-bool';
79
+ if (value === null || value === undefined) return 'tai-syntax-null';
80
+ return 'tai-muted';
81
+ }
82
+
83
+ function JsonNode({ name, value, defaultExpanded }: NodeProps): ReactElement {
84
+ if (Array.isArray(value) || isRecord(value)) {
85
+ const entries: [string, unknown][] = Array.isArray(value)
86
+ ? value.map((item, index) => [String(index), item])
87
+ : Object.entries(value);
88
+ const summary = Array.isArray(value)
89
+ ? `Array(${String(value.length)})`
90
+ : `Object(${String(entries.length)})`;
91
+ return (
92
+ <details open={defaultExpanded}>
93
+ <summary style={summaryStyle}>
94
+ {name !== undefined ? <span className="tai-syntax-key">{name}: </span> : null}
95
+ <span className="tai-muted">{summary}</span>
96
+ </summary>
97
+ <div style={childrenStyle}>
98
+ {entries.map(([childName, childValue]) => (
99
+ <JsonNode
100
+ key={childName}
101
+ name={childName}
102
+ value={childValue}
103
+ defaultExpanded={defaultExpanded}
104
+ />
105
+ ))}
106
+ </div>
107
+ </details>
108
+ );
109
+ }
110
+
111
+ return (
112
+ <div>
113
+ {name !== undefined ? <span className="tai-syntax-key">{name}: </span> : null}
114
+ <span className={primitiveClass(value)}>{primitiveText(value)}</span>
115
+ </div>
116
+ );
117
+ }
118
+
119
+ export function JsonTree({ data, defaultExpanded = true, label }: JsonTreeProps) {
120
+ // No consumer ref: the measurement's own callback ref is the only thing that
121
+ // needs the pane, and this component publishes no `ref` prop.
122
+ const region = useOverflowRegion(undefined, label ?? DEFAULT_LABEL);
123
+
124
+ return (
125
+ <div className="tai-code-block" {...region}>
126
+ <JsonNode value={data} defaultExpanded={defaultExpanded} />
127
+ </div>
128
+ );
129
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Focus return for a modal that renders no Radix `Trigger`.
3
+ *
4
+ * Radix composes its own close handler AFTER the consumer's — `composeEventHandlers`
5
+ * runs the consumer's `onCloseAutoFocus` first and skips Radix's only when that
6
+ * handler called `preventDefault`. Radix's handler is
7
+ * `event.preventDefault(); triggerRef.current?.focus()`: it returns focus to the
8
+ * Trigger and cancels the focus scope's restore in the same breath. A modal
9
+ * driven purely by `open`/`onOpenChange` — every confirm mounted for the
10
+ * duration of one action — has no Trigger for that ref to point at, so if that
11
+ * handler is allowed to run the cancel lands, the focus does not, and the reader
12
+ * is left on `<body>` with their place in the page gone (WCAG 2.4.3).
13
+ *
14
+ * So with no Trigger this stands in for one and never yields the decision back:
15
+ * it prevents default unconditionally — Radix's restore is a guaranteed no-op
16
+ * here, and a `<body>` if allowed to run — and then lands the focus itself, on
17
+ * the first of these that will take it:
18
+ *
19
+ * 1. the element focus left on the way in, when it can still hold focus;
20
+ * 2. the nearest of that element's ancestors still in the document, borrowing
21
+ * it a `tabindex="-1"` if it has none.
22
+ *
23
+ * The second case is the common one, not the exotic one: the action a confirm
24
+ * confirms is usually what removes, disables or hides the control that opened
25
+ * it. The list or table the vanished row sat in is still on screen and is where
26
+ * the reader was, so that is where they are put back.
27
+ *
28
+ * Those ancestors are recorded on the way IN because they cannot be recovered on
29
+ * the way out: detaching a node clears its own `parentElement`, so walking up
30
+ * from a removed row's button dead-ends inside the removed subtree instead of
31
+ * reaching the list that is still there. While the opener is still attached its
32
+ * live chain is used instead, so an opener that moved is followed rather than
33
+ * remembered wrongly.
34
+ *
35
+ * A borrowed `tabindex` is handed back on the element's `blur`, so the DOM keeps
36
+ * the attribute only while that element actually holds focus — and an element
37
+ * that is itself removed first takes the listener with it. Removing it any
38
+ * earlier would strip the attribute out from under the focus it exists to hold.
39
+ *
40
+ * When a Trigger IS rendered both handlers stand down and prevent nothing, so
41
+ * Radix's own restore runs untouched — there is never a second mechanism
42
+ * competing with the primitive.
43
+ */
44
+ import { useRef } from 'react';
45
+
46
+ export interface ModalFocusReturnHandlers {
47
+ /** Runs before Radix moves focus into the panel, while the opener still holds it. */
48
+ readonly onOpenAutoFocus: (event: Event) => void;
49
+ /** Runs as the panel unmounts, before the focus scope would restore. */
50
+ readonly onCloseAutoFocus: (event: Event) => void;
51
+ }
52
+
53
+ /**
54
+ * `element`'s ancestors, nearest first, stopping short of `<body>` — landing on
55
+ * `<body>` is the failure this module exists to prevent, and it is already where
56
+ * focus falls when nothing else will take it.
57
+ */
58
+ function ancestorChain(element: HTMLElement): HTMLElement[] {
59
+ const chain: HTMLElement[] = [];
60
+ const { body } = element.ownerDocument;
61
+ for (
62
+ let node = element.parentElement;
63
+ node !== null && node !== body;
64
+ node = node.parentElement
65
+ ) {
66
+ chain.push(node);
67
+ }
68
+ return chain;
69
+ }
70
+
71
+ /**
72
+ * Focuses a container that was never meant to be focusable, lending it a
73
+ * `tabindex="-1"` if it has none and taking that back when it loses focus.
74
+ *
75
+ * @returns whether the focus actually landed.
76
+ */
77
+ function focusAsFallback(element: HTMLElement): boolean {
78
+ const borrowed = !element.hasAttribute('tabindex');
79
+ if (borrowed) element.setAttribute('tabindex', '-1');
80
+ element.focus();
81
+ if (element.ownerDocument.activeElement !== element) {
82
+ if (borrowed) element.removeAttribute('tabindex');
83
+ return false;
84
+ }
85
+ if (borrowed) {
86
+ element.addEventListener(
87
+ 'blur',
88
+ () => {
89
+ element.removeAttribute('tabindex');
90
+ },
91
+ { once: true },
92
+ );
93
+ }
94
+ return true;
95
+ }
96
+
97
+ /**
98
+ * The `onOpenAutoFocus` / `onCloseAutoFocus` pair to spread onto a Radix
99
+ * `Dialog.Content`.
100
+ *
101
+ * @param hasTrigger - whether a Radix `Trigger` is rendered. When it is, both
102
+ * handlers stand down and Radix's own focus return applies.
103
+ */
104
+ export function useModalFocusReturn(hasTrigger: boolean): ModalFocusReturnHandlers {
105
+ const openerRef = useRef<HTMLElement | null>(null);
106
+ const openedUnderRef = useRef<HTMLElement[]>([]);
107
+
108
+ return {
109
+ onOpenAutoFocus: (): void => {
110
+ // Recording is only ever read by the close handler, which stands down with
111
+ // a Trigger present. Standing down here too keeps the pair symmetric and
112
+ // stops the hook holding the opener and its whole ancestor chain alive for
113
+ // a modal that will never consult them.
114
+ if (hasTrigger) return;
115
+ // The focus scope dispatches this BEFORE it focuses the panel, so the
116
+ // active element is still whatever the reader activated to open the modal,
117
+ // and its ancestors are still attached to record.
118
+ const active = document.activeElement;
119
+ const opener = active instanceof HTMLElement ? active : null;
120
+ openerRef.current = opener;
121
+ openedUnderRef.current = opener === null ? [] : ancestorChain(opener);
122
+ },
123
+ onCloseAutoFocus: (event: Event): void => {
124
+ if (hasTrigger) return;
125
+ const opener = openerRef.current;
126
+ const openedUnder = openedUnderRef.current;
127
+ openerRef.current = null;
128
+ openedUnderRef.current = [];
129
+
130
+ // Radix's handler runs next unless this one prevents default. Its
131
+ // `triggerRef` is null by definition here, and it cancels the scope's
132
+ // restore before finding that out, so letting it run is a guaranteed
133
+ // `<body>`. Take the decision away from it in every case, including the
134
+ // one where there is nothing to return focus to.
135
+ event.preventDefault();
136
+ if (opener === null) return;
137
+
138
+ opener.focus();
139
+ if (opener.ownerDocument.activeElement === opener) return;
140
+
141
+ // The opener would not take focus — it was removed, disabled or hidden by
142
+ // the action this modal confirmed. Fall back to the nearest ancestor that
143
+ // is still in the document, so the reader keeps their place in the page.
144
+ const chain = opener.isConnected ? ancestorChain(opener) : openedUnder;
145
+ for (const ancestor of chain) {
146
+ if (ancestor.isConnected && focusAsFallback(ancestor)) return;
147
+ }
148
+ },
149
+ };
150
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * `PageHeader`, `Page`, `Stack` — the screen scaffolding every feature was
3
+ * hand-rolling as a flex column plus an ad-hoc heading block.
4
+ *
5
+ * `PageHeader` owns a DOM contract `page-header.test.tsx` asserts: exactly one
6
+ * `<h1>` whose accessible name is the title VERBATIM. The nav-section eyebrow a
7
+ * screen shows above its title is therefore a SIBLING of the `<h1>`, never a
8
+ * child — nesting it would fold it into the computed name and break every
9
+ * heading assertion. The `<h1>` is also permanently focusable
10
+ * (`tabIndex={-1}`), which is what makes it addressable as a focus target
11
+ * through {@link PageHeaderProps.titleRef} or `id`.
12
+ */
13
+ import type { CSSProperties, ReactElement, ReactNode, Ref } from 'react';
14
+
15
+ /** The wrapper props both layout primitives share, ref included. */
16
+ interface LayoutElementProps {
17
+ readonly className?: string;
18
+ readonly style?: CSSProperties;
19
+ /** A consumer ref for the wrapper element itself. */
20
+ readonly ref?: Ref<HTMLDivElement>;
21
+ }
22
+
23
+ export interface PageHeaderProps {
24
+ /** The screen's title. It is the `<h1>`'s accessible name, VERBATIM. */
25
+ readonly title: string;
26
+ /** The screen's existing nav-section label, rendered ABOVE the h1 — never inside it. */
27
+ readonly eyebrow?: string;
28
+ readonly description?: string;
29
+ readonly actions?: ReactNode;
30
+ /**
31
+ * A consumer ref for the `<h1>`. Published so a caller that wants to move
32
+ * focus to the new screen's title — after a route change, say — has the
33
+ * element without querying for it; the heading is focusable for that reason.
34
+ */
35
+ readonly titleRef?: Ref<HTMLHeadingElement>;
36
+ /**
37
+ * An id for the `<h1>`, not for the `<header>` root. The heading is the element
38
+ * a caller has a reason to address — an `aria-labelledby` pointing at it names
39
+ * the region from the screen's own title — and it is the other way to reach it
40
+ * as a focus target.
41
+ */
42
+ readonly id?: string;
43
+ }
44
+
45
+ export function PageHeader({
46
+ title,
47
+ eyebrow,
48
+ description,
49
+ actions,
50
+ titleRef,
51
+ id,
52
+ }: PageHeaderProps): ReactElement {
53
+ return (
54
+ <header className="tai-page-header">
55
+ <div>
56
+ {eyebrow === undefined ? null : <div className="tai-label">{eyebrow}</div>}
57
+ <h1 className="tai-page-title" tabIndex={-1} ref={titleRef} id={id}>
58
+ {title}
59
+ </h1>
60
+ {description === undefined ? null : <p className="tai-page-description">{description}</p>}
61
+ </div>
62
+ {actions === undefined ? null : <div className="tai-page-actions">{actions}</div>}
63
+ </header>
64
+ );
65
+ }
66
+
67
+ export interface PageLayoutProps extends LayoutElementProps {
68
+ readonly children: ReactNode;
69
+ }
70
+
71
+ /** The screen wrapper: the max-width, the gutters, and the vertical rhythm. */
72
+ export function Page({ children, className, style, ref }: PageLayoutProps): ReactElement {
73
+ return (
74
+ <div
75
+ className={className === undefined ? 'tai-page' : `tai-page ${className}`}
76
+ style={style}
77
+ ref={ref}
78
+ >
79
+ {children}
80
+ </div>
81
+ );
82
+ }
83
+
84
+ export interface StackProps extends LayoutElementProps {
85
+ readonly children: ReactNode;
86
+ /** The gap step; 4 is the default and needs no modifier class. */
87
+ readonly gap?: 2 | 3 | 4 | 6;
88
+ }
89
+
90
+ /** The modifier class per non-default gap step; gap 4 is `.tai-stack`'s own gap. */
91
+ const STACK_GAP_CLASS: Record<2 | 3 | 6, string> = {
92
+ 2: 'tai-stack-2',
93
+ 3: 'tai-stack-3',
94
+ 6: 'tai-stack-6',
95
+ };
96
+
97
+ /** A vertical flex column on the spacing scale. */
98
+ export function Stack({ children, gap = 4, className, style, ref }: StackProps): ReactElement {
99
+ const classes = ['tai-stack'];
100
+ if (gap !== 4) classes.push(STACK_GAP_CLASS[gap]);
101
+ if (className !== undefined) classes.push(className);
102
+ return (
103
+ <div className={classes.join(' ')} style={style} ref={ref}>
104
+ {children}
105
+ </div>
106
+ );
107
+ }