@speakeasy-api/moonshine 2.0.0-alpha.1 → 2.0.0-alpha.2

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 (330) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +49 -23
  3. package/package.json +33 -50
  4. package/scripts/generate-utility-docs.js +324 -0
  5. package/src/assets/icons/external/github.svg +3 -0
  6. package/src/assets/icons/external/maven.svg +152 -0
  7. package/src/assets/icons/external/npm.svg +4 -0
  8. package/src/assets/icons/external/nuget.svg +5 -0
  9. package/src/assets/icons/external/packagist.svg +1 -0
  10. package/src/assets/icons/external/pypi.svg +182 -0
  11. package/src/assets/icons/external/rubygems.svg +14 -0
  12. package/src/assets/icons/external/terraform.svg +1 -0
  13. package/src/assets/icons/languages/csharp.svg +1 -0
  14. package/src/assets/icons/languages/go.svg +1 -0
  15. package/src/assets/icons/languages/java.svg +1 -0
  16. package/src/assets/icons/languages/json.svg +2 -0
  17. package/src/assets/icons/languages/php.svg +1 -0
  18. package/src/assets/icons/languages/postman.svg +3 -0
  19. package/src/assets/icons/languages/python.svg +1 -0
  20. package/src/assets/icons/languages/ruby.svg +1 -0
  21. package/src/assets/icons/languages/swift.svg +1 -0
  22. package/src/assets/icons/languages/terraform.svg +1 -0
  23. package/src/assets/icons/languages/typescript.svg +1 -0
  24. package/src/assets/icons/languages/unity.svg +1 -0
  25. package/src/base.css +12 -12
  26. package/src/components/AIChat/AIChatContainer.tsx +71 -0
  27. package/src/components/AIChat/AIChatMessage.tsx +135 -0
  28. package/src/components/AIChat/AIChatMessageComposer.tsx +175 -0
  29. package/src/components/AIChat/AIChatMessageList.tsx +34 -0
  30. package/src/components/AIChat/AIChatModelSelector.tsx +159 -0
  31. package/src/components/AIChat/componentsTypes.ts +36 -0
  32. package/src/components/AIChat/context.ts +15 -0
  33. package/src/components/AIChat/index.ts +12 -0
  34. package/src/components/AIChat/parts/AIChatMessageFilePart.tsx +129 -0
  35. package/src/components/AIChat/parts/AIChatMessageReasoningPart.tsx +23 -0
  36. package/src/components/AIChat/parts/AIChatMessageSourcePart.tsx +58 -0
  37. package/src/components/AIChat/parts/AIChatMessageTextPart.tsx +33 -0
  38. package/src/components/AIChat/parts/AIChatMessageToolInvocationPart.tsx +53 -0
  39. package/src/components/AIChat/parts/AIChatMessageToolPart.tsx +395 -0
  40. package/src/components/AIChat/parts/AIChatMessageToolResultPart.tsx +46 -0
  41. package/src/components/AIChat/toolCallApproval.ts +61 -0
  42. package/src/components/AIChat/types.ts +97 -0
  43. package/src/components/ActionBar/index.tsx +184 -0
  44. package/src/components/Alert/index.tsx +118 -0
  45. package/src/components/Alert/types.ts +12 -0
  46. package/src/components/AppLayout/context.tsx +31 -0
  47. package/src/components/AppLayout/index.tsx +550 -0
  48. package/src/components/AppLayout/provider.tsx +40 -0
  49. package/src/components/AppLayout/useAppLayoutKeys.ts +26 -0
  50. package/src/components/Badge/index.tsx +227 -0
  51. package/src/components/Button/index.tsx +531 -0
  52. package/src/components/Card/index.tsx +193 -0
  53. package/src/components/CodeEditorLayout/index.tsx +394 -0
  54. package/src/components/CodeEditorLayout/styles.module.css +8 -0
  55. package/src/components/CodeHighlight/Pre.tsx +63 -0
  56. package/src/components/CodePlayground/index.tsx +411 -0
  57. package/src/components/CodeSnippet/codeSnippet.css +97 -0
  58. package/src/components/CodeSnippet/index.tsx +224 -0
  59. package/src/components/Combobox/index.tsx +193 -0
  60. package/src/components/Command/index.tsx +152 -0
  61. package/src/components/Container/index.tsx +31 -0
  62. package/src/components/ContextDropdown/index.tsx +150 -0
  63. package/src/components/Dialog/index.tsx +123 -0
  64. package/src/components/DragNDrop/DragNDropArea.tsx +30 -0
  65. package/src/components/DragNDrop/DragOverlay.tsx +4 -0
  66. package/src/components/DragNDrop/Draggable.tsx +97 -0
  67. package/src/components/DragNDrop/Droppable.tsx +51 -0
  68. package/src/components/Dropdown/index.tsx +201 -0
  69. package/src/components/ExternalPill/index.tsx +58 -0
  70. package/src/components/Facepile/index.tsx +309 -0
  71. package/src/components/GradientCircle/gradientCircle.css +34 -0
  72. package/src/components/GradientCircle/index.tsx +143 -0
  73. package/src/components/Grid/index.tsx +150 -0
  74. package/src/components/Heading/index.tsx +54 -0
  75. package/src/components/HighlightedText/index.tsx +152 -0
  76. package/src/components/Icon/customIcons/createCustomLucideIcon.ts +25 -0
  77. package/src/components/Icon/customIcons/gems.ts +26 -0
  78. package/{dist/go-CiWl_aXI.mjs → src/components/Icon/customIcons/go.ts} +21 -19
  79. package/src/components/Icon/customIcons/index.ts +11 -0
  80. package/{dist/maven-DhmnGXoB.mjs → src/components/Icon/customIcons/maven.ts} +17 -15
  81. package/src/components/Icon/customIcons/npm.ts +19 -0
  82. package/{dist/nuget-5a2icRS2.mjs → src/components/Icon/customIcons/nuget.ts} +17 -15
  83. package/src/components/Icon/customIcons/packagist.ts +124 -0
  84. package/{dist/pypi-DsuRYjdK.mjs → src/components/Icon/customIcons/pypi.ts} +16 -14
  85. package/src/components/Icon/index.tsx +83 -0
  86. package/src/components/Icon/isIconName.ts +10 -0
  87. package/src/components/Icon/names.ts +14 -0
  88. package/src/components/IconButton/index.tsx +51 -0
  89. package/src/components/Input/index.tsx +98 -0
  90. package/src/components/KeyHint/index.tsx +118 -0
  91. package/src/components/LanguageIndicator/index.tsx +68 -0
  92. package/src/components/Link/index.tsx +153 -0
  93. package/src/components/LoggedInUserMenu/index.tsx +116 -0
  94. package/src/components/Logo/Animated.tsx +191 -0
  95. package/src/components/Logo/index.tsx +17 -0
  96. package/src/components/Logo/speakeasy-logo.riv +0 -0
  97. package/src/components/Logo/svgs/index.tsx +126 -0
  98. package/src/components/Modal/index.tsx +104 -0
  99. package/src/components/PageHeader/index.tsx +227 -0
  100. package/src/components/PageHeader/styles.module.css +27 -0
  101. package/src/components/Popover/index.tsx +35 -0
  102. package/src/components/PromptInput/index.tsx +372 -0
  103. package/src/components/PullRequestLink/index.tsx +64 -0
  104. package/src/components/ResizablePanel/index.tsx +119 -0
  105. package/src/components/Score/index.module.css +32 -0
  106. package/src/components/Score/index.tsx +268 -0
  107. package/src/components/ScrollArea/index.tsx +48 -0
  108. package/src/components/SegmentedButton/index.module.css +19 -0
  109. package/src/components/SegmentedButton/index.tsx +101 -0
  110. package/src/components/Select/index.tsx +159 -0
  111. package/src/components/Separator/index.tsx +23 -0
  112. package/src/components/Skeleton/index.tsx +61 -0
  113. package/src/components/Skeleton/skeleton.css +52 -0
  114. package/src/components/Stack/index.tsx +137 -0
  115. package/src/components/Subnav/index.tsx +315 -0
  116. package/src/components/Switch/index.tsx +29 -0
  117. package/src/components/Table/context/context.tsx +19 -0
  118. package/src/components/Table/context/tableProvider.tsx +39 -0
  119. package/src/components/Table/index.tsx +707 -0
  120. package/src/components/Table/styles.module.css +25 -0
  121. package/src/components/Tabs/index.tsx +87 -0
  122. package/src/components/TargetLanguageIcon/index.tsx +84 -0
  123. package/src/components/Text/index.tsx +59 -0
  124. package/src/components/ThemeSwitcher/index.tsx +118 -0
  125. package/src/components/Timeline/index.tsx +290 -0
  126. package/src/components/Tooltip/index.tsx +41 -0
  127. package/src/components/UserAvatar/index.tsx +87 -0
  128. package/src/components/UserAvatar/sizeMap.ts +12 -0
  129. package/src/components/Wizard/index.tsx +208 -0
  130. package/src/components/Wizard/types.ts +17 -0
  131. package/src/components/WorkspaceSelector/CreateOrg.tsx +95 -0
  132. package/src/components/WorkspaceSelector/CreateWorkspace.tsx +196 -0
  133. package/src/components/WorkspaceSelector/OrgList.tsx +115 -0
  134. package/src/components/WorkspaceSelector/OrgSelector.tsx +207 -0
  135. package/src/components/WorkspaceSelector/RecentWorkspaces.tsx +83 -0
  136. package/src/components/WorkspaceSelector/ScrollingList.tsx +84 -0
  137. package/src/components/WorkspaceSelector/SearchBox.tsx +40 -0
  138. package/src/components/WorkspaceSelector/WorkspaceItem.tsx +37 -0
  139. package/src/components/WorkspaceSelector/WorkspaceList.tsx +107 -0
  140. package/src/components/WorkspaceSelector/index.tsx +400 -0
  141. package/src/components/WorkspaceSelector/styles.css +74 -0
  142. package/src/components/__beta__/CLIWizard/index.tsx +357 -0
  143. package/src/components/__beta__/CLIWizard/terminal-command.tsx +108 -0
  144. package/src/components/__beta__/CLIWizard/terminal.tsx +83 -0
  145. package/src/components/__beta__/README.md +3 -0
  146. package/src/components/index.mdx +38 -0
  147. package/src/context/ConfigContext.tsx +43 -0
  148. package/src/context/ModalContext.tsx +118 -0
  149. package/src/context/theme.ts +1 -0
  150. package/src/hooks/useAppLayout.ts +10 -0
  151. package/src/hooks/useConfig.ts +10 -0
  152. package/src/hooks/useIsMounted.ts +13 -0
  153. package/src/hooks/useModal.tsx +10 -0
  154. package/src/hooks/useTailwindBreakpoint.ts +47 -0
  155. package/src/hooks/useTheme.ts +13 -0
  156. package/src/index.ts +234 -0
  157. package/src/lib/assert.ts +9 -0
  158. package/src/lib/codeUtils.ts +177 -0
  159. package/src/lib/debounce.ts +9 -0
  160. package/src/lib/responsiveMappers.ts +69 -0
  161. package/src/lib/responsiveUtils.ts +23 -0
  162. package/src/lib/storybookUtils.tsx +26 -0
  163. package/src/lib/typeUtils.ts +109 -0
  164. package/src/lib/utils.ts +85 -0
  165. package/src/styles/codeSyntax.css +59 -0
  166. package/src/styles/globals.css +51 -0
  167. package/src/types.ts +200 -0
  168. package/src/utilities.css +347 -6
  169. package/src/vite-env.d.ts +6 -0
  170. package/types/utilities.d.ts +43 -1
  171. package/dist/components/AIChat/AIChatContainer.d.ts +0 -25
  172. package/dist/components/AIChat/AIChatMessage.d.ts +0 -19
  173. package/dist/components/AIChat/AIChatMessageComposer.d.ts +0 -22
  174. package/dist/components/AIChat/AIChatMessageList.d.ts +0 -6
  175. package/dist/components/AIChat/AIChatModelSelector.d.ts +0 -14
  176. package/dist/components/AIChat/componentsTypes.d.ts +0 -11
  177. package/dist/components/AIChat/context.d.ts +0 -3
  178. package/dist/components/AIChat/index.d.ts +0 -12
  179. package/dist/components/AIChat/parts/AIChatMessageFilePart.d.ts +0 -7
  180. package/dist/components/AIChat/parts/AIChatMessageReasoningPart.d.ts +0 -5
  181. package/dist/components/AIChat/parts/AIChatMessageSourcePart.d.ts +0 -9
  182. package/dist/components/AIChat/parts/AIChatMessageTextPart.d.ts +0 -5
  183. package/dist/components/AIChat/parts/AIChatMessageToolInvocationPart.d.ts +0 -6
  184. package/dist/components/AIChat/parts/AIChatMessageToolPart.d.ts +0 -33
  185. package/dist/components/AIChat/parts/AIChatMessageToolResultPart.d.ts +0 -5
  186. package/dist/components/AIChat/toolCallApproval.d.ts +0 -15
  187. package/dist/components/AIChat/types.d.ts +0 -78
  188. package/dist/components/ActionBar/index.d.ts +0 -36
  189. package/dist/components/Alert/index.d.ts +0 -18
  190. package/dist/components/Alert/types.d.ts +0 -4
  191. package/dist/components/Badge/index.d.ts +0 -10
  192. package/dist/components/Button/index.d.ts +0 -11
  193. package/dist/components/Card/index.d.ts +0 -47
  194. package/dist/components/CodeEditorLayout/index.d.ts +0 -101
  195. package/dist/components/CodePlayground/index.d.ts +0 -108
  196. package/dist/components/CodePlayground/lineNumbers.d.ts +0 -2
  197. package/dist/components/CodePlayground/tokenTransitions.d.ts +0 -2
  198. package/dist/components/CodePlayground/wordWrap.d.ts +0 -2
  199. package/dist/components/CodeSnippet/index.d.ts +0 -50
  200. package/dist/components/Combobox/index.d.ts +0 -35
  201. package/dist/components/Command/index.d.ts +0 -80
  202. package/dist/components/Container/index.d.ts +0 -9
  203. package/dist/components/ContextDropdown/index.d.ts +0 -7
  204. package/dist/components/ContextDropdown/provider.d.ts +0 -22
  205. package/dist/components/ContextDropdown/useModal.d.ts +0 -11
  206. package/dist/components/Dialog/index.d.ts +0 -19
  207. package/dist/components/DragNDrop/DragNDropArea.d.ts +0 -8
  208. package/dist/components/DragNDrop/DragOverlay.d.ts +0 -1
  209. package/dist/components/DragNDrop/Draggable.d.ts +0 -29
  210. package/dist/components/DragNDrop/Droppable.d.ts +0 -28
  211. package/dist/components/Dropdown/index.d.ts +0 -27
  212. package/dist/components/ExternalPill/index.d.ts +0 -12
  213. package/dist/components/Facepile/index.d.ts +0 -16
  214. package/dist/components/GradientCircle/index.d.ts +0 -10
  215. package/dist/components/Grid/index.d.ts +0 -80
  216. package/dist/components/Heading/index.d.ts +0 -12
  217. package/dist/components/HighlightedText/index.d.ts +0 -19
  218. package/dist/components/Icon/customIcons/createCustomLucideIcon.d.ts +0 -3
  219. package/dist/components/Icon/customIcons/gems.d.ts +0 -2
  220. package/dist/components/Icon/customIcons/go.d.ts +0 -2
  221. package/dist/components/Icon/customIcons/index.d.ts +0 -10
  222. package/dist/components/Icon/customIcons/maven.d.ts +0 -2
  223. package/dist/components/Icon/customIcons/npm.d.ts +0 -2
  224. package/dist/components/Icon/customIcons/nuget.d.ts +0 -2
  225. package/dist/components/Icon/customIcons/packagist.d.ts +0 -2
  226. package/dist/components/Icon/customIcons/pypi.d.ts +0 -2
  227. package/dist/components/Icon/index.d.ts +0 -10
  228. package/dist/components/Icon/isIconName.d.ts +0 -2
  229. package/dist/components/Icon/names.d.ts +0 -6
  230. package/dist/components/Input/index.d.ts +0 -8
  231. package/dist/components/KeyHint/index.d.ts +0 -16
  232. package/dist/components/LanguageIndicator/index.d.ts +0 -7
  233. package/dist/components/Link/index.d.ts +0 -19
  234. package/dist/components/LoggedInUserMenu/index.d.ts +0 -17
  235. package/dist/components/Logo/Animated.d.ts +0 -7
  236. package/dist/components/Logo/index.d.ts +0 -7
  237. package/dist/components/Logo/svgs/index.d.ts +0 -6
  238. package/dist/components/Navbar/Slim.d.ts +0 -33
  239. package/dist/components/Navbar/index.d.ts +0 -15
  240. package/dist/components/PageHeader/index.d.ts +0 -45
  241. package/dist/components/Popover/index.d.ts +0 -8
  242. package/dist/components/PromptInput/index.d.ts +0 -55
  243. package/dist/components/PullRequestLink/index.d.ts +0 -10
  244. package/dist/components/ResizablePanel/index.d.ts +0 -26
  245. package/dist/components/Score/index.d.ts +0 -37
  246. package/dist/components/ScrollArea/index.d.ts +0 -5
  247. package/dist/components/Select/index.d.ts +0 -13
  248. package/dist/components/Separator/index.d.ts +0 -6
  249. package/dist/components/Skeleton/index.d.ts +0 -27
  250. package/dist/components/Stack/index.d.ts +0 -33
  251. package/dist/components/Subnav/index.d.ts +0 -12
  252. package/dist/components/Switch/index.d.ts +0 -4
  253. package/dist/components/Table/context/context.d.ts +0 -8
  254. package/dist/components/Table/context/tableProvider.d.ts +0 -6
  255. package/dist/components/Table/index.d.ts +0 -94
  256. package/dist/components/Tabs/index.d.ts +0 -21
  257. package/dist/components/TargetLanguageIcon/index.d.ts +0 -7
  258. package/dist/components/Text/index.d.ts +0 -19
  259. package/dist/components/ThemeSwitcher/index.d.ts +0 -5
  260. package/dist/components/Tooltip/index.d.ts +0 -8
  261. package/dist/components/UserAvatar/index.d.ts +0 -9
  262. package/dist/components/UserAvatar/sizeMap.d.ts +0 -3
  263. package/dist/components/Wizard/index.d.ts +0 -19
  264. package/dist/components/Wizard/types.d.ts +0 -15
  265. package/dist/components/WorkspaceSelector/CreateOrg.d.ts +0 -6
  266. package/dist/components/WorkspaceSelector/CreateWorkspace.d.ts +0 -17
  267. package/dist/components/WorkspaceSelector/OrgList.d.ts +0 -11
  268. package/dist/components/WorkspaceSelector/OrgSelector.d.ts +0 -13
  269. package/dist/components/WorkspaceSelector/RecentWorkspaces.d.ts +0 -11
  270. package/dist/components/WorkspaceSelector/ScrollingList.d.ts +0 -21
  271. package/dist/components/WorkspaceSelector/SearchBox.d.ts +0 -9
  272. package/dist/components/WorkspaceSelector/WorkspaceItem.d.ts +0 -9
  273. package/dist/components/WorkspaceSelector/WorkspaceList.d.ts +0 -10
  274. package/dist/components/WorkspaceSelector/index.d.ts +0 -34
  275. package/dist/components/__beta__/CLIWizard/index.d.ts +0 -21
  276. package/dist/components/__beta__/CLIWizard/terminal-command.d.ts +0 -19
  277. package/dist/components/__beta__/CLIWizard/terminal.d.ts +0 -26
  278. package/dist/context/ConfigContext.d.ts +0 -18
  279. package/dist/context/theme.d.ts +0 -1
  280. package/dist/createCustomLucideIcon-YlrRX5h9.mjs +0 -19
  281. package/dist/createCustomLucideIcon-YlrRX5h9.mjs.map +0 -1
  282. package/dist/gems-BcsO9cXq.mjs +0 -24
  283. package/dist/gems-BcsO9cXq.mjs.map +0 -1
  284. package/dist/github-kgjMtfE7.mjs +0 -11
  285. package/dist/github-kgjMtfE7.mjs.map +0 -1
  286. package/dist/go-CiWl_aXI.mjs.map +0 -1
  287. package/dist/hooks/useConfig.d.ts +0 -2
  288. package/dist/hooks/useIsMounted.d.ts +0 -1
  289. package/dist/hooks/useTailwindBreakpoint.d.ts +0 -3
  290. package/dist/hooks/useTheme.d.ts +0 -6
  291. package/dist/index-COXZ9O-g.mjs +0 -50882
  292. package/dist/index-COXZ9O-g.mjs.map +0 -1
  293. package/dist/index.d.ts +0 -73
  294. package/dist/lib/assert.d.ts +0 -2
  295. package/dist/lib/codeUtils.d.ts +0 -35
  296. package/dist/lib/debounce.d.ts +0 -1
  297. package/dist/lib/responsiveMappers.d.ts +0 -10
  298. package/dist/lib/responsiveUtils.d.ts +0 -3
  299. package/dist/lib/storybookUtils.d.ts +0 -5
  300. package/dist/lib/typeUtils.d.ts +0 -24
  301. package/dist/lib/utils.d.ts +0 -23
  302. package/dist/lucide-icons-BDw0imyx.mjs +0 -28054
  303. package/dist/lucide-icons-BDw0imyx.mjs.map +0 -1
  304. package/dist/maven-DhmnGXoB.mjs.map +0 -1
  305. package/dist/maven-W_nkSDNW.mjs +0 -107
  306. package/dist/maven-W_nkSDNW.mjs.map +0 -1
  307. package/dist/moonshine.es.js +0 -114
  308. package/dist/moonshine.es.js.map +0 -1
  309. package/dist/npm-BWTcVvFH.mjs +0 -11
  310. package/dist/npm-BWTcVvFH.mjs.map +0 -1
  311. package/dist/npm-CvQ4GKW4.mjs +0 -17
  312. package/dist/npm-CvQ4GKW4.mjs.map +0 -1
  313. package/dist/nuget-5a2icRS2.mjs.map +0 -1
  314. package/dist/nuget-CV5HU1JR.mjs +0 -11
  315. package/dist/nuget-CV5HU1JR.mjs.map +0 -1
  316. package/dist/packagist-CET6q9hi.mjs +0 -118
  317. package/dist/packagist-CET6q9hi.mjs.map +0 -1
  318. package/dist/packagist-D01fn9N_.mjs +0 -11
  319. package/dist/packagist-D01fn9N_.mjs.map +0 -1
  320. package/dist/pypi-DLh6kIJe.mjs +0 -11
  321. package/dist/pypi-DLh6kIJe.mjs.map +0 -1
  322. package/dist/pypi-DsuRYjdK.mjs.map +0 -1
  323. package/dist/rubygems-DeiNjcDV.mjs +0 -11
  324. package/dist/rubygems-DeiNjcDV.mjs.map +0 -1
  325. package/dist/speakeasy-logo-ByBTXLWb.mjs +0 -5
  326. package/dist/speakeasy-logo-ByBTXLWb.mjs.map +0 -1
  327. package/dist/style.css +0 -1
  328. package/dist/terraform-C4aktQ0o.mjs +0 -11
  329. package/dist/terraform-C4aktQ0o.mjs.map +0 -1
  330. package/dist/types.d.ts +0 -80
@@ -0,0 +1,707 @@
1
+ // TODO: https://linear.app/speakeasy/issue/SXF-170/table-component
2
+ import React, {
3
+ forwardRef,
4
+ PropsWithChildren,
5
+ type ReactNode,
6
+ useCallback,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ } from 'react'
11
+ import { cn } from '../../lib/utils'
12
+ import { Loader2 } from 'lucide-react'
13
+ import { isGroupOf } from '../../lib/typeUtils'
14
+ import styles from './styles.module.css'
15
+ import {
16
+ Tooltip,
17
+ TooltipContent,
18
+ TooltipProvider,
19
+ TooltipTrigger,
20
+ } from '../Tooltip'
21
+ import { Button } from '../Button'
22
+ import { ExpandChevron } from '../__beta__/CLIWizard'
23
+ import { TableProvider } from './context/tableProvider'
24
+ import { useTable } from './context/context'
25
+
26
+ export type Column<T extends object> = {
27
+ key: keyof T | string
28
+ header: ReactNode
29
+ render?: (row: T) => ReactNode
30
+ width?: `${number}fr` | `${number}px` | 'auto' | undefined
31
+ }
32
+
33
+ export type Group<T extends object> = {
34
+ key: string
35
+ items: T[]
36
+ [k: string]: unknown
37
+ }
38
+
39
+ type CellPadding = 'normal' | 'condensed' | 'spacious'
40
+
41
+ type PropsWithChildrenAndClassName = PropsWithChildren<{ className?: string }>
42
+
43
+ export type TableProps<T extends object> = {
44
+ columns: Column<T>[]
45
+ data: T[] | Group<T>[]
46
+ rowKey: (row: T) => string | number
47
+ onRowClick?: (row: T) => void
48
+ renderGroupHeader?: (group: Group<T>) => ReactNode
49
+ renderExpandedContent?: (row: T) => ReactNode
50
+ onLoadMore?: () => Promise<void> | (() => void)
51
+ hasMore?: boolean
52
+ noResultsMessage?: ReactNode
53
+ className?: string
54
+ cellPadding?: CellPadding
55
+ hideHeader?: boolean
56
+ }
57
+
58
+ export type TableWrapperProps<T extends object> =
59
+ PropsWithChildrenAndClassName & {
60
+ columns: Column<T>[]
61
+ cellPadding?: CellPadding
62
+ }
63
+
64
+ function expandColumn<T extends object>(): Column<T> {
65
+ return {
66
+ key: 'expand',
67
+ header: '',
68
+ width: `64px`, // 32px is padding, 32px is the width of the expand button
69
+ }
70
+ }
71
+
72
+ type TableContainerProps = PropsWithChildrenAndClassName & {
73
+ tableDepth: number
74
+ colWidths: string
75
+ cellPadding?: CellPadding
76
+ expandedRowKeys?: Set<string | number>
77
+ }
78
+
79
+ const TableContainer = forwardRef<HTMLTableElement, TableContainerProps>(
80
+ (
81
+ {
82
+ children,
83
+ className,
84
+ tableDepth,
85
+ colWidths,
86
+ cellPadding,
87
+ expandedRowKeys,
88
+ },
89
+ ref
90
+ ) => {
91
+ return (
92
+ <TableProvider depth={tableDepth} expandedRowKeys={expandedRowKeys}>
93
+ <table
94
+ style={
95
+ { '--grid-template-columns': colWidths } as React.CSSProperties
96
+ }
97
+ ref={ref}
98
+ className={cn(
99
+ styles.table,
100
+ 'relative grid w-full caption-bottom [border-collapse:separate] [border-spacing:0] [grid-template-columns:var(--grid-template-columns)] overflow-x-auto overflow-y-hidden rounded-lg border text-sm',
101
+ tableDepth > 1 && 'rounded-none border-none',
102
+ className
103
+ )}
104
+ data-cell-padding={cellPadding}
105
+ >
106
+ {children}
107
+ </table>
108
+ </TableProvider>
109
+ )
110
+ }
111
+ )
112
+
113
+ function TableRoot<T extends object>(
114
+ props: TableProps<T> | TableWrapperProps<T>
115
+ ) {
116
+ const { depth } = useTable()
117
+ const tableDepth = depth + 1
118
+
119
+ const tableBodyRef = useRef<HTMLTableSectionElement | null>(null)
120
+ const tableRef = useRef<HTMLTableElement | null>(null)
121
+
122
+ let columns = props.columns
123
+
124
+ // We add the expand column here so that all parts of the table know about it, particularly needed for widths
125
+ if (
126
+ !propsHasChildren<TableWrapperProps<T>, TableProps<T>>(props) &&
127
+ props.renderExpandedContent
128
+ ) {
129
+ columns = [expandColumn(), ...columns]
130
+ }
131
+
132
+ const colWidths = useMemo(
133
+ () => columns.map((column) => column.width ?? '1fr').join(' '),
134
+ [columns]
135
+ )
136
+
137
+ if (propsHasChildren<TableWrapperProps<T>, TableProps<T>>(props)) {
138
+ return (
139
+ <TableContainer
140
+ className={props.className}
141
+ colWidths={colWidths}
142
+ tableDepth={tableDepth}
143
+ cellPadding={props.cellPadding}
144
+ ref={tableRef}
145
+ >
146
+ {props.children}
147
+ </TableContainer>
148
+ )
149
+ }
150
+
151
+ const {
152
+ data,
153
+ rowKey,
154
+ onRowClick,
155
+ onLoadMore,
156
+ hasMore,
157
+ noResultsMessage,
158
+ renderGroupHeader,
159
+ renderExpandedContent,
160
+ className,
161
+ hideHeader,
162
+ cellPadding,
163
+ } = props
164
+
165
+ const [isLoading, setIsLoading] = useState(false)
166
+ const handleLoadMore = async () => {
167
+ setIsLoading(true)
168
+ await onLoadMore?.()
169
+ setIsLoading(false)
170
+ }
171
+
172
+ const expandedRowKeys = useMemo(() => {
173
+ if (!isGroupOf<T>(data)) {
174
+ return new Set(
175
+ data
176
+ .filter((row) => 'defaultExpanded' in row && row.defaultExpanded)
177
+ .map((row) => rowKey(row as T))
178
+ )
179
+ }
180
+ }, [data, rowKey])
181
+
182
+ return (
183
+ <TableContainer
184
+ className={className}
185
+ colWidths={colWidths}
186
+ tableDepth={tableDepth}
187
+ cellPadding={cellPadding}
188
+ ref={tableRef}
189
+ expandedRowKeys={expandedRowKeys}
190
+ >
191
+ {!hideHeader && <Table.Header columns={columns} />}
192
+ <Table.Body
193
+ data={data}
194
+ ref={tableBodyRef}
195
+ columns={columns}
196
+ rowKey={rowKey}
197
+ hasMore={hasMore}
198
+ noResultsMessage={noResultsMessage}
199
+ renderGroupHeader={renderGroupHeader}
200
+ renderExpandedContent={renderExpandedContent}
201
+ handleLoadMore={handleLoadMore}
202
+ isLoading={isLoading}
203
+ onRowClick={onRowClick}
204
+ />
205
+ </TableContainer>
206
+ )
207
+ }
208
+
209
+ type HeaderProps<T extends object> = {
210
+ columns: Column<T>[]
211
+ className?: string
212
+ }
213
+
214
+ function HeaderContainer({
215
+ className,
216
+ children,
217
+ }: PropsWithChildrenAndClassName) {
218
+ return (
219
+ <thead
220
+ className={cn(
221
+ '[grid-column:1/-1] grid [grid-template-columns:subgrid]',
222
+ className
223
+ )}
224
+ >
225
+ {children}
226
+ </thead>
227
+ )
228
+ }
229
+
230
+ function Header<T extends object>(
231
+ props: HeaderProps<T> | PropsWithChildrenAndClassName
232
+ ) {
233
+ if (propsHasChildren<PropsWithChildrenAndClassName, HeaderProps<T>>(props)) {
234
+ return (
235
+ <HeaderContainer className={props.className}>
236
+ {props.children}
237
+ </HeaderContainer>
238
+ )
239
+ }
240
+
241
+ return (
242
+ <HeaderContainer className={props.className}>
243
+ <tr className="table-header [grid-column:1/-1] grid [grid-template-columns:subgrid] border-b">
244
+ {props.columns.map((column) => (
245
+ <HeaderCell key={column.key.toString()}>{column.header}</HeaderCell>
246
+ ))}
247
+ </tr>
248
+ </HeaderContainer>
249
+ )
250
+ }
251
+
252
+ type BodyProps<T extends object> = {
253
+ columns: Column<T>[]
254
+ data: T[] | Group<T>[]
255
+ rowKey: (row: T) => string | number
256
+ onRowClick?: (row: T) => void
257
+ noResultsMessage?: ReactNode
258
+ renderGroupHeader?: (group: Group<T>) => ReactNode
259
+ renderExpandedContent?: (row: T) => ReactNode
260
+ hasMore?: boolean
261
+ handleLoadMore?: () => void
262
+ isLoading?: boolean
263
+ className?: string
264
+ }
265
+
266
+ const BodyContainer = forwardRef<
267
+ HTMLTableSectionElement,
268
+ PropsWithChildrenAndClassName
269
+ >(({ className, children }, ref) => {
270
+ return (
271
+ <tbody
272
+ ref={ref}
273
+ className={cn(
274
+ 'relative [grid-column:1/-1] grid [grid-template-columns:subgrid]',
275
+ className
276
+ )}
277
+ >
278
+ {children}
279
+ </tbody>
280
+ )
281
+ })
282
+
283
+ const Body = React.forwardRef(function Body<T extends object>(
284
+ props: BodyProps<T> | PropsWithChildrenAndClassName,
285
+ ref: React.ForwardedRef<HTMLTableSectionElement>
286
+ ) {
287
+ if (propsHasChildren<PropsWithChildrenAndClassName, BodyProps<T>>(props)) {
288
+ return (
289
+ <BodyContainer ref={ref} className={props.className}>
290
+ {props.children}
291
+ </BodyContainer>
292
+ )
293
+ }
294
+
295
+ const {
296
+ data,
297
+ columns,
298
+ rowKey,
299
+ hasMore,
300
+ onRowClick,
301
+ noResultsMessage,
302
+ renderGroupHeader,
303
+ handleLoadMore,
304
+ isLoading,
305
+ className,
306
+ renderExpandedContent,
307
+ } = props
308
+
309
+ const renderRow = (row: T | Group<T>) => {
310
+ if (isGroupOf<T>(row)) {
311
+ return (
312
+ <RowGroup
313
+ group={row}
314
+ columns={columns}
315
+ rowKey={rowKey}
316
+ renderGroupHeader={renderGroupHeader}
317
+ key={row.key}
318
+ onRowClick={onRowClick}
319
+ />
320
+ )
321
+ } else if (renderExpandedContent) {
322
+ return (
323
+ <RowExpandable
324
+ row={row}
325
+ columns={columns}
326
+ rowKey={rowKey}
327
+ renderExpandedContent={renderExpandedContent}
328
+ key={rowKey(row)}
329
+ onClick={onRowClick}
330
+ />
331
+ )
332
+ } else {
333
+ return (
334
+ <Row
335
+ row={row}
336
+ key={rowKey(row)}
337
+ columns={columns}
338
+ onClick={onRowClick}
339
+ />
340
+ )
341
+ }
342
+ }
343
+
344
+ return (
345
+ <BodyContainer ref={ref} className={cn(hasMore && 'pb-16', className)}>
346
+ {data.length === 0 ? (
347
+ <NoResultsMessage>{noResultsMessage}</NoResultsMessage>
348
+ ) : (
349
+ data.map(renderRow)
350
+ )}
351
+ {hasMore && handleLoadMore && (
352
+ <LoadMore
353
+ columns={columns}
354
+ handleLoadMore={handleLoadMore}
355
+ isLoading={isLoading}
356
+ />
357
+ )}
358
+ </BodyContainer>
359
+ )
360
+ }) as <T extends object>(
361
+ props: {
362
+ ref?: React.ForwardedRef<HTMLTableSectionElement>
363
+ } & (BodyProps<T> | PropsWithChildrenAndClassName)
364
+ ) => JSX.Element
365
+
366
+ type RowProps<T extends object> = {
367
+ row: T
368
+ onClick?: (row: T) => void
369
+ columns: Column<T>[]
370
+ className?: string
371
+ }
372
+
373
+ type RowContainerProps = {
374
+ onClick?: () => void
375
+ } & PropsWithChildrenAndClassName
376
+
377
+ function RowContainer({ className, children, onClick }: RowContainerProps) {
378
+ return (
379
+ <tr
380
+ className={cn(
381
+ 'hover:bg-muted/50 data-[state=selected]:bg-muted -z-0 [grid-column:1/-1] grid max-w-full [grid-template-columns:subgrid] border-b transition-colors last:border-none',
382
+ onClick && 'cursor-pointer',
383
+ className
384
+ )}
385
+ onClick={onClick}
386
+ >
387
+ {children}
388
+ </tr>
389
+ )
390
+ }
391
+
392
+ function Row<T extends object>(props: RowProps<T> | RowContainerProps) {
393
+ if (propsHasChildren<RowContainerProps, RowProps<T>>(props)) {
394
+ return (
395
+ <RowContainer className={props.className} onClick={props.onClick}>
396
+ {props.children}
397
+ </RowContainer>
398
+ )
399
+ }
400
+
401
+ const { row, onClick, columns, className } = props
402
+ return (
403
+ <RowContainer
404
+ className={className}
405
+ onClick={onClick ? () => onClick(row) : undefined}
406
+ >
407
+ {columns.map((column) => (
408
+ <Cell key={column.key.toString()} column={column} row={row} />
409
+ ))}
410
+ </RowContainer>
411
+ )
412
+ }
413
+
414
+ function RowExpandable<T extends object>({
415
+ row,
416
+ onClick,
417
+ columns,
418
+ rowKey,
419
+ renderExpandedContent,
420
+ className,
421
+ }: {
422
+ row: T
423
+ columns: Column<T>[]
424
+ rowKey: (row: T) => string | number
425
+ renderExpandedContent: (row: T) => ReactNode
426
+ onClick?: (row: T) => void
427
+ className?: string
428
+ }) {
429
+ const { expandedRowKeys, toggleExpanded } = useTable()
430
+
431
+ const isExpanded = expandedRowKeys.has(rowKey(row))
432
+
433
+ const expand = (e: React.MouseEvent<HTMLButtonElement>) => {
434
+ e.stopPropagation()
435
+ toggleExpanded(rowKey(row))
436
+ }
437
+
438
+ const content = useMemo(
439
+ () => renderExpandedContent(row),
440
+ [renderExpandedContent, row]
441
+ )
442
+
443
+ const renderExpandCol = useCallback(() => {
444
+ return content ? (
445
+ <TooltipProvider>
446
+ <Tooltip delayDuration={0}>
447
+ <TooltipTrigger asChild>
448
+ <div className="flex w-full justify-end">
449
+ <Button
450
+ onClick={expand}
451
+ variant={'tertiary'}
452
+ className={`h-6 w-6`}
453
+ >
454
+ <ExpandChevron isCollapsed={!isExpanded} />
455
+ </Button>
456
+ </div>
457
+ </TooltipTrigger>
458
+ <TooltipContent>{isExpanded ? 'Collapse' : 'Expand'}</TooltipContent>
459
+ </Tooltip>
460
+ </TooltipProvider>
461
+ ) : null
462
+ }, [expand, isExpanded, content])
463
+
464
+ const expandCol = columns.find((column) => column.key === expandColumn().key)
465
+
466
+ if (expandCol) {
467
+ expandCol.render = renderExpandCol
468
+ }
469
+
470
+ let onClickFn = onClick
471
+
472
+ // If there's some expanded content to show and onClick is not provided, let the row expand when clicked
473
+ if (!onClick && content) {
474
+ onClickFn = () => toggleExpanded(rowKey(row))
475
+ }
476
+
477
+ return (
478
+ <>
479
+ <Row
480
+ row={row}
481
+ onClick={onClickFn}
482
+ columns={columns}
483
+ className={className}
484
+ />
485
+ {/* This grid stuff is a cute way to make the height animate smoothly when expanding/collapsing */}
486
+ <div
487
+ className={cn(
488
+ '[grid-column:1/-1] grid overflow-hidden transition-[grid-template-rows] duration-300',
489
+ isExpanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'
490
+ )}
491
+ >
492
+ <div className="min-h-0 overflow-auto">{content}</div>
493
+ </div>
494
+ </>
495
+ )
496
+ }
497
+
498
+ function RowGroup<T extends object>({
499
+ group,
500
+ columns,
501
+ rowKey,
502
+ renderGroupHeader,
503
+ className,
504
+ onRowClick,
505
+ }: {
506
+ group: Group<T>
507
+ columns: Column<T>[]
508
+ rowKey: (row: T) => string | number
509
+ renderGroupHeader?: (group: Group<T>) => ReactNode
510
+ className?: string
511
+ onRowClick?: (row: T) => void
512
+ }) {
513
+ return (
514
+ <div
515
+ className={cn(
516
+ '[grid-column:1/-1] grid [grid-template-columns:subgrid]',
517
+ className
518
+ )}
519
+ >
520
+ <div className="[grid-column:1/-1]">{renderGroupHeader?.(group)}</div>
521
+ {group.items.map((row) => (
522
+ <Row
523
+ row={row}
524
+ key={rowKey(row)}
525
+ columns={columns}
526
+ onClick={onRowClick}
527
+ />
528
+ ))}
529
+ </div>
530
+ )
531
+ }
532
+
533
+ type CellProps<T extends object> = {
534
+ row: T
535
+ column: Column<T>
536
+ className?: string
537
+ }
538
+
539
+ function CellContainer({ children, className }: PropsWithChildrenAndClassName) {
540
+ return (
541
+ <td
542
+ className={cn(
543
+ styles.tableCell,
544
+ `flex max-w-full items-center`,
545
+ className
546
+ )}
547
+ >
548
+ <SubtableIndendation />
549
+ {children}
550
+ </td>
551
+ )
552
+ }
553
+
554
+ function Cell<T extends object>(
555
+ props: CellProps<T> | PropsWithChildrenAndClassName
556
+ ) {
557
+ if (propsHasChildren<PropsWithChildrenAndClassName, CellProps<T>>(props)) {
558
+ return (
559
+ <CellContainer className={props.className}>
560
+ {props.children}
561
+ </CellContainer>
562
+ )
563
+ }
564
+
565
+ const { row, column, className } = props
566
+ const content = column.render
567
+ ? column.render(row)
568
+ : isKeyOfT<T>(column.key, row)
569
+ ? String(row[column.key])
570
+ : ''
571
+
572
+ return <CellContainer className={className}>{content}</CellContainer>
573
+ }
574
+
575
+ function NoResultsMessage({
576
+ className,
577
+ children,
578
+ }: PropsWithChildrenAndClassName) {
579
+ const Wrapper = ({ children, className }: PropsWithChildrenAndClassName) => (
580
+ <div
581
+ className={cn(
582
+ '[grid-column:1/-1] grid [grid-template-columns:subgrid]',
583
+ className
584
+ )}
585
+ >
586
+ {children}
587
+ </div>
588
+ )
589
+
590
+ const ContentWrapper = ({ children }: PropsWithChildren) => (
591
+ <div className="[grid-column:1/-1]">{children}</div>
592
+ )
593
+
594
+ return (
595
+ <Wrapper className={className}>
596
+ <ContentWrapper>{children}</ContentWrapper>
597
+ </Wrapper>
598
+ )
599
+ }
600
+
601
+ function LoadMore<T extends object>({
602
+ columns,
603
+ handleLoadMore,
604
+ isLoading,
605
+ }: {
606
+ columns: Column<T>[]
607
+ handleLoadMore: () => void
608
+ isLoading?: boolean
609
+ }) {
610
+ const RowWrapper = ({
611
+ children,
612
+ className,
613
+ }: PropsWithChildren<{ className?: string }>) => {
614
+ const colWidths = columns.map((column) => column.width ?? '1fr').join(' ')
615
+ return (
616
+ <tr
617
+ style={{ '--grid-template-columns': colWidths } as React.CSSProperties}
618
+ className={cn(
619
+ 'absolute right-0 bottom-0 left-0 -z-0 [grid-column:1/-1] grid min-h-16 max-w-full cursor-pointer [grid-template-columns:var(--grid-template-columns)] items-center border-b opacity-30 transition-colors',
620
+ className
621
+ )}
622
+ >
623
+ {children}
624
+ </tr>
625
+ )
626
+ }
627
+
628
+ const ButtonWrapper = ({ children }: PropsWithChildren) => (
629
+ <div className="absolute right-0 bottom-0 left-0 z-10 flex min-h-14 w-full items-center justify-center py-4">
630
+ {children}
631
+ </div>
632
+ )
633
+
634
+ return (
635
+ <>
636
+ <RowWrapper
637
+ className={cn(isLoading && 'animate-pulse opacity-100 duration-[2.5s]')}
638
+ >
639
+ {columns.map((column) => (
640
+ <Cell key={column.key.toString()}>
641
+ <div className="bg-muted h-4 w-full rounded" />
642
+ </Cell>
643
+ ))}
644
+ </RowWrapper>
645
+ <ButtonWrapper>
646
+ <button
647
+ className="focus-visible:ring-ring border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex h-9 items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-medium whitespace-nowrap normal-case transition-colors select-none focus-visible:ring-1 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0"
648
+ onClick={handleLoadMore}
649
+ >
650
+ {isLoading ? (
651
+ <>
652
+ Loading
653
+ <Loader2 className="animate-spin" />
654
+ </>
655
+ ) : (
656
+ 'Load more'
657
+ )}
658
+ </button>
659
+ </ButtonWrapper>
660
+ </>
661
+ )
662
+ }
663
+
664
+ function HeaderCell({
665
+ className,
666
+ children,
667
+ }: PropsWithChildren<{ className?: string }>) {
668
+ return (
669
+ <th
670
+ className={cn(
671
+ styles.tableHeader,
672
+ 'text-body flex items-center align-middle font-medium whitespace-nowrap select-none',
673
+ className
674
+ )}
675
+ >
676
+ <SubtableIndendation />
677
+ {children}
678
+ </th>
679
+ )
680
+ }
681
+
682
+ // Has the effect of "indenting" subtables while still allowing them to occupy the full width of the parent table
683
+ function SubtableIndendation() {
684
+ const { depth } = useTable()
685
+ return depth > 1 ? (
686
+ <div style={{ minWidth: `${16 * (depth - 1)}px` }} />
687
+ ) : null
688
+ }
689
+
690
+ function propsHasChildren<P extends PropsWithChildren, Q extends object>(
691
+ props: P | Q
692
+ ): props is P {
693
+ return 'children' in props && props.children !== undefined
694
+ }
695
+
696
+ function isKeyOfT<T extends object>(key: unknown, data: T): key is keyof T {
697
+ return typeof key === 'string' && Object.keys(data).includes(key)
698
+ }
699
+
700
+ export const Table = Object.assign(TableRoot, {
701
+ Header: Object.assign(Header, { Cell: HeaderCell }),
702
+ Body,
703
+ Row,
704
+ Cell,
705
+ RowGroup,
706
+ NoResultsMessage,
707
+ })