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

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,531 @@
1
+ import * as React from 'react'
2
+ import { Slot } from '@radix-ui/react-slot'
3
+ import { cva } from 'class-variance-authority'
4
+
5
+ import { cn } from '../../lib/utils'
6
+ import { ButtonSize, ButtonVariant, ButtonContext } from '../../types'
7
+
8
+ // Lerp for angles, taking the shortest path around the circle
9
+ const lerpAngle = (a: number, b: number, t: number) => {
10
+ // Normalize angles to 0-360 range
11
+ a = ((a % 360) + 360) % 360
12
+ b = ((b % 360) + 360) % 360
13
+
14
+ // Calculate the shortest difference
15
+ let diff = b - a
16
+ if (diff > 180) {
17
+ diff -= 360
18
+ } else if (diff < -180) {
19
+ diff += 360
20
+ }
21
+
22
+ // Interpolate and normalize result
23
+ const result = a + diff * t
24
+ return ((result % 360) + 360) % 360
25
+ }
26
+
27
+ const useAnimationFrame = (
28
+ callback: (timestamp: number, delta: number) => void,
29
+ enabled: boolean = true
30
+ ) => {
31
+ const requestRef = React.useRef<number>()
32
+ const previousTimeRef = React.useRef<number>()
33
+
34
+ const animate = React.useCallback(
35
+ (timestamp: number) => {
36
+ if (previousTimeRef.current !== undefined) {
37
+ const deltaTime = timestamp - previousTimeRef.current
38
+ callback(timestamp, deltaTime)
39
+ }
40
+ previousTimeRef.current = timestamp
41
+ if (enabled) {
42
+ requestRef.current = requestAnimationFrame(animate)
43
+ }
44
+ },
45
+ [callback, enabled]
46
+ )
47
+
48
+ React.useEffect(() => {
49
+ if (enabled) {
50
+ requestRef.current = requestAnimationFrame(animate)
51
+ }
52
+ return () => {
53
+ if (requestRef.current) {
54
+ cancelAnimationFrame(requestRef.current)
55
+ }
56
+ }
57
+ }, [animate, enabled])
58
+ }
59
+
60
+ const ButtonLeftIcon = React.forwardRef<
61
+ HTMLSpanElement,
62
+ React.HTMLAttributes<HTMLSpanElement>
63
+ >(({ className, ...props }, ref) => (
64
+ <span
65
+ ref={ref}
66
+ className={cn(
67
+ 'inline-flex shrink-0 items-center justify-center',
68
+ className
69
+ )}
70
+ {...props}
71
+ />
72
+ ))
73
+ ButtonLeftIcon.displayName = 'ButtonLeftIcon'
74
+
75
+ const ButtonRightIcon = React.forwardRef<
76
+ HTMLSpanElement,
77
+ React.HTMLAttributes<HTMLSpanElement>
78
+ >(({ className, ...props }, ref) => (
79
+ <span
80
+ ref={ref}
81
+ className={cn(
82
+ 'inline-flex shrink-0 items-center justify-center',
83
+ className
84
+ )}
85
+ {...props}
86
+ />
87
+ ))
88
+ ButtonRightIcon.displayName = 'ButtonRightIcon'
89
+
90
+ const ButtonIcon = ButtonLeftIcon
91
+
92
+ const ButtonText = React.forwardRef<
93
+ HTMLSpanElement,
94
+ React.HTMLAttributes<HTMLSpanElement>
95
+ >(({ className, ...props }, ref) => (
96
+ <span ref={ref} className={cn('flex-1', className)} {...props} />
97
+ ))
98
+ ButtonText.displayName = 'ButtonText'
99
+
100
+ const buttonVariants = cva(
101
+ 'relative inline-flex items-center justify-center whitespace-nowrap text-sm font-mono uppercase tracking-[0.01em] transition-all select-none cursor-pointer focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-[var(--border-focus)] focus-visible:ring-offset-[var(--bg-surface-primary-default)] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
102
+ {
103
+ variants: {
104
+ context: {
105
+ product: 'rounded-sm',
106
+ marketing: 'rounded-full',
107
+ },
108
+ variant: {
109
+ brand:
110
+ 'relative text-btn-brand hover:text-btn-brand-hover disabled:text-btn-brand-disabled [transform:translateZ(0)] before:absolute before:content-[""] before:-z-10 before:pointer-events-none [--gradient-rotation:220deg] before:bg-[conic-gradient(from_var(--gradient-rotation),hsl(334,54%,13%),hsl(4,67%,47%),hsl(23,96%,62%),hsl(68,52%,72%),hsl(108,24%,41%),hsl(154,100%,7%),hsl(220,100%,12%),hsl(214,69%,50%),hsl(216,100%,80%),hsl(334,54%,13%))] after:absolute after:content-[""] after:-z-20 after:pointer-events-none after:opacity-0 after:transition-opacity after:duration-300 hover:after:opacity-100 after:bg-[conic-gradient(from_var(--gradient-rotation),hsl(334,54%,13%),hsl(4,67%,47%),hsl(23,96%,62%),hsl(68,52%,72%),hsl(108,24%,41%),hsl(154,100%,7%),hsl(220,100%,12%),hsl(214,69%,50%),hsl(216,100%,80%),hsl(334,54%,13%))] after:blur-[2px] focus-visible:ring-2 focus-visible:ring-offset-3 focus-visible:ring-[var(--border-focus)] focus-visible:ring-offset-[var(--bg-surface-primary-default)]',
111
+ primary:
112
+ 'bg-btn-primary text-btn-primary shadow-[0px_2px_1px_0px_rgba(255,255,255,0.1)_inset,0px_-2px_1px_0px_rgba(0,0,0,0.2)_inset] hover:bg-btn-primary-hover hover:text-btn-primary-hover hover:shadow-[0px_2px_1px_0px_rgba(255,255,255,0.08)_inset,0px_-2px_1px_0px_rgba(0,0,0,0.25)_inset] active:bg-btn-primary-active active:text-btn-primary-active active:shadow-none disabled:bg-btn-primary-disabled disabled:text-btn-primary-disabled',
113
+ secondary:
114
+ 'bg-btn-secondary text-btn-secondary shadow-[0px_2px_1px_0px_rgba(255,255,255,0.25)_inset,0px_-2px_1px_0px_rgba(0,0,0,0.1)_inset] hover:bg-btn-secondary-hover hover:text-btn-secondary-hover hover:shadow-[0px_2px_1px_0px_rgba(255,255,255,0.2)_inset,0px_-2px_1px_0px_rgba(0,0,0,0.15)_inset] active:bg-btn-secondary-active active:text-btn-secondary-active active:shadow-none disabled:bg-btn-secondary-disabled disabled:text-btn-secondary-disabled',
115
+ tertiary:
116
+ 'bg-transparent text-btn-tertiary hover:bg-btn-secondary-hover hover:text-btn-tertiary-hover active:bg-btn-secondary-active active:text-btn-tertiary-active disabled:text-btn-tertiary-disabled',
117
+ 'destructive-primary':
118
+ 'bg-btn-destructive text-btn-destructive-primary shadow-[0px_2px_1px_0px_rgba(255,255,255,0.1)_inset,0px_-2px_1px_0px_rgba(0,0,0,0.2)_inset] hover:bg-btn-destructive-hover hover:text-btn-destructive-primary-hover hover:shadow-[0px_2px_1px_0px_rgba(255,255,255,0.08)_inset,0px_-2px_1px_0px_rgba(0,0,0,0.25)_inset] active:bg-btn-destructive-active active:text-btn-destructive-primary-active active:shadow-none disabled:bg-btn-destructive-disabled disabled:text-btn-destructive-primary-disabled',
119
+ 'destructive-secondary':
120
+ 'bg-transparent text-btn-destructive-secondary hover:text-btn-destructive-secondary-hover active:text-btn-destructive-secondary-active disabled:text-btn-destructive-secondary-disabled',
121
+ },
122
+ size: {
123
+ xs: 'h-7 px-2 py-1 text-xs gap-1 [&_svg]:size-3',
124
+ sm: 'h-8 px-3 py-2 text-sm gap-1.5 [&_svg]:size-3.5',
125
+ md: 'h-9 px-4 py-2 gap-2 [&_svg]:size-4',
126
+ lg: 'h-10 px-6 py-2 text-lg gap-2.5 [&_svg]:size-5',
127
+ },
128
+ },
129
+ compoundVariants: [
130
+ {
131
+ variant: 'brand',
132
+ className:
133
+ 'before:inset-[-1px] active:before:inset-[-2px] after:inset-[-1px] active:after:inset-[-2px]',
134
+ },
135
+ {
136
+ context: 'product',
137
+ variant: 'brand',
138
+ className:
139
+ 'before:rounded-[calc(theme(borderRadius.sm)+1px)] active:before:rounded-[calc(theme(borderRadius.sm)+2px)] after:rounded-[calc(theme(borderRadius.sm)+1px)] active:after:rounded-[calc(theme(borderRadius.sm)+2px)]',
140
+ },
141
+ {
142
+ context: 'marketing',
143
+ variant: 'brand',
144
+ className: 'before:rounded-full after:rounded-full',
145
+ },
146
+ ],
147
+ defaultVariants: {
148
+ context: 'product',
149
+ variant: 'primary',
150
+ size: 'md',
151
+ },
152
+ }
153
+ )
154
+
155
+ type Attributes = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'style'>
156
+
157
+ export interface ButtonProps extends Attributes {
158
+ asChild?: boolean
159
+ variant?: ButtonVariant
160
+ size?: ButtonSize
161
+ context?: ButtonContext
162
+ className?: string
163
+ 'aria-label'?: string
164
+ }
165
+
166
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
167
+ (
168
+ {
169
+ variant = 'primary',
170
+ size = 'md',
171
+ context = 'product',
172
+ asChild = false,
173
+ className,
174
+ onMouseEnter,
175
+ onMouseLeave,
176
+ onMouseDown,
177
+ onMouseUp,
178
+ onMouseMove,
179
+ ...props
180
+ },
181
+ ref
182
+ ) => {
183
+ const [cursorPosition, setCursorPosition] = React.useState<{
184
+ x: number
185
+ y: number
186
+ } | null>(null)
187
+ const buttonRef = React.useRef<HTMLButtonElement | null>(null)
188
+ const buttonDimensionsRef = React.useRef<{
189
+ width: number
190
+ height: number
191
+ } | null>(null)
192
+
193
+ if (process.env.NODE_ENV === 'development' && !asChild) {
194
+ const validateChildren = () => {
195
+ if (!props.children) return
196
+
197
+ const childArray = React.Children.toArray(props.children)
198
+ const hasButtonText = childArray.some(
199
+ (child) =>
200
+ (typeof child === 'string' && child.trim().length > 0) ||
201
+ typeof child === 'number' ||
202
+ (React.isValidElement(child) &&
203
+ (child.type === ButtonText ||
204
+ (child.type as { displayName?: string })?.displayName ===
205
+ 'ButtonText'))
206
+ )
207
+ const hasAriaLabel = props['aria-label']
208
+
209
+ if (!hasButtonText && !hasAriaLabel) {
210
+ console.warn(
211
+ 'Button: Buttons should either contain Button.Text or have an aria-label for accessibility. Consider using IconButton for icon-only buttons.'
212
+ )
213
+ }
214
+
215
+ const invalidChildren = childArray.filter((child) => {
216
+ if (typeof child === 'string' || typeof child === 'number') {
217
+ return false // Raw text is OK - we'll auto-wrap it
218
+ }
219
+ if (React.isValidElement(child)) {
220
+ const displayName = (child.type as { displayName?: string })
221
+ ?.displayName
222
+ return ![
223
+ 'ButtonText',
224
+ 'ButtonLeftIcon',
225
+ 'ButtonRightIcon',
226
+ 'ButtonIcon',
227
+ ].includes(displayName || '')
228
+ }
229
+ return true // Other types are invalid
230
+ })
231
+
232
+ if (invalidChildren.length > 0) {
233
+ console.warn(
234
+ 'Button: Only Button.Text, Button.LeftIcon, Button.RightIcon, and raw text should be used as children.'
235
+ )
236
+ }
237
+ }
238
+
239
+ validateChildren()
240
+ }
241
+
242
+ const gradientRefs = React.useRef<{
243
+ target: number
244
+ current: number
245
+ }>({
246
+ target: 220,
247
+ current: 220,
248
+ })
249
+
250
+ const isBrandVariant = variant === 'brand'
251
+
252
+ // Get gap class for the current size
253
+ const getGapClass = (size: ButtonSize) => {
254
+ switch (size) {
255
+ case 'xs':
256
+ return 'gap-1'
257
+ case 'sm':
258
+ return 'gap-1.5'
259
+ case 'md':
260
+ return 'gap-2'
261
+ case 'lg':
262
+ return 'gap-2.5'
263
+ default:
264
+ return 'gap-2'
265
+ }
266
+ }
267
+
268
+ // Only run animation frame when brand variant is active
269
+ useAnimationFrame(
270
+ React.useCallback(
271
+ (_timestamp: number, delta: number) => {
272
+ if (!buttonRef.current) return
273
+
274
+ const gradientValues = gradientRefs.current
275
+
276
+ // Calculate target based on cursor position if hovering
277
+ if (cursorPosition && buttonDimensionsRef.current) {
278
+ const centerX = buttonDimensionsRef.current.width / 2
279
+ const centerY = buttonDimensionsRef.current.height / 2
280
+
281
+ // Calculate angle from center to cursor position
282
+ const angle =
283
+ Math.atan2(
284
+ cursorPosition.y - centerY,
285
+ cursorPosition.x - centerX
286
+ ) *
287
+ (180 / Math.PI)
288
+
289
+ // Normalize angle to 0-360 range and add offset
290
+ gradientValues.target = (angle + 360) % 360
291
+ } else {
292
+ // Always reset to original position when not hovering
293
+ gradientValues.target = 220
294
+ }
295
+
296
+ // Early exit if we're close enough to target
297
+ const diff = Math.abs(gradientValues.target - gradientValues.current)
298
+ if (diff < 0.1) {
299
+ if (diff > 0) {
300
+ gradientValues.current = gradientValues.target
301
+ buttonRef.current.style.setProperty(
302
+ '--gradient-rotation',
303
+ `${Math.round(gradientValues.target)}deg`
304
+ )
305
+ }
306
+ return
307
+ }
308
+
309
+ const lerpValue = Math.min((delta / 1000) * 8, 1) // Increased speed for snappier response
310
+
311
+ const newCurrent = lerpAngle(
312
+ gradientValues.current,
313
+ gradientValues.target,
314
+ lerpValue
315
+ )
316
+ gradientValues.current = newCurrent
317
+
318
+ buttonRef.current.style.setProperty(
319
+ '--gradient-rotation',
320
+ `${Math.round(newCurrent)}deg`
321
+ )
322
+ },
323
+ [cursorPosition]
324
+ ),
325
+ isBrandVariant // Only enable animation for brand variant
326
+ )
327
+
328
+ if (process.env.NODE_ENV === 'development') {
329
+ const deprecatedVariants = {
330
+ default: 'primary',
331
+ destructive: 'destructive-primary',
332
+ outline: 'secondary',
333
+ ghost: 'tertiary',
334
+ link: 'a semantic <a> tag',
335
+ }
336
+
337
+ if (variant && variant in deprecatedVariants) {
338
+ console.warn(
339
+ `Button: The variant "${variant}" is deprecated. Please use "${deprecatedVariants[variant as keyof typeof deprecatedVariants]}" instead.`
340
+ )
341
+ }
342
+ }
343
+
344
+ const handleMouseMove = React.useCallback(
345
+ (e: React.MouseEvent<HTMLButtonElement>) => {
346
+ if (isBrandVariant && buttonRef.current) {
347
+ const rect = buttonRef.current.getBoundingClientRect()
348
+ const x = e.clientX - rect.left
349
+ const y = e.clientY - rect.top
350
+ setCursorPosition({ x, y })
351
+ }
352
+ onMouseMove?.(e)
353
+ },
354
+ [isBrandVariant, onMouseMove]
355
+ )
356
+
357
+ const handleMouseEnter = React.useCallback(
358
+ (e: React.MouseEvent<HTMLButtonElement>) => {
359
+ if (isBrandVariant && buttonRef.current) {
360
+ const rect = buttonRef.current.getBoundingClientRect()
361
+ // Cache dimensions for animation frame
362
+ buttonDimensionsRef.current = {
363
+ width: rect.width,
364
+ height: rect.height,
365
+ }
366
+ const x = e.clientX - rect.left
367
+ const y = e.clientY - rect.top
368
+ setCursorPosition({ x, y })
369
+ }
370
+ onMouseEnter?.(e)
371
+ },
372
+ [isBrandVariant, onMouseEnter]
373
+ )
374
+
375
+ const handleMouseLeave = React.useCallback(
376
+ (e: React.MouseEvent<HTMLButtonElement>) => {
377
+ if (isBrandVariant) {
378
+ setCursorPosition(null)
379
+ buttonDimensionsRef.current = null
380
+ // Explicitly set target to ensure reset
381
+ gradientRefs.current.target = 220
382
+ }
383
+ onMouseLeave?.(e)
384
+ },
385
+ [isBrandVariant, onMouseLeave]
386
+ )
387
+
388
+ const handleMouseDown = React.useCallback(
389
+ (e: React.MouseEvent<HTMLButtonElement>) => {
390
+ onMouseDown?.(e)
391
+ },
392
+ [onMouseDown]
393
+ )
394
+
395
+ const handleMouseUp = React.useCallback(
396
+ (e: React.MouseEvent<HTMLButtonElement>) => {
397
+ onMouseUp?.(e)
398
+ },
399
+ [onMouseUp]
400
+ )
401
+
402
+ React.useEffect(() => {
403
+ if (isBrandVariant && buttonRef.current) {
404
+ buttonRef.current.style.setProperty('--gradient-rotation', '220deg')
405
+ Object.assign(gradientRefs.current, { target: 220, current: 220 })
406
+ }
407
+ }, [isBrandVariant])
408
+
409
+ // Custom Slot wrapper for brand variant
410
+ const BrandSlot = React.forwardRef<
411
+ HTMLElement,
412
+ React.ComponentProps<typeof Slot>
413
+ >((slotProps, slotRef) => {
414
+ if (!isBrandVariant) {
415
+ return <Slot {...slotProps} ref={slotRef} />
416
+ }
417
+
418
+ // For brand variant, we need to inject the brand span
419
+ const child = React.Children.only(slotProps.children)
420
+ if (!React.isValidElement(child)) {
421
+ return <Slot {...slotProps} ref={slotRef} />
422
+ }
423
+
424
+ // Create brand span
425
+ const brandSpan = (
426
+ <span
427
+ key="brand-bg"
428
+ className="bg-btn-brand hover:bg-btn-brand-hover disabled:bg-btn-brand-disabled pointer-events-none absolute inset-0 -z-10 rounded-[inherit]"
429
+ />
430
+ )
431
+
432
+ // Clone child with brand span injected
433
+ const childWithBrand = React.cloneElement(
434
+ child as React.ReactElement<React.ComponentProps<typeof Slot>>,
435
+ {
436
+ ...child.props,
437
+ children: (child.props as React.ComponentProps<typeof Slot>).children
438
+ ? [
439
+ brandSpan,
440
+ (child.props as React.ComponentProps<typeof Slot>).children,
441
+ ]
442
+ : brandSpan,
443
+ }
444
+ )
445
+
446
+ return (
447
+ <Slot {...slotProps} ref={slotRef}>
448
+ {childWithBrand}
449
+ </Slot>
450
+ )
451
+ })
452
+ BrandSlot.displayName = 'BrandSlot'
453
+
454
+ const Comp = asChild ? BrandSlot : 'button'
455
+ const combinedRef = React.useCallback(
456
+ (node: HTMLButtonElement | null) => {
457
+ buttonRef.current = node
458
+ if (typeof ref === 'function') {
459
+ ref(node)
460
+ } else if (ref && node) {
461
+ ;(ref as React.MutableRefObject<HTMLButtonElement | null>).current =
462
+ node
463
+ }
464
+ },
465
+ [ref]
466
+ )
467
+
468
+ // Auto-wrap raw text children in Button.Text (only when not using asChild)
469
+ const processedChildren = React.useMemo(() => {
470
+ if (asChild) {
471
+ // When asChild is true, return children as-is - BrandSlot will handle brand logic
472
+ return props.children
473
+ }
474
+
475
+ return React.Children.map(props.children, (child) => {
476
+ if (typeof child === 'string' || typeof child === 'number') {
477
+ return <ButtonText>{child}</ButtonText>
478
+ }
479
+ return child
480
+ })
481
+ }, [props.children, asChild])
482
+
483
+ return (
484
+ <Comp
485
+ className={cn(buttonVariants({ variant, size, context }), className)}
486
+ ref={combinedRef}
487
+ onMouseEnter={handleMouseEnter}
488
+ onMouseLeave={handleMouseLeave}
489
+ onMouseMove={handleMouseMove}
490
+ onMouseDown={handleMouseDown}
491
+ onMouseUp={handleMouseUp}
492
+ {...props}
493
+ >
494
+ {asChild ? (
495
+ processedChildren
496
+ ) : (
497
+ <>
498
+ {isBrandVariant && (
499
+ <span className="bg-btn-brand hover:bg-btn-brand-hover disabled:bg-btn-brand-disabled pointer-events-none absolute inset-0 z-10 rounded-[inherit]" />
500
+ )}
501
+ <span
502
+ className={cn(
503
+ 'relative flex items-center justify-center',
504
+ getGapClass(size),
505
+ isBrandVariant ? `z-20` : ''
506
+ )}
507
+ >
508
+ {processedChildren}
509
+ </span>
510
+ </>
511
+ )}
512
+ </Comp>
513
+ )
514
+ }
515
+ )
516
+ Button.displayName = 'Button'
517
+
518
+ const ButtonWithCompounds = Object.assign(Button, {
519
+ Icon: ButtonIcon,
520
+ LeftIcon: ButtonLeftIcon,
521
+ RightIcon: ButtonRightIcon,
522
+ Text: ButtonText,
523
+ })
524
+
525
+ export {
526
+ ButtonWithCompounds as Button,
527
+ ButtonIcon,
528
+ ButtonLeftIcon,
529
+ ButtonRightIcon,
530
+ ButtonText,
531
+ }