@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,115 @@
1
+ import { Org } from '.'
2
+ import { CommandItem } from '../Command'
3
+ import { GradientCircle } from '../GradientCircle'
4
+ import { Icon } from '../Icon'
5
+ import { cn } from '../../lib/utils'
6
+ import { ScrollingList } from './ScrollingList'
7
+ import { VirtuosoHandle } from 'react-virtuoso'
8
+ import { useEffect, useRef, useState } from 'react'
9
+ import { SearchBox } from './SearchBox'
10
+ import { Text } from '../Text'
11
+
12
+ interface OrgListProps {
13
+ orgs: Org[]
14
+ enableRecents: boolean
15
+ showRecents: boolean
16
+ selectedOrg: Org | null
17
+ setSelectedOrg: (org: Org) => void
18
+ onSelectRecent: () => void
19
+ filterOrgFunc: (org: Org, search: string) => boolean
20
+ }
21
+
22
+ export function OrgList({
23
+ orgs,
24
+ showRecents,
25
+ selectedOrg,
26
+ setSelectedOrg,
27
+ enableRecents,
28
+ onSelectRecent,
29
+ filterOrgFunc,
30
+ }: OrgListProps) {
31
+ const virtuoso = useRef<VirtuosoHandle | null>(null)
32
+ const [search, setSearch] = useState('')
33
+ const inputRef = useRef<HTMLInputElement>(null)
34
+ const [filteredOrgs, setFilteredOrgs] = useState(orgs)
35
+
36
+ useEffect(() => {
37
+ setFilteredOrgs(orgs.filter((org) => filterOrgFunc(org, search)))
38
+ }, [search, filterOrgFunc, orgs])
39
+
40
+ useEffect(() => {
41
+ if (selectedOrg && virtuoso.current) {
42
+ const index = orgs.findIndex((org) => org.slug === selectedOrg.slug)
43
+
44
+ setTimeout(() => {
45
+ virtuoso.current?.scrollIntoView({
46
+ index,
47
+ behavior: orgs.length < 10 ? 'smooth' : 'auto',
48
+ })
49
+ }, 100)
50
+ }
51
+ }, [selectedOrg, orgs])
52
+
53
+ return (
54
+ <div className="border-neutral-softest flex w-1/3 flex-col border-r">
55
+ <SearchBox
56
+ inputRef={inputRef}
57
+ placeholder="Search organizations..."
58
+ search={search}
59
+ setSearch={setSearch}
60
+ />
61
+
62
+ {filteredOrgs.length > 0 ? (
63
+ <ScrollingList
64
+ items={filteredOrgs}
65
+ ref={virtuoso}
66
+ renderItem={(org) => (
67
+ <CommandItem
68
+ key={org.slug}
69
+ onSelect={() => setSelectedOrg(org)}
70
+ value={`org-${org.slug}`}
71
+ className={cn(
72
+ 'hover:!bg-accent/40 flex max-w-lg cursor-pointer flex-row gap-3 rounded-none p-4 text-base',
73
+ !showRecents &&
74
+ selectedOrg?.slug === org.slug &&
75
+ 'bg-accent/40 text-accent-foreground font-semibold'
76
+ )}
77
+ >
78
+ <GradientCircle name={org.label} />
79
+ <Text className="truncate">{org.slug}</Text>
80
+ {!showRecents && selectedOrg?.slug === org.slug && (
81
+ <div className="ml-auto">
82
+ <Icon name="chevron-right" size="small" />
83
+ </div>
84
+ )}
85
+ </CommandItem>
86
+ )}
87
+ />
88
+ ) : (
89
+ <div className="text-muted-foreground m-auto flex h-full max-w-2/3 items-center justify-center p-6 text-center">
90
+ No organizations found
91
+ </div>
92
+ )}
93
+
94
+ {enableRecents && (
95
+ <div className="bg-background border-neutral-softest border-t">
96
+ <CommandItem
97
+ onSelect={onSelectRecent}
98
+ className={cn(
99
+ 'bg-background text-foreground/80 sticky top-0 z-10 m-1 flex cursor-pointer items-center p-4 text-base',
100
+ showRecents && 'text-accent-foreground font-semibold'
101
+ )}
102
+ >
103
+ <Icon name="history" />
104
+ <Text>Recently used</Text>
105
+ {showRecents && (
106
+ <div className="ml-auto">
107
+ <Icon name="chevron-right" size="2xl" />
108
+ </div>
109
+ )}
110
+ </CommandItem>
111
+ </div>
112
+ )}
113
+ </div>
114
+ )
115
+ }
@@ -0,0 +1,207 @@
1
+ import * as React from 'react'
2
+
3
+ import { Command, CommandInput, CommandItem, CommandList } from '../Command'
4
+ import { Popover, PopoverContent, PopoverTrigger } from '../Popover'
5
+ import { Org } from '.'
6
+ import { Icon } from '../Icon'
7
+ import { Virtuoso, VirtuosoHandle } from 'react-virtuoso'
8
+ import { cn } from '../../lib/utils'
9
+
10
+ interface OrgSelectorProps {
11
+ orgs: Org[]
12
+ selectedOrg: Org | null
13
+ onSelect: (org: Org) => void
14
+ searchable?: boolean
15
+ error?: boolean
16
+ errorText?: string
17
+ emptyText?: string
18
+ searchPlaceholder?: string
19
+ }
20
+
21
+ const COMBOBOX_CONFIG = {
22
+ heights: {
23
+ row: 16,
24
+ header: 28,
25
+ padding: 8,
26
+ max: 250,
27
+ },
28
+ } as const
29
+
30
+ function uniqBy<T>(arr: T[], key: keyof T) {
31
+ return [...new Map(arr.map((item) => [item[key], item])).values()]
32
+ }
33
+
34
+ export function OrgSelector({
35
+ orgs = [],
36
+ selectedOrg,
37
+ onSelect,
38
+ searchable = true,
39
+ error = false,
40
+ errorText = 'An error occurred',
41
+ emptyText = 'No organizations found',
42
+ searchPlaceholder = 'Search...',
43
+ }: OrgSelectorProps) {
44
+ const [open, setOpen] = React.useState(false)
45
+ const [search, setSearch] = React.useState('')
46
+ const [activeIndex, setActiveIndex] = React.useState(0)
47
+ const triggerRef = React.useRef<HTMLButtonElement>(null)
48
+ const handleSelect = (org: Org, index: number) => {
49
+ onSelect(org)
50
+ setOpen(false)
51
+ setActiveIndex(index)
52
+ }
53
+
54
+ const filteredItems = React.useMemo(() => {
55
+ if (!search) return uniqBy(orgs, 'slug')
56
+ return uniqBy(
57
+ orgs.filter((org) => org.slug.includes(search)),
58
+ 'slug'
59
+ )
60
+ }, [search, orgs])
61
+
62
+ const getSelectorMedianWidth = React.useMemo(() => {
63
+ if (!orgs.length) return 0
64
+
65
+ // Get lengths of all slugs
66
+ const lengths = orgs.map((org) => org.slug.length)
67
+
68
+ // Sort lengths to find the middle value(s)
69
+ const sortedLengths = [...lengths].sort((a, b) => a - b)
70
+ const mid = Math.floor(sortedLengths.length / 2)
71
+
72
+ // Get median length (average of two middle values for even-length arrays)
73
+ const medianLength =
74
+ sortedLengths.length % 2 === 0
75
+ ? (sortedLengths[mid - 1] + sortedLengths[mid]) / 2
76
+ : sortedLengths[mid]
77
+
78
+ // Base width calculation:
79
+ // - Average lowercase letter: ~8px at 16px font size
80
+ // - Average uppercase letter: ~10px at 16px font size
81
+ // - Numbers: ~8px at 16px font size
82
+ // - Font size adjustment (text-lg = 1.125rem = 18px): 18/16 = 1.125
83
+ const baseCharWidth = 8 * 1.125
84
+
85
+ return Math.round(medianLength * baseCharWidth * 1.2)
86
+ }, [orgs])
87
+
88
+ const virtuosoRef = React.useRef<VirtuosoHandle>(null)
89
+
90
+ const handleOpenChange = (open: boolean) => {
91
+ setOpen(open)
92
+ if (open) {
93
+ virtuosoRef.current?.scrollToIndex({ index: activeIndex, align: 'start' })
94
+ }
95
+ }
96
+
97
+ const VirtuosoItem = React.memo(
98
+ ({ index, org }: { index: number; org: Org }) => {
99
+ return (
100
+ <CommandItem
101
+ key={org.slug}
102
+ value={org.slug}
103
+ onSelect={() => handleSelect(org, index)}
104
+ className="relative block w-full cursor-pointer items-center gap-2 truncate rounded-sm px-2 py-1.5 text-sm outline-none select-none"
105
+ >
106
+ {org.label}
107
+ </CommandItem>
108
+ )
109
+ }
110
+ )
111
+
112
+ // When the search changes, reset the active index and scroll to the top
113
+ React.useEffect(() => {
114
+ setActiveIndex(0)
115
+ virtuosoRef.current?.scrollToIndex({ index: 0, align: 'start' })
116
+ }, [search])
117
+
118
+ const handleKeyDown = React.useCallback(
119
+ (e: React.KeyboardEvent<HTMLDivElement>) => {
120
+ if (!['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(e.key)) return
121
+ if (!virtuosoRef.current) return
122
+
123
+ let newIndex = activeIndex
124
+ switch (e.key) {
125
+ case 'ArrowDown':
126
+ newIndex = Math.min(activeIndex + 1, filteredItems.length - 1)
127
+ break
128
+ case 'ArrowUp':
129
+ newIndex = Math.max(activeIndex - 1, 0)
130
+ break
131
+ case 'Home':
132
+ newIndex = 0
133
+ break
134
+ }
135
+ setActiveIndex(newIndex)
136
+ virtuosoRef.current?.scrollToIndex({ index: newIndex, align: 'start' })
137
+ },
138
+ [activeIndex, filteredItems.length]
139
+ )
140
+
141
+ const virtuosoHeight = React.useMemo(() => {
142
+ const { row, header, padding, max } = COMBOBOX_CONFIG.heights
143
+ const shouldShowHeaders = filteredItems.some((group) => group.label)
144
+ const headerHeight = shouldShowHeaders ? filteredItems.length * header : 0
145
+ const contentHeight = filteredItems.reduce((sum) => row + sum, headerHeight)
146
+
147
+ return Math.min(contentHeight + padding, max)
148
+ }, [filteredItems])
149
+
150
+ return (
151
+ <Popover open={open} onOpenChange={handleOpenChange}>
152
+ <PopoverTrigger asChild className="flex flex-row items-center">
153
+ <button
154
+ type="button"
155
+ role="combobox"
156
+ ref={triggerRef}
157
+ aria-expanded={open}
158
+ title={selectedOrg ? selectedOrg.slug : 'Select organization...'}
159
+ className="flex flex-row items-center gap-2 outline-none"
160
+ >
161
+ <div
162
+ className={cn(
163
+ 'min-w-0 flex-1 truncate text-left text-lg font-semibold lowercase',
164
+ open ? 'text-foreground' : 'text-foreground/80'
165
+ )}
166
+ style={{ width: getSelectorMedianWidth }}
167
+ >
168
+ {selectedOrg ? selectedOrg.slug : 'Select organization...'}
169
+ </div>
170
+ <Icon
171
+ name={open ? 'chevron-up' : 'chevron-down'}
172
+ className="flex-shrink-0"
173
+ />
174
+ </button>
175
+ </PopoverTrigger>
176
+ <PopoverContent className="w-fit p-0" align="start" sideOffset={10}>
177
+ <Command shouldFilter={false} onKeyDown={handleKeyDown}>
178
+ {searchable && (
179
+ <CommandInput
180
+ value={search}
181
+ onValueChange={setSearch}
182
+ placeholder={searchPlaceholder}
183
+ />
184
+ )}
185
+ <CommandList>
186
+ {error ? (
187
+ <CommandItem disabled>{errorText}</CommandItem>
188
+ ) : filteredItems.length === 0 ? (
189
+ <CommandItem disabled>{emptyText}</CommandItem>
190
+ ) : (
191
+ <Virtuoso
192
+ style={{ height: virtuosoHeight }}
193
+ data={filteredItems}
194
+ ref={virtuosoRef}
195
+ initialTopMostItemIndex={activeIndex}
196
+ totalCount={filteredItems.length}
197
+ itemContent={(index, org) => (
198
+ <VirtuosoItem index={index} org={org} />
199
+ )}
200
+ />
201
+ )}
202
+ </CommandList>
203
+ </Command>
204
+ </PopoverContent>
205
+ </Popover>
206
+ )
207
+ }
@@ -0,0 +1,83 @@
1
+ import { cn } from '../../lib/utils'
2
+ import { Org, Workspace } from '.'
3
+ import { WorkspaceItem } from './WorkspaceItem'
4
+ import { ScrollingList } from './ScrollingList'
5
+ import { CommandGroup, CommandItem } from '../Command'
6
+ import { Icon } from '../Icon'
7
+ import { useState, useRef, useEffect } from 'react'
8
+ import { SearchBox } from './SearchBox'
9
+ import { Text } from '../Text'
10
+
11
+ interface RecentWorkspacesProps {
12
+ orgsWithFilteredWorkspaces: Org[]
13
+ onSelect: (org: Org, workspace: Workspace) => void
14
+ fullWidth?: boolean
15
+ selectedOrg: Org | null
16
+ selectedWorkspace: Workspace | null
17
+ handleCreateViewOpen: () => void
18
+ }
19
+
20
+ export function RecentWorkspaces({
21
+ orgsWithFilteredWorkspaces,
22
+ onSelect,
23
+ fullWidth = false,
24
+ selectedOrg,
25
+ selectedWorkspace,
26
+ handleCreateViewOpen,
27
+ }: RecentWorkspacesProps) {
28
+ const [search, setSearch] = useState('')
29
+ const inputRef = useRef<HTMLInputElement>(null)
30
+ const [filteredRecents, setFilteredRecents] = useState<Org[]>(
31
+ orgsWithFilteredWorkspaces
32
+ )
33
+
34
+ useEffect(() => {
35
+ setFilteredRecents(
36
+ orgsWithFilteredWorkspaces.filter((org) =>
37
+ org.workspaces.some((workspace) => workspace.slug.includes(search))
38
+ )
39
+ )
40
+ }, [search, orgsWithFilteredWorkspaces])
41
+
42
+ return (
43
+ <div className={cn('flex h-full flex-col', fullWidth ? 'w-full' : 'w-2/3')}>
44
+ <SearchBox
45
+ inputRef={inputRef}
46
+ placeholder="Search workspaces..."
47
+ search={search}
48
+ setSearch={setSearch}
49
+ />
50
+ <ScrollingList
51
+ items={filteredRecents}
52
+ renderItem={(org) => {
53
+ return (
54
+ <CommandGroup key={org.id} heading={org.slug}>
55
+ {org.workspaces.map((workspace) => (
56
+ <WorkspaceItem
57
+ key={workspace.id}
58
+ workspace={workspace}
59
+ isSelected={
60
+ selectedOrg?.id === org.id &&
61
+ selectedWorkspace?.id === workspace.id
62
+ }
63
+ selectedOrg={org}
64
+ handleSelect={onSelect}
65
+ />
66
+ ))}
67
+ </CommandGroup>
68
+ )
69
+ }}
70
+ />
71
+
72
+ <div className="bg-background border-neutral-softest border-t">
73
+ <CommandItem
74
+ onSelect={handleCreateViewOpen}
75
+ className={cn('m-1 cursor-pointer !items-center p-4 text-base')}
76
+ >
77
+ <Icon name="plus" />
78
+ <Text>Create workspace</Text>
79
+ </CommandItem>
80
+ </div>
81
+ </div>
82
+ )
83
+ }
@@ -0,0 +1,84 @@
1
+ import React from 'react'
2
+ import {
3
+ GroupedVirtuoso,
4
+ GroupedVirtuosoHandle,
5
+ Virtuoso,
6
+ VirtuosoHandle,
7
+ } from 'react-virtuoso'
8
+
9
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
10
+ const forwardRefWithGenerics = <T, P = {}>(
11
+ render: (props: P, ref: React.Ref<T>) => React.ReactNode | null
12
+ ): ((props: P & React.RefAttributes<T>) => React.ReactNode | null) =>
13
+ // @ts-expect-error cant type this correctly
14
+ React.forwardRef<T, P>(render)
15
+
16
+ const DEFAULT_SCROLLBAR_CLASSES =
17
+ '[&::-webkit-scrollbar-thumb]:bg-gray-200 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb:active]:bg-black/20 dark:[&::-webkit-scrollbar-thumb]:bg-neutral-700 dark:[&::-webkit-scrollbar-thumb:active]:bg-white/50 [&::-webkit-scrollbar-track]:bg-gray-100 dark:[&::-webkit-scrollbar-track]:bg-neutral-900 [&::-webkit-scrollbar]:w-2'
18
+
19
+ interface ScrollingListProps<T> {
20
+ items: T[]
21
+ renderItem: (item: T) => React.ReactNode
22
+ ref?: React.RefObject<VirtuosoHandle>
23
+ }
24
+
25
+ function ScrollingListInner<T>(
26
+ { items, renderItem }: ScrollingListProps<T>,
27
+ ref: React.ForwardedRef<VirtuosoHandle>
28
+ ) {
29
+ return (
30
+ <Virtuoso
31
+ className={DEFAULT_SCROLLBAR_CLASSES}
32
+ totalCount={items.length}
33
+ data={items}
34
+ itemContent={(_, item) => renderItem(item)}
35
+ ref={ref}
36
+ />
37
+ )
38
+ }
39
+
40
+ export const ScrollingList = forwardRefWithGenerics(ScrollingListInner)
41
+
42
+ interface GroupedScrollingListProps<G> {
43
+ groups: G[]
44
+
45
+ /**
46
+ * An array of integers to represent the number of items in each group.
47
+ * e.g [10, 20, 30] means there are 3 groups with 10, 20, and 30 items respectively.
48
+ */
49
+ groupCounts: number[]
50
+ renderGroupHeader: (group: G) => React.ReactNode
51
+ renderItem: (group: G, itemIndex: number) => React.ReactNode
52
+ ref?: React.RefObject<GroupedVirtuosoHandle>
53
+ }
54
+
55
+ function GroupedScrollingListInner<G>(
56
+ {
57
+ groups,
58
+ groupCounts,
59
+ renderGroupHeader,
60
+ renderItem,
61
+ }: GroupedScrollingListProps<G>,
62
+ ref: React.ForwardedRef<GroupedVirtuosoHandle>
63
+ ) {
64
+ return (
65
+ <GroupedVirtuoso
66
+ style={{
67
+ height: '100%',
68
+ }}
69
+ className={DEFAULT_SCROLLBAR_CLASSES}
70
+ groupCounts={groupCounts}
71
+ groupContent={(index) => renderGroupHeader(groups[index])}
72
+ itemContent={(itemIndex, groupIndex) => {
73
+ const group = groups[groupIndex]
74
+
75
+ return renderItem(group, itemIndex)
76
+ }}
77
+ ref={ref}
78
+ />
79
+ )
80
+ }
81
+
82
+ export const GroupedScrollingList = forwardRefWithGenerics(
83
+ GroupedScrollingListInner
84
+ )
@@ -0,0 +1,40 @@
1
+ import { RefObject } from 'react'
2
+ import { Icon } from '../Icon'
3
+
4
+ interface SearchBoxProps {
5
+ inputRef: RefObject<HTMLInputElement>
6
+ search: string
7
+ setSearch: (search: string) => void
8
+ placeholder: string
9
+ }
10
+
11
+ export function SearchBox({
12
+ inputRef,
13
+ search,
14
+ setSearch,
15
+ placeholder,
16
+ }: SearchBoxProps) {
17
+ return (
18
+ <div className="border-neutral-softest relative flex items-center gap-2 border-b p-3">
19
+ <Icon name="search" className="text-muted-foreground" />
20
+ <input
21
+ ref={inputRef}
22
+ placeholder={placeholder}
23
+ value={search}
24
+ className="placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-base outline-none disabled:cursor-not-allowed disabled:opacity-50"
25
+ onChange={(e) => setSearch(e.target.value)}
26
+ />
27
+
28
+ <div className="absolute right-3">
29
+ {search && (
30
+ <button
31
+ className="hover:bg-accent text-muted-foreground flex h-8 w-8 cursor-pointer items-center justify-center rounded-sm"
32
+ onClick={() => setSearch('')}
33
+ >
34
+ <Icon name="x" size="small" />
35
+ </button>
36
+ )}
37
+ </div>
38
+ </div>
39
+ )
40
+ }
@@ -0,0 +1,37 @@
1
+ import { cn } from '../../lib/utils'
2
+ import { CommandItem } from '../Command'
3
+ import { GradientCircle } from '../GradientCircle'
4
+ import { Org, Workspace } from '.'
5
+ import { Text } from '../Text'
6
+ import { forwardRef } from 'react'
7
+
8
+ interface WorkspaceItemProps {
9
+ workspace: Workspace
10
+ selectedOrg: Org
11
+ isSelected: boolean
12
+ handleSelect: (org: Org, workspace: Workspace) => void
13
+ }
14
+
15
+ export const WorkspaceItem = forwardRef<HTMLDivElement, WorkspaceItemProps>(
16
+ function WorkspaceItem(
17
+ { workspace, selectedOrg, handleSelect, isSelected },
18
+ ref
19
+ ) {
20
+ return (
21
+ <CommandItem
22
+ key={workspace.id}
23
+ ref={ref}
24
+ value={`workspace-${selectedOrg.id}-${workspace.id}`}
25
+ onSelect={() => handleSelect(selectedOrg, workspace)}
26
+ className={cn(
27
+ 'hover:!bg-accent/40 data-[selected=true]:!bg-accent/40 flex w-full max-w-full cursor-pointer flex-row gap-3 rounded-none p-4 text-base',
28
+ isSelected && 'font-semibold',
29
+ !workspace.active && 'opacity-50'
30
+ )}
31
+ >
32
+ <GradientCircle name={workspace.label} />
33
+ <Text className="truncate">{workspace.label}</Text>
34
+ </CommandItem>
35
+ )
36
+ }
37
+ )
@@ -0,0 +1,107 @@
1
+ import { CommandItem } from '../Command'
2
+ import { Icon } from '../Icon'
3
+ import { Org, Workspace } from '.'
4
+ import { cn } from '../../lib/utils'
5
+ import { WorkspaceItem } from './WorkspaceItem'
6
+ import { useEffect, useRef, useState } from 'react'
7
+ import { ScrollingList } from './ScrollingList'
8
+ import { VirtuosoHandle } from 'react-virtuoso'
9
+ import { SearchBox } from './SearchBox'
10
+ import { Text } from '../Text'
11
+
12
+ interface WorkspaceListProps {
13
+ selectedOrg: Org
14
+ selectedWorkspace: Workspace | null
15
+ handleCreateViewOpen: () => void
16
+ handleSelect: (org: Org, workspace: Workspace) => void
17
+ enableCreate?: boolean
18
+ filterWorkspaceFunc: (workspace: Workspace, search: string) => boolean
19
+ }
20
+
21
+ export function WorkspaceList({
22
+ selectedOrg,
23
+ selectedWorkspace,
24
+ handleCreateViewOpen,
25
+ handleSelect,
26
+ enableCreate = true,
27
+ filterWorkspaceFunc,
28
+ }: WorkspaceListProps) {
29
+ const virtuoso = useRef<VirtuosoHandle | null>(null)
30
+ const [search, setSearch] = useState('')
31
+ const inputRef = useRef<HTMLInputElement>(null)
32
+ const [filteredWorkspaces, setFilteredWorkspaces] = useState<Workspace[]>(
33
+ selectedOrg?.workspaces || []
34
+ )
35
+
36
+ useEffect(() => {
37
+ setFilteredWorkspaces(
38
+ selectedOrg?.workspaces.filter((workspace) =>
39
+ filterWorkspaceFunc(workspace, search)
40
+ )
41
+ )
42
+ }, [search, selectedOrg?.workspaces, filterWorkspaceFunc])
43
+
44
+ useEffect(() => {
45
+ if (selectedWorkspace && virtuoso.current) {
46
+ const index = selectedOrg?.workspaces.findIndex(
47
+ (workspace) => workspace.slug === selectedWorkspace.slug
48
+ )
49
+
50
+ // wait for the ref to update
51
+ setTimeout(() => {
52
+ virtuoso.current?.scrollToIndex({
53
+ index,
54
+ behavior: selectedOrg?.workspaces.length < 10 ? 'smooth' : 'auto',
55
+ })
56
+ }, 100)
57
+ }
58
+ }, [selectedWorkspace, selectedOrg?.workspaces])
59
+
60
+ return (
61
+ <div className="flex w-2/3 flex-col">
62
+ <SearchBox
63
+ inputRef={inputRef}
64
+ placeholder="Search workspaces..."
65
+ search={search}
66
+ setSearch={setSearch}
67
+ />
68
+ {filteredWorkspaces.length === 0 ? (
69
+ <div className="flex flex-grow items-center justify-center">
70
+ <p className="text-body">
71
+ {search.length > 0
72
+ ? 'No workspaces found'
73
+ : 'No workspaces in this organization'}
74
+ </p>
75
+ </div>
76
+ ) : (
77
+ <ScrollingList
78
+ ref={virtuoso}
79
+ items={filteredWorkspaces}
80
+ renderItem={(workspace) => (
81
+ <WorkspaceItem
82
+ key={workspace.id}
83
+ workspace={workspace}
84
+ selectedOrg={selectedOrg}
85
+ handleSelect={handleSelect}
86
+ isSelected={
87
+ selectedOrg.id === selectedOrg.id &&
88
+ selectedWorkspace?.id === workspace.id
89
+ }
90
+ />
91
+ )}
92
+ />
93
+ )}
94
+ {enableCreate && (
95
+ <div className="bg-background border-neutral-softest border-t">
96
+ <CommandItem
97
+ onSelect={handleCreateViewOpen}
98
+ className={cn('m-1 cursor-pointer !items-center p-4 text-base')}
99
+ >
100
+ <Icon name="plus" />
101
+ <Text>Create workspace</Text>
102
+ </CommandItem>
103
+ </div>
104
+ )}
105
+ </div>
106
+ )
107
+ }