@pixel-point/toolcraft 0.0.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 (257) hide show
  1. package/LICENSE.md +98 -0
  2. package/README.md +41 -0
  3. package/bin/create-toolcraft-app.mjs +8 -0
  4. package/bin/toolcraft.mjs +8 -0
  5. package/package.json +24 -0
  6. package/scripts/prepare-pack.mjs +29 -0
  7. package/src/cli.mjs +392 -0
  8. package/src/cli.test.mjs +284 -0
  9. package/src/copy-recursive.mjs +86 -0
  10. package/src/generate.mjs +212 -0
  11. package/src/generate.test.mjs +322 -0
  12. package/src/import-map.mjs +14 -0
  13. package/src/package-json.mjs +80 -0
  14. package/src/package-json.test.mjs +67 -0
  15. package/src/rewrite-imports.mjs +85 -0
  16. package/src/rewrite-imports.test.mjs +58 -0
  17. package/templates/runtime/contracts/component-contracts.test.ts +1165 -0
  18. package/templates/runtime/contracts/component-contracts.ts +1340 -0
  19. package/templates/runtime/contracts/decision-contracts.test.ts +206 -0
  20. package/templates/runtime/contracts/decision-contracts.ts +283 -0
  21. package/templates/runtime/contracts/index.test.ts +14 -0
  22. package/templates/runtime/contracts/index.ts +3 -0
  23. package/templates/runtime/contracts/types.ts +56 -0
  24. package/templates/runtime/export/export.test.ts +203 -0
  25. package/templates/runtime/export/export.ts +132 -0
  26. package/templates/runtime/export/index.ts +1 -0
  27. package/templates/runtime/index.ts +14 -0
  28. package/templates/runtime/react/canvas-shell.test.tsx +424 -0
  29. package/templates/runtime/react/canvas-shell.tsx +408 -0
  30. package/templates/runtime/react/control-renderers.ts +31 -0
  31. package/templates/runtime/react/controls-panel.test.tsx +3736 -0
  32. package/templates/runtime/react/controls-panel.tsx +2327 -0
  33. package/templates/runtime/react/curve-geometry.test.ts +70 -0
  34. package/templates/runtime/react/index.ts +15 -0
  35. package/templates/runtime/react/layer-tree.ts +96 -0
  36. package/templates/runtime/react/layers-panel.test.tsx +487 -0
  37. package/templates/runtime/react/layers-panel.tsx +1348 -0
  38. package/templates/runtime/react/media-file.ts +82 -0
  39. package/templates/runtime/react/panel-host-config.ts +80 -0
  40. package/templates/runtime/react/panel-host-geometry.test.ts +66 -0
  41. package/templates/runtime/react/panel-host-geometry.ts +109 -0
  42. package/templates/runtime/react/panel-host-types.ts +74 -0
  43. package/templates/runtime/react/panel-host.test.tsx +102 -0
  44. package/templates/runtime/react/panel-host.tsx +353 -0
  45. package/templates/runtime/react/runtime-public-api.test.tsx +132 -0
  46. package/templates/runtime/react/settings-transfer.test.ts +150 -0
  47. package/templates/runtime/react/settings-transfer.ts +279 -0
  48. package/templates/runtime/react/storage-key-migration.ts +48 -0
  49. package/templates/runtime/react/theme-runtime.tsx +177 -0
  50. package/templates/runtime/react/timeline-panel.test.tsx +668 -0
  51. package/templates/runtime/react/timeline-panel.tsx +2953 -0
  52. package/templates/runtime/react/toolbar-panel.test.tsx +212 -0
  53. package/templates/runtime/react/toolbar-panel.tsx +205 -0
  54. package/templates/runtime/react/toolcraft-app.integration.test.tsx +350 -0
  55. package/templates/runtime/react/toolcraft-app.test.tsx +339 -0
  56. package/templates/runtime/react/toolcraft-app.tsx +81 -0
  57. package/templates/runtime/react/toolcraft-root.test.tsx +347 -0
  58. package/templates/runtime/react/toolcraft-root.tsx +203 -0
  59. package/templates/runtime/react/use-toolcraft.ts +41 -0
  60. package/templates/runtime/schema/define-toolcraft.test.ts +1524 -0
  61. package/templates/runtime/schema/define-toolcraft.ts +1442 -0
  62. package/templates/runtime/schema/keyframe-capability.test.ts +90 -0
  63. package/templates/runtime/schema/keyframe-capability.ts +51 -0
  64. package/templates/runtime/schema/runtime-targets.ts +40 -0
  65. package/templates/runtime/schema/types.ts +370 -0
  66. package/templates/runtime/state/canvas-zoom.ts +8 -0
  67. package/templates/runtime/state/create-template-state.test.ts +242 -0
  68. package/templates/runtime/state/create-template-state.ts +95 -0
  69. package/templates/runtime/state/keyframe-evaluation.test.ts +141 -0
  70. package/templates/runtime/state/keyframe-evaluation.ts +203 -0
  71. package/templates/runtime/state/persistence.test.ts +217 -0
  72. package/templates/runtime/state/persistence.ts +511 -0
  73. package/templates/runtime/state/reducer.test.ts +937 -0
  74. package/templates/runtime/state/reducer.ts +1212 -0
  75. package/templates/runtime/state/timeline-readiness.ts +43 -0
  76. package/templates/runtime/state/types.ts +242 -0
  77. package/templates/runtime/styles.css +125 -0
  78. package/templates/runtime/testing/performance.test.ts +1058 -0
  79. package/templates/runtime/testing/performance.ts +1078 -0
  80. package/templates/starter/AGENTS.md +186 -0
  81. package/templates/starter/LICENSE.md +98 -0
  82. package/templates/starter/NOTICE.md +8 -0
  83. package/templates/starter/docs/toolcraft/README.md +41 -0
  84. package/templates/starter/docs/toolcraft/acceptance-testing.md +205 -0
  85. package/templates/starter/docs/toolcraft/agent-worklog.md +81 -0
  86. package/templates/starter/docs/toolcraft/assembly-workflow.md +206 -0
  87. package/templates/starter/docs/toolcraft/component-rules.md +299 -0
  88. package/templates/starter/docs/toolcraft/custom-controls.md +71 -0
  89. package/templates/starter/docs/toolcraft/decision-contract.md +71 -0
  90. package/templates/starter/docs/toolcraft/performance.md +112 -0
  91. package/templates/starter/docs/toolcraft/renderer-technique.md +48 -0
  92. package/templates/starter/docs/toolcraft/schema-reference.md +265 -0
  93. package/templates/starter/docs/toolcraft/workflow.md +87 -0
  94. package/templates/starter/e2e/app-browser-acceptance.spec.ts +785 -0
  95. package/templates/starter/e2e/app-controls.spec.ts +41 -0
  96. package/templates/starter/e2e/app-performance.spec.ts +326 -0
  97. package/templates/starter/e2e/canvas-handle-helpers.ts +244 -0
  98. package/templates/starter/e2e/performance-helpers.ts +612 -0
  99. package/templates/starter/e2e/product-observable-helpers.ts +170 -0
  100. package/templates/starter/index.html +12 -0
  101. package/templates/starter/package.json +52 -0
  102. package/templates/starter/playwright.config.ts +43 -0
  103. package/templates/starter/scripts/check-ai-skills.mjs +95 -0
  104. package/templates/starter/scripts/check-toolcraft-docs.mjs +159 -0
  105. package/templates/starter/scripts/check-toolcraft-integrity.mjs +232 -0
  106. package/templates/starter/scripts/run-vite-on-free-port.mjs +48 -0
  107. package/templates/starter/scripts/toolcraft-port.mjs +54 -0
  108. package/templates/starter/scripts/toolcraft-port.test.mjs +73 -0
  109. package/templates/starter/src/app/starter-acceptance.test.ts +5959 -0
  110. package/templates/starter/src/app/starter-acceptance.ts +2646 -0
  111. package/templates/starter/src/app/starter-performance.test.ts +1390 -0
  112. package/templates/starter/src/app/starter-performance.ts +12 -0
  113. package/templates/starter/src/app/starter-schema.test.ts +70 -0
  114. package/templates/starter/src/app/starter-schema.ts +15 -0
  115. package/templates/starter/src/main.tsx +18 -0
  116. package/templates/starter/src/router.tsx +16 -0
  117. package/templates/starter/src/routes/index.tsx +7 -0
  118. package/templates/starter/src/routes/root.tsx +19 -0
  119. package/templates/starter/src/styles.css +120 -0
  120. package/templates/starter/tsconfig.json +11 -0
  121. package/templates/starter/vite.config.ts +13 -0
  122. package/templates/ui/components/composites/accordion.tsx +73 -0
  123. package/templates/ui/components/composites/alert-dialog.tsx +190 -0
  124. package/templates/ui/components/composites/alert.tsx +74 -0
  125. package/templates/ui/components/composites/aspect-ratio.tsx +22 -0
  126. package/templates/ui/components/composites/avatar.tsx +98 -0
  127. package/templates/ui/components/composites/badge.tsx +69 -0
  128. package/templates/ui/components/composites/breadcrumb.tsx +106 -0
  129. package/templates/ui/components/composites/card.tsx +91 -0
  130. package/templates/ui/components/composites/combobox.tsx +486 -0
  131. package/templates/ui/components/composites/command.tsx +296 -0
  132. package/templates/ui/components/composites/context-menu.tsx +247 -0
  133. package/templates/ui/components/composites/dialog.tsx +282 -0
  134. package/templates/ui/components/composites/dropdown-menu.tsx +299 -0
  135. package/templates/ui/components/composites/empty.tsx +110 -0
  136. package/templates/ui/components/composites/hover-card.tsx +44 -0
  137. package/templates/ui/components/composites/index.ts +30 -0
  138. package/templates/ui/components/composites/menubar.tsx +214 -0
  139. package/templates/ui/components/composites/navigation-menu.tsx +167 -0
  140. package/templates/ui/components/composites/pagination.tsx +131 -0
  141. package/templates/ui/components/composites/progress.tsx +72 -0
  142. package/templates/ui/components/composites/radio-group.tsx +84 -0
  143. package/templates/ui/components/composites/resizable.tsx +42 -0
  144. package/templates/ui/components/composites/sheet.tsx +153 -0
  145. package/templates/ui/components/composites/sidebar-structural.tsx +310 -0
  146. package/templates/ui/components/composites/sidebar.tsx +431 -0
  147. package/templates/ui/components/composites/sonner.tsx +35 -0
  148. package/templates/ui/components/composites/spinner.tsx +43 -0
  149. package/templates/ui/components/composites/table.tsx +108 -0
  150. package/templates/ui/components/composites/tabs.tsx +83 -0
  151. package/templates/ui/components/control-layout/index.tsx +437 -0
  152. package/templates/ui/components/controls/actions/actions-control.tsx +139 -0
  153. package/templates/ui/components/controls/actions/index.ts +9 -0
  154. package/templates/ui/components/controls/anchor-grid/anchor-grid-control.tsx +107 -0
  155. package/templates/ui/components/controls/anchor-grid/index.ts +4 -0
  156. package/templates/ui/components/controls/boolean/boolean-controls.tsx +79 -0
  157. package/templates/ui/components/controls/boolean/index.ts +4 -0
  158. package/templates/ui/components/controls/channel-mixer/channel-mixer-control.tsx +95 -0
  159. package/templates/ui/components/controls/channel-mixer/index.ts +4 -0
  160. package/templates/ui/components/controls/channel-tabs/channel-tabs.tsx +42 -0
  161. package/templates/ui/components/controls/channel-tabs/index.ts +6 -0
  162. package/templates/ui/components/controls/code-textarea/code-textarea-control.tsx +90 -0
  163. package/templates/ui/components/controls/code-textarea/index.ts +4 -0
  164. package/templates/ui/components/controls/color/color-control.tsx +571 -0
  165. package/templates/ui/components/controls/color/color-picker-popover.tsx +104 -0
  166. package/templates/ui/components/controls/color/index.ts +41 -0
  167. package/templates/ui/components/controls/color/palette-control-data.ts +436 -0
  168. package/templates/ui/components/controls/color/palette-control.tsx +535 -0
  169. package/templates/ui/components/controls/color/style-guide-color-picker-channel-utils.ts +162 -0
  170. package/templates/ui/components/controls/color/style-guide-color-picker-interactions.ts +190 -0
  171. package/templates/ui/components/controls/color/style-guide-color-picker-logic.ts +485 -0
  172. package/templates/ui/components/controls/color/style-guide-color-picker-parts.tsx +710 -0
  173. package/templates/ui/components/controls/color/style-guide-color-picker.tsx +503 -0
  174. package/templates/ui/components/controls/control-types.ts +43 -0
  175. package/templates/ui/components/controls/curves/curve-geometry.ts +355 -0
  176. package/templates/ui/components/controls/curves/curve-graph.tsx +390 -0
  177. package/templates/ui/components/controls/curves/curves-control.tsx +445 -0
  178. package/templates/ui/components/controls/curves/index.ts +6 -0
  179. package/templates/ui/components/controls/file-drop/file-drop-control.tsx +191 -0
  180. package/templates/ui/components/controls/file-drop/index.ts +5 -0
  181. package/templates/ui/components/controls/font-picker/font-catalog.json +15360 -0
  182. package/templates/ui/components/controls/font-picker/font-catalog.ts +116 -0
  183. package/templates/ui/components/controls/font-picker/font-picker-control.tsx +1202 -0
  184. package/templates/ui/components/controls/font-picker/font-preview-loader.ts +336 -0
  185. package/templates/ui/components/controls/font-picker/index.ts +24 -0
  186. package/templates/ui/components/controls/font-picker/use-hover-intent.ts +46 -0
  187. package/templates/ui/components/controls/gradient/gradient-control-utils.ts +190 -0
  188. package/templates/ui/components/controls/gradient/gradient-control.tsx +612 -0
  189. package/templates/ui/components/controls/gradient/gradient-stop-list.tsx +400 -0
  190. package/templates/ui/components/controls/gradient/gradient-toolbar.tsx +152 -0
  191. package/templates/ui/components/controls/gradient/index.ts +4 -0
  192. package/templates/ui/components/controls/image-picker/image-picker-control.tsx +139 -0
  193. package/templates/ui/components/controls/image-picker/index.ts +7 -0
  194. package/templates/ui/components/controls/index.ts +192 -0
  195. package/templates/ui/components/controls/range-input/index.ts +4 -0
  196. package/templates/ui/components/controls/range-input/range-input-control.tsx +173 -0
  197. package/templates/ui/components/controls/range-slider/index.ts +4 -0
  198. package/templates/ui/components/controls/range-slider/range-slider-control.tsx +122 -0
  199. package/templates/ui/components/controls/range-slider/range-slider-value.ts +61 -0
  200. package/templates/ui/components/controls/segmented/index.ts +8 -0
  201. package/templates/ui/components/controls/segmented/segmented-control.tsx +94 -0
  202. package/templates/ui/components/controls/select/index.ts +4 -0
  203. package/templates/ui/components/controls/select/select-control.tsx +223 -0
  204. package/templates/ui/components/controls/slider/index.ts +4 -0
  205. package/templates/ui/components/controls/slider/slider-control.tsx +150 -0
  206. package/templates/ui/components/controls/slider/slider-value.ts +56 -0
  207. package/templates/ui/components/controls/text-input/index.ts +4 -0
  208. package/templates/ui/components/controls/text-input/text-input-control.tsx +158 -0
  209. package/templates/ui/components/controls/use-measured-element-width.ts +42 -0
  210. package/templates/ui/components/controls/vector/index.ts +8 -0
  211. package/templates/ui/components/controls/vector/vector-control.tsx +401 -0
  212. package/templates/ui/components/panel/index.ts +19 -0
  213. package/templates/ui/components/panel/panel-actions.tsx +165 -0
  214. package/templates/ui/components/panel/panel-header.tsx +61 -0
  215. package/templates/ui/components/panel/panel-icon-button.tsx +96 -0
  216. package/templates/ui/components/panel/panel-section.tsx +168 -0
  217. package/templates/ui/components/panel/panel-surface.tsx +206 -0
  218. package/templates/ui/components/panel/panel.tsx +210 -0
  219. package/templates/ui/components/primitives/animated-loader.tsx +61 -0
  220. package/templates/ui/components/primitives/button-group.tsx +134 -0
  221. package/templates/ui/components/primitives/button.tsx +429 -0
  222. package/templates/ui/components/primitives/checkbox.tsx +62 -0
  223. package/templates/ui/components/primitives/editable-slider-value-label.tsx +337 -0
  224. package/templates/ui/components/primitives/field.tsx +225 -0
  225. package/templates/ui/components/primitives/index.ts +82 -0
  226. package/templates/ui/components/primitives/input-group.tsx +298 -0
  227. package/templates/ui/components/primitives/input.tsx +61 -0
  228. package/templates/ui/components/primitives/internal/button-loading.tsx +178 -0
  229. package/templates/ui/components/primitives/label.tsx +16 -0
  230. package/templates/ui/components/primitives/popover.tsx +126 -0
  231. package/templates/ui/components/primitives/portal-layer-context.tsx +33 -0
  232. package/templates/ui/components/primitives/primitive-arrow-icon.tsx +38 -0
  233. package/templates/ui/components/primitives/scroll-fade-logic.ts +441 -0
  234. package/templates/ui/components/primitives/scroll-fade-render.tsx +75 -0
  235. package/templates/ui/components/primitives/scroll-fade-types.ts +41 -0
  236. package/templates/ui/components/primitives/scroll-fade.tsx +72 -0
  237. package/templates/ui/components/primitives/select.tsx +408 -0
  238. package/templates/ui/components/primitives/selection-state.ts +31 -0
  239. package/templates/ui/components/primitives/separator.tsx +21 -0
  240. package/templates/ui/components/primitives/slider/index.ts +4 -0
  241. package/templates/ui/components/primitives/slider/slider-interaction.tsx +96 -0
  242. package/templates/ui/components/primitives/slider/slider-parts.tsx +303 -0
  243. package/templates/ui/components/primitives/slider/slider-reset.ts +152 -0
  244. package/templates/ui/components/primitives/slider/slider-value.ts +114 -0
  245. package/templates/ui/components/primitives/slider/slider.tsx +511 -0
  246. package/templates/ui/components/primitives/switch.tsx +35 -0
  247. package/templates/ui/components/primitives/textarea.tsx +49 -0
  248. package/templates/ui/components/primitives/toggle-group.tsx +114 -0
  249. package/templates/ui/components/primitives/toggle.tsx +46 -0
  250. package/templates/ui/components/primitives/tooltip.tsx +100 -0
  251. package/templates/ui/hooks/use-mobile.ts +21 -0
  252. package/templates/ui/index.ts +31 -0
  253. package/templates/ui/lib/control-outline.ts +3 -0
  254. package/templates/ui/lib/input-control-style.ts +131 -0
  255. package/templates/ui/lib/style-guide-color-utils.ts +111 -0
  256. package/templates/ui/lib/utils.ts +6 -0
  257. package/templates/ui/styles.css +291 -0
@@ -0,0 +1,61 @@
1
+ import type { CSSProperties } from "react";
2
+
3
+ import { cn } from "../../lib/utils";
4
+
5
+ export type LoaderSize = number | string;
6
+
7
+ export type AnimatedLoaderProps = {
8
+ className?: string;
9
+ height?: LoaderSize;
10
+ indicatorClassName?: string;
11
+ insetX?: number;
12
+ width?: LoaderSize;
13
+ };
14
+
15
+ export const DEFAULT_ANIMATED_LOADER_WIDTH = 40;
16
+ export const DEFAULT_ANIMATED_LOADER_HEIGHT = 6;
17
+ export const MAX_ANIMATED_LOADER_WIDTH = 64;
18
+
19
+ export function toCssSize(value: LoaderSize): string {
20
+ return typeof value === "number" ? `${value}px` : value;
21
+ }
22
+
23
+ export function resolveAnimatedLoaderWidthStyle(width: LoaderSize, insetX?: number): string {
24
+ const resolvedWidth = toCssSize(width);
25
+ const constrainedByMaxWidth = `min(${resolvedWidth}, ${MAX_ANIMATED_LOADER_WIDTH}px)`;
26
+
27
+ return typeof insetX === "number" && insetX > 0
28
+ ? `min(${constrainedByMaxWidth}, max(0px, calc(100% - ${insetX * 2}px)))`
29
+ : constrainedByMaxWidth;
30
+ }
31
+
32
+ export function AnimatedLoader({
33
+ className,
34
+ height = DEFAULT_ANIMATED_LOADER_HEIGHT,
35
+ indicatorClassName,
36
+ insetX,
37
+ width = DEFAULT_ANIMATED_LOADER_WIDTH,
38
+ }: AnimatedLoaderProps): React.JSX.Element {
39
+ const style: CSSProperties = {
40
+ height: toCssSize(height),
41
+ width: resolveAnimatedLoaderWidthStyle(width, insetX),
42
+ };
43
+
44
+ return (
45
+ <span
46
+ aria-hidden="true"
47
+ className={cn("relative shrink-0", className)}
48
+ data-inset-x={typeof insetX === "number" ? String(insetX) : undefined}
49
+ data-slot="animated-loader"
50
+ style={style}
51
+ >
52
+ <span
53
+ className={cn(
54
+ "button-loader-indicator absolute inset-y-0 left-0 right-[65%] rounded-full bg-[color:var(--foreground)] opacity-90",
55
+ indicatorClassName,
56
+ )}
57
+ data-slot="animated-loader-indicator"
58
+ />
59
+ </span>
60
+ );
61
+ }
@@ -0,0 +1,134 @@
1
+ import { mergeProps } from "@base-ui/react/merge-props";
2
+ import { useRender } from "@base-ui/react/use-render";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+
5
+ import { cn } from "../../lib/utils";
6
+ import { Separator } from "./separator";
7
+
8
+ const buttonGroupVariants = cva(
9
+ "flex w-fit items-stretch *:focus:relative *:focus:z-10 *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot]]:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
10
+ {
11
+ variants: {
12
+ orientation: {
13
+ horizontal:
14
+ "*:data-slot:rounded-r-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-md! [&>[data-slot]~[data-slot]]:rounded-l-none [&>[data-slot]:has(~[data-slot])]:border-r-0",
15
+ vertical:
16
+ "flex-col *:data-slot:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-md! [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]:has(~[data-slot])]:border-b-0",
17
+ },
18
+ adjacentBorderTone: {
19
+ default: null,
20
+ subtle: null,
21
+ },
22
+ },
23
+ compoundVariants: [
24
+ {
25
+ adjacentBorderTone: "default",
26
+ className:
27
+ "[&>[data-slot]:not([data-slot=button-group-separator]):hover+[data-slot]]:!border-l-[color:color-mix(in_oklab,var(--border)_20%,transparent)] [&>[data-slot]:not([data-slot=button-group-separator]):focus+[data-slot]]:!border-l-[color:color-mix(in_oklab,var(--border)_30%,transparent)] [&>[data-slot]:not([data-slot=button-group-separator])[aria-expanded=true]+[data-slot]]:!border-l-[color:color-mix(in_oklab,var(--border)_45%,transparent)] [&>[data-slot]:not([data-slot=button-group-separator])[data-open]+[data-slot]]:!border-l-[color:color-mix(in_oklab,var(--border)_45%,transparent)] [&>[data-slot]:not([data-slot=button-group-separator])[data-popup-open]+[data-slot]]:!border-l-[color:color-mix(in_oklab,var(--border)_45%,transparent)]",
28
+ orientation: "horizontal",
29
+ },
30
+ {
31
+ adjacentBorderTone: "subtle",
32
+ className:
33
+ "[&>[data-slot]:not([data-slot=button-group-separator]):hover+[data-slot]]:!border-l-[color:color-mix(in_oklab,var(--border)_20%,transparent)] [&>[data-slot]:not([data-slot=button-group-separator]):focus+[data-slot]]:!border-l-[color:color-mix(in_oklab,var(--border)_30%,transparent)] [&>[data-slot]:not([data-slot=button-group-separator])[aria-expanded=true]+[data-slot]]:!border-l-[color:color-mix(in_oklab,var(--border)_30%,transparent)] [&>[data-slot]:not([data-slot=button-group-separator])[data-open]+[data-slot]]:!border-l-[color:color-mix(in_oklab,var(--border)_30%,transparent)] [&>[data-slot]:not([data-slot=button-group-separator])[data-popup-open]+[data-slot]]:!border-l-[color:color-mix(in_oklab,var(--border)_30%,transparent)]",
34
+ orientation: "horizontal",
35
+ },
36
+ {
37
+ adjacentBorderTone: "default",
38
+ className:
39
+ "[&>[data-slot]:not([data-slot=button-group-separator]):hover+[data-slot]]:!border-t-[color:color-mix(in_oklab,var(--border)_20%,transparent)] [&>[data-slot]:not([data-slot=button-group-separator]):focus+[data-slot]]:!border-t-[color:color-mix(in_oklab,var(--border)_30%,transparent)] [&>[data-slot]:not([data-slot=button-group-separator])[aria-expanded=true]+[data-slot]]:!border-t-[color:color-mix(in_oklab,var(--border)_45%,transparent)] [&>[data-slot]:not([data-slot=button-group-separator])[data-open]+[data-slot]]:!border-t-[color:color-mix(in_oklab,var(--border)_45%,transparent)] [&>[data-slot]:not([data-slot=button-group-separator])[data-popup-open]+[data-slot]]:!border-t-[color:color-mix(in_oklab,var(--border)_45%,transparent)]",
40
+ orientation: "vertical",
41
+ },
42
+ {
43
+ adjacentBorderTone: "subtle",
44
+ className:
45
+ "[&>[data-slot]:not([data-slot=button-group-separator]):hover+[data-slot]]:!border-t-[color:color-mix(in_oklab,var(--border)_20%,transparent)] [&>[data-slot]:not([data-slot=button-group-separator]):focus+[data-slot]]:!border-t-[color:color-mix(in_oklab,var(--border)_30%,transparent)] [&>[data-slot]:not([data-slot=button-group-separator])[aria-expanded=true]+[data-slot]]:!border-t-[color:color-mix(in_oklab,var(--border)_30%,transparent)] [&>[data-slot]:not([data-slot=button-group-separator])[data-open]+[data-slot]]:!border-t-[color:color-mix(in_oklab,var(--border)_30%,transparent)] [&>[data-slot]:not([data-slot=button-group-separator])[data-popup-open]+[data-slot]]:!border-t-[color:color-mix(in_oklab,var(--border)_30%,transparent)]",
46
+ orientation: "vertical",
47
+ },
48
+ ],
49
+ defaultVariants: {
50
+ adjacentBorderTone: "default",
51
+ orientation: "horizontal",
52
+ },
53
+ },
54
+ );
55
+
56
+ function ButtonGroup({
57
+ adjacentBorderTone,
58
+ className,
59
+ orientation,
60
+ ...props
61
+ }: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {
62
+ return (
63
+ <div
64
+ role="group"
65
+ data-slot="button-group"
66
+ data-orientation={orientation}
67
+ className={cn(
68
+ buttonGroupVariants({ adjacentBorderTone, orientation }),
69
+ className,
70
+ )}
71
+ {...props}
72
+ />
73
+ );
74
+ }
75
+
76
+ function ButtonGroupText({
77
+ className,
78
+ render,
79
+ ...props
80
+ }: useRender.ComponentProps<"div">) {
81
+ return useRender({
82
+ defaultTagName: "div",
83
+ props: mergeProps<"div">(
84
+ {
85
+ className: cn(
86
+ "flex items-center gap-2 rounded-md border border-[color:color-mix(in_oklab,var(--border)_12%,transparent)] bg-[color:var(--muted)] px-2.5 text-xs/relaxed font-medium [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
87
+ className,
88
+ ),
89
+ },
90
+ props,
91
+ ),
92
+ render,
93
+ state: {
94
+ slot: "button-group-text",
95
+ },
96
+ });
97
+ }
98
+
99
+ function ButtonGroupSeparator({
100
+ className,
101
+ orientation = "vertical",
102
+ ...props
103
+ }: React.ComponentProps<typeof Separator>) {
104
+ return (
105
+ <Separator
106
+ data-slot="button-group-separator"
107
+ orientation={orientation}
108
+ className={cn(
109
+ "relative self-stretch bg-[color:var(--input)] data-horizontal:mx-px data-horizontal:w-auto data-vertical:my-px data-vertical:h-auto",
110
+ className,
111
+ )}
112
+ {...props}
113
+ />
114
+ );
115
+ }
116
+
117
+ const BUTTON_GROUP_FOUNDATION_PREVIEW_TABS = [
118
+ { id: "sm", label: "SM", buttonSize: "sm", iconSize: "icon-sm" },
119
+ { id: "default", label: "Default", buttonSize: "default", iconSize: "icon" },
120
+ { id: "lg", label: "LG", buttonSize: "lg", iconSize: "icon-lg" },
121
+ { id: "xl", label: "XL", buttonSize: "xl", iconSize: "icon-xl" },
122
+ ] as const;
123
+
124
+ type ButtonGroupFoundationPreviewTabId =
125
+ (typeof BUTTON_GROUP_FOUNDATION_PREVIEW_TABS)[number]["id"];
126
+
127
+ export {
128
+ ButtonGroup,
129
+ BUTTON_GROUP_FOUNDATION_PREVIEW_TABS,
130
+ ButtonGroupSeparator,
131
+ ButtonGroupText,
132
+ buttonGroupVariants,
133
+ type ButtonGroupFoundationPreviewTabId,
134
+ };
@@ -0,0 +1,429 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Button as ButtonPrimitive } from "@base-ui/react/button";
5
+ import { cva, type VariantProps } from "class-variance-authority";
6
+
7
+ import { useButtonLoadingState } from "./internal/button-loading";
8
+ import { pressedSelectedItemClassName } from "./selection-state";
9
+ import type { LoaderSize } from "./animated-loader";
10
+ import { cn } from "../../lib/utils";
11
+
12
+ type ButtonIconWeight =
13
+ | "thin"
14
+ | "light"
15
+ | "regular"
16
+ | "bold"
17
+ | "fill"
18
+ | "duotone";
19
+ type ButtonSize = VariantProps<typeof buttonVariants>["size"];
20
+
21
+ function interactiveStateClassName(params: {
22
+ active: string;
23
+ hover: string;
24
+ persistent: string;
25
+ }): string {
26
+ const focus = params.hover.replaceAll("hover:", "focus:");
27
+
28
+ return [params.hover, focus, params.active, params.persistent].join(" ");
29
+ }
30
+
31
+ function stepUpButtonIconWeight(weight: unknown): ButtonIconWeight {
32
+ switch (weight) {
33
+ case "thin":
34
+ return "light";
35
+ case "light":
36
+ return "regular";
37
+ case "regular":
38
+ return "bold";
39
+ case "bold":
40
+ case "duotone":
41
+ case "fill":
42
+ return weight;
43
+ default:
44
+ return "bold";
45
+ }
46
+ }
47
+
48
+ function getReactElementTypeName(
49
+ type: string | React.JSXElementConstructor<unknown>,
50
+ ): string | null {
51
+ if (typeof type === "string") {
52
+ return type;
53
+ }
54
+
55
+ const componentType = type as {
56
+ displayName?: string;
57
+ name?: string;
58
+ render?:
59
+ | {
60
+ displayName?: string;
61
+ name?: string;
62
+ }
63
+ | ((...args: never[]) => unknown);
64
+ };
65
+
66
+ if (
67
+ typeof componentType.displayName === "string" &&
68
+ componentType.displayName.length > 0
69
+ ) {
70
+ return componentType.displayName;
71
+ }
72
+
73
+ if (typeof componentType.name === "string" && componentType.name.length > 0) {
74
+ return componentType.name;
75
+ }
76
+
77
+ if (typeof componentType.render === "function") {
78
+ const renderComponent = componentType.render as {
79
+ displayName?: string;
80
+ name?: string;
81
+ };
82
+ return renderComponent.displayName || renderComponent.name || null;
83
+ }
84
+
85
+ return null;
86
+ }
87
+
88
+ function isSteppableButtonIconElement(
89
+ element: React.ReactElement<{
90
+ children?: React.ReactNode;
91
+ weight?: ButtonIconWeight;
92
+ }>,
93
+ ): boolean {
94
+ const typeName = getReactElementTypeName(element.type);
95
+ return typeName?.endsWith("Icon") ?? false;
96
+ }
97
+
98
+ function shouldStepButtonIconWeight(_size: ButtonSize): boolean {
99
+ return false;
100
+ }
101
+
102
+ function withSteppedButtonIconWeight(
103
+ children: React.ReactNode,
104
+ enabled: boolean,
105
+ ): React.ReactNode {
106
+ return React.Children.map(children, (child) => {
107
+ if (
108
+ !React.isValidElement<{
109
+ children?: React.ReactNode;
110
+ weight?: ButtonIconWeight;
111
+ }>(child)
112
+ ) {
113
+ return child;
114
+ }
115
+
116
+ const nextChildren =
117
+ child.props.children === undefined
118
+ ? child.props.children
119
+ : withSteppedButtonIconWeight(child.props.children, enabled);
120
+
121
+ if (!enabled) {
122
+ return nextChildren === child.props.children
123
+ ? child
124
+ : React.cloneElement(child, undefined, nextChildren);
125
+ }
126
+
127
+ if (isSteppableButtonIconElement(child)) {
128
+ const nextWeight = stepUpButtonIconWeight(child.props.weight);
129
+
130
+ return nextChildren === child.props.children
131
+ ? React.cloneElement(child, { weight: nextWeight })
132
+ : React.cloneElement(child, { weight: nextWeight }, nextChildren);
133
+ }
134
+
135
+ return nextChildren === child.props.children
136
+ ? child
137
+ : React.cloneElement(child, undefined, nextChildren);
138
+ });
139
+ }
140
+
141
+ const buttonVariants = cva(
142
+ "group/button inline-flex shrink-0 cursor-pointer items-center justify-center border border-transparent font-medium whitespace-nowrap transition-colors outline-none select-none focus-visible:border-[color:var(--ring)] focus-visible:ring-2 focus-visible:ring-[color:color-mix(in_oklab,var(--ring)_30%,transparent)] disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-[color:var(--destructive)] aria-invalid:ring-2 aria-invalid:ring-[color:color-mix(in_oklab,var(--destructive)_20%,transparent)] dark:aria-invalid:border-[color:color-mix(in_oklab,var(--destructive)_50%,transparent)] dark:aria-invalid:ring-[color:color-mix(in_oklab,var(--destructive)_40%,transparent)] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_[data-icon]]:opacity-60 [&_[data-icon]]:transition-opacity hover:[&_[data-icon]]:opacity-100 data-[size=icon]:[&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-60 data-[size=icon]:[&_svg:not([data-slot='primitive-arrow-icon'])]:transition-opacity data-[size=icon]:hover:[&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-100 data-[size=icon-tight]:[&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-60 data-[size=icon-tight]:[&_svg:not([data-slot='primitive-arrow-icon'])]:transition-opacity data-[size=icon-tight]:hover:[&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-100 data-[size=icon-xxs]:[&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-60 data-[size=icon-xxs]:[&_svg:not([data-slot='primitive-arrow-icon'])]:transition-opacity data-[size=icon-xxs]:hover:[&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-100 data-[size=icon-xs]:[&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-60 data-[size=icon-xs]:[&_svg:not([data-slot='primitive-arrow-icon'])]:transition-opacity data-[size=icon-xs]:hover:[&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-100 data-[size=icon-sm]:[&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-60 data-[size=icon-sm]:[&_svg:not([data-slot='primitive-arrow-icon'])]:transition-opacity data-[size=icon-sm]:hover:[&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-100 data-[size=icon-lg]:[&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-60 data-[size=icon-lg]:[&_svg:not([data-slot='primitive-arrow-icon'])]:transition-opacity data-[size=icon-lg]:hover:[&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-100 data-[size=icon-xl]:[&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-60 data-[size=icon-xl]:[&_svg:not([data-slot='primitive-arrow-icon'])]:transition-opacity data-[size=icon-xl]:hover:[&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-100 data-[icon-active=true]:[&_[data-icon]]:!opacity-100 data-[icon-active=true]:[&_svg:not([data-slot='primitive-arrow-icon'])]:!opacity-100",
143
+ {
144
+ variants: {
145
+ motion: {
146
+ default: "",
147
+ static: "",
148
+ },
149
+ radius: {
150
+ default: "rounded-lg",
151
+ full: "rounded-full",
152
+ lg: "rounded-lg",
153
+ md: "rounded-md",
154
+ sm: "rounded-md",
155
+ "tab-control": "button-radius-tab-control",
156
+ xl: "rounded-xl",
157
+ },
158
+ variant: {
159
+ default: `bg-[color:var(--primary)] text-[color:var(--primary-foreground)] ${interactiveStateClassName(
160
+ {
161
+ active:
162
+ "active:bg-[color:color-mix(in_oklab,var(--primary)_82%,black)]",
163
+ hover:
164
+ "hover:bg-[color:color-mix(in_oklab,var(--primary)_88%,black)]",
165
+ persistent:
166
+ "aria-expanded:bg-[color:color-mix(in_oklab,var(--primary)_88%,black)] aria-pressed:bg-[color:color-mix(in_oklab,var(--primary)_82%,black)] data-open:bg-[color:color-mix(in_oklab,var(--primary)_88%,black)] data-popup-open:bg-[color:color-mix(in_oklab,var(--primary)_88%,black)] data-[state=open]:bg-[color:color-mix(in_oklab,var(--primary)_88%,black)] data-[pressed]:bg-[color:color-mix(in_oklab,var(--primary)_82%,black)]",
167
+ },
168
+ )}`,
169
+ outline: `border-[color:color-mix(in_oklab,var(--border)_12%,transparent)] bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] ${interactiveStateClassName(
170
+ {
171
+ active:
172
+ "active:bg-[color:color-mix(in_oklab,var(--input)_15%,transparent)] active:text-[color:var(--foreground)]",
173
+ hover:
174
+ "hover:!border-[color:color-mix(in_oklab,var(--border)_20%,transparent)] hover:bg-[color:color-mix(in_oklab,var(--input)_15%,transparent)] hover:text-[color:var(--foreground)]",
175
+ persistent: `aria-expanded:border-[color:color-mix(in_oklab,var(--border)_45%,transparent)] aria-expanded:bg-[color:color-mix(in_oklab,var(--input)_15%,transparent)] aria-expanded:text-[color:var(--foreground)] data-open:border-[color:color-mix(in_oklab,var(--border)_45%,transparent)] data-open:bg-[color:color-mix(in_oklab,var(--input)_15%,transparent)] data-open:text-[color:var(--foreground)] data-popup-open:border-[color:color-mix(in_oklab,var(--border)_45%,transparent)] data-popup-open:bg-[color:color-mix(in_oklab,var(--input)_15%,transparent)] data-popup-open:text-[color:var(--foreground)] data-[state=open]:border-[color:color-mix(in_oklab,var(--border)_45%,transparent)] data-[state=open]:bg-[color:color-mix(in_oklab,var(--input)_15%,transparent)] data-[state=open]:text-[color:var(--foreground)] ${pressedSelectedItemClassName}`,
176
+ },
177
+ )}`,
178
+ "outline-inverted": `border-[color:color-mix(in_oklab,var(--background)_15%,transparent)] text-[color:color-mix(in_oklab,var(--background)_70%,transparent)] ${interactiveStateClassName(
179
+ {
180
+ active:
181
+ "active:bg-[color:color-mix(in_oklab,var(--background)_10%,transparent)] active:text-[color:var(--background)]",
182
+ hover:
183
+ "hover:bg-[color:color-mix(in_oklab,var(--background)_10%,transparent)] hover:text-[color:var(--background)]",
184
+ persistent:
185
+ "aria-expanded:bg-[color:color-mix(in_oklab,var(--background)_10%,transparent)] aria-expanded:text-[color:var(--background)] aria-pressed:bg-[color:color-mix(in_oklab,var(--background)_10%,transparent)] aria-pressed:text-[color:var(--background)] data-open:bg-[color:color-mix(in_oklab,var(--background)_10%,transparent)] data-open:text-[color:var(--background)] data-popup-open:bg-[color:color-mix(in_oklab,var(--background)_10%,transparent)] data-popup-open:text-[color:var(--background)] data-[state=open]:bg-[color:color-mix(in_oklab,var(--background)_10%,transparent)] data-[state=open]:text-[color:var(--background)] data-[pressed]:bg-[color:color-mix(in_oklab,var(--background)_10%,transparent)] data-[pressed]:text-[color:var(--background)]",
186
+ },
187
+ )}`,
188
+ "destructive-outline": `border-[color:color-mix(in_oklab,var(--destructive)_80%,transparent)] bg-[color:color-mix(in_oklab,var(--destructive)_80%,transparent)] text-[color:var(--destructive-foreground)] ${interactiveStateClassName(
189
+ {
190
+ active:
191
+ "active:bg-[color:color-mix(in_oklab,var(--destructive)_70%,transparent)]",
192
+ hover:
193
+ "hover:border-[color:color-mix(in_oklab,var(--destructive)_70%,transparent)] hover:bg-[color:color-mix(in_oklab,var(--destructive)_70%,transparent)]",
194
+ persistent:
195
+ "aria-expanded:bg-[color:color-mix(in_oklab,var(--destructive)_70%,transparent)] aria-pressed:bg-[color:color-mix(in_oklab,var(--destructive)_70%,transparent)] data-open:bg-[color:color-mix(in_oklab,var(--destructive)_70%,transparent)] data-popup-open:bg-[color:color-mix(in_oklab,var(--destructive)_70%,transparent)] data-[state=open]:bg-[color:color-mix(in_oklab,var(--destructive)_70%,transparent)] data-[pressed]:bg-[color:color-mix(in_oklab,var(--destructive)_70%,transparent)]",
196
+ },
197
+ )} focus-visible:border-[color:var(--destructive)] focus-visible:ring-[color:color-mix(in_oklab,var(--destructive)_20%,transparent)] dark:focus-visible:ring-[color:color-mix(in_oklab,var(--destructive)_40%,transparent)]`,
198
+ "destructive-outline-inverted": `border-[color:color-mix(in_oklab,var(--destructive)_80%,transparent)] bg-[color:color-mix(in_oklab,var(--destructive)_80%,transparent)] text-[color:var(--destructive-foreground)] ${interactiveStateClassName(
199
+ {
200
+ active:
201
+ "active:bg-[color:color-mix(in_oklab,var(--destructive)_70%,transparent)]",
202
+ hover:
203
+ "hover:border-[color:color-mix(in_oklab,var(--destructive)_70%,transparent)] hover:bg-[color:color-mix(in_oklab,var(--destructive)_70%,transparent)]",
204
+ persistent:
205
+ "aria-expanded:bg-[color:color-mix(in_oklab,var(--destructive)_70%,transparent)] aria-pressed:bg-[color:color-mix(in_oklab,var(--destructive)_70%,transparent)] data-open:bg-[color:color-mix(in_oklab,var(--destructive)_70%,transparent)] data-popup-open:bg-[color:color-mix(in_oklab,var(--destructive)_70%,transparent)] data-[state=open]:bg-[color:color-mix(in_oklab,var(--destructive)_70%,transparent)] data-[pressed]:bg-[color:color-mix(in_oklab,var(--destructive)_70%,transparent)]",
206
+ },
207
+ )} focus-visible:border-[color:var(--destructive)] focus-visible:ring-[color:color-mix(in_oklab,var(--destructive)_20%,transparent)]`,
208
+ secondary: `bg-[color:color-mix(in_oklab,var(--secondary)_8%,transparent)] text-[color:var(--secondary-foreground)] ${interactiveStateClassName(
209
+ {
210
+ active:
211
+ "active:bg-[color:color-mix(in_oklab,var(--secondary)_20%,transparent)]",
212
+ hover:
213
+ "hover:bg-[color:color-mix(in_oklab,var(--secondary)_20%,transparent)]",
214
+ persistent:
215
+ "aria-expanded:bg-[color:color-mix(in_oklab,var(--secondary)_20%,transparent)] aria-pressed:bg-[color:color-mix(in_oklab,var(--secondary)_20%,transparent)] data-open:bg-[color:color-mix(in_oklab,var(--secondary)_20%,transparent)] data-popup-open:bg-[color:color-mix(in_oklab,var(--secondary)_20%,transparent)] data-[state=open]:bg-[color:color-mix(in_oklab,var(--secondary)_20%,transparent)] data-[pressed]:bg-[color:color-mix(in_oklab,var(--secondary)_20%,transparent)]",
216
+ },
217
+ )}`,
218
+ "ghost-static": `bg-clip-border text-[color:var(--foreground)] ${interactiveStateClassName(
219
+ {
220
+ active:
221
+ "active:bg-transparent active:text-[color:var(--foreground)]",
222
+ hover: "hover:bg-transparent hover:text-[color:var(--foreground)]",
223
+ persistent:
224
+ "aria-expanded:bg-transparent aria-expanded:text-[color:var(--foreground)] aria-pressed:bg-transparent aria-pressed:text-[color:var(--foreground)] data-open:bg-transparent data-open:text-[color:var(--foreground)] data-popup-open:bg-transparent data-popup-open:text-[color:var(--foreground)] data-[state=open]:bg-transparent data-[state=open]:text-[color:var(--foreground)] data-[pressed]:bg-transparent data-[pressed]:text-[color:var(--foreground)]",
225
+ },
226
+ )}`,
227
+ send: `bg-[color:color-mix(in_oklab,var(--foreground)_80%,transparent)] !text-[color:color-mix(in_oklab,var(--background)_80%,transparent)] hover:!text-[color:color-mix(in_oklab,var(--background)_80%,transparent)] active:!text-[color:color-mix(in_oklab,var(--background)_80%,transparent)] [&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-100 hover:[&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-100 active:[&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-100 data-[size=icon]:[&_svg:not([data-slot='primitive-arrow-icon'])]:!opacity-100 data-[size=icon]:hover:[&_svg:not([data-slot='primitive-arrow-icon'])]:!opacity-100 data-[size=icon]:active:[&_svg:not([data-slot='primitive-arrow-icon'])]:!opacity-100 ${interactiveStateClassName(
228
+ {
229
+ active:
230
+ "active:bg-[color:color-mix(in_oklab,var(--foreground)_80%,transparent)]",
231
+ hover:
232
+ "hover:bg-[color:color-mix(in_oklab,var(--foreground)_80%,transparent)]",
233
+ persistent:
234
+ "aria-expanded:bg-[color:color-mix(in_oklab,var(--foreground)_80%,transparent)] aria-pressed:bg-[color:color-mix(in_oklab,var(--foreground)_80%,transparent)] data-open:bg-[color:color-mix(in_oklab,var(--foreground)_80%,transparent)] data-popup-open:bg-[color:color-mix(in_oklab,var(--foreground)_80%,transparent)] data-[state=open]:bg-[color:color-mix(in_oklab,var(--foreground)_80%,transparent)] data-[pressed]:bg-[color:color-mix(in_oklab,var(--foreground)_80%,transparent)]",
235
+ },
236
+ )}`,
237
+ stop: `bg-[color:var(--foreground)] !text-[color:color-mix(in_oklab,var(--background)_90%,transparent)] hover:!text-[color:color-mix(in_oklab,var(--background)_90%,transparent)] active:!text-[color:color-mix(in_oklab,var(--background)_90%,transparent)] [&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-100 hover:[&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-100 active:[&_svg:not([data-slot='primitive-arrow-icon'])]:opacity-100 data-[size=icon]:[&_svg:not([data-slot='primitive-arrow-icon'])]:!opacity-100 data-[size=icon]:hover:[&_svg:not([data-slot='primitive-arrow-icon'])]:!opacity-100 data-[size=icon]:active:[&_svg:not([data-slot='primitive-arrow-icon'])]:!opacity-100 ${interactiveStateClassName(
238
+ {
239
+ active: "active:bg-[color:var(--foreground)]",
240
+ hover: "hover:bg-[color:var(--foreground)]",
241
+ persistent:
242
+ "aria-expanded:bg-[color:var(--foreground)] aria-pressed:bg-[color:var(--foreground)] data-open:bg-[color:var(--foreground)] data-popup-open:bg-[color:var(--foreground)] data-[state=open]:bg-[color:var(--foreground)] data-[pressed]:bg-[color:var(--foreground)]",
243
+ },
244
+ )}`,
245
+ ghost: `bg-clip-border text-[color:var(--foreground)] ${interactiveStateClassName(
246
+ {
247
+ active:
248
+ "active:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] active:text-[color:var(--foreground)]",
249
+ hover:
250
+ "hover:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] hover:text-[color:var(--foreground)]",
251
+ persistent: `aria-expanded:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] aria-expanded:text-[color:var(--foreground)] data-open:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] data-open:text-[color:var(--foreground)] data-popup-open:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] data-popup-open:text-[color:var(--foreground)] data-[state=open]:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] data-[state=open]:text-[color:var(--foreground)] ${pressedSelectedItemClassName}`,
252
+ },
253
+ )}`,
254
+ "ghost-muted": `bg-clip-border text-[color:color-mix(in_oklab,var(--foreground)_60%,transparent)] ${interactiveStateClassName(
255
+ {
256
+ active:
257
+ "active:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] active:text-[color:var(--foreground)]",
258
+ hover:
259
+ "hover:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] hover:text-[color:var(--foreground)]",
260
+ persistent: `aria-expanded:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] aria-expanded:text-[color:var(--foreground)] data-open:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] data-open:text-[color:var(--foreground)] data-popup-open:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] data-popup-open:text-[color:var(--foreground)] data-[state=open]:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] data-[state=open]:text-[color:var(--foreground)] ${pressedSelectedItemClassName}`,
261
+ },
262
+ )}`,
263
+ destructive: `border-[color:color-mix(in_oklab,var(--destructive)_30%,transparent)] bg-[color:color-mix(in_oklab,var(--destructive)_15%,transparent)] text-[color:var(--destructive)] ${interactiveStateClassName(
264
+ {
265
+ active:
266
+ "active:bg-[color:color-mix(in_oklab,var(--destructive)_25%,transparent)] active:text-[color:var(--destructive)]",
267
+ hover:
268
+ "hover:border-[color:color-mix(in_oklab,var(--destructive)_60%,transparent)] hover:bg-[color:color-mix(in_oklab,var(--destructive)_25%,transparent)] hover:text-[color:var(--destructive)]",
269
+ persistent:
270
+ "aria-expanded:bg-[color:color-mix(in_oklab,var(--destructive)_25%,transparent)] aria-expanded:text-[color:var(--destructive)] aria-pressed:bg-[color:color-mix(in_oklab,var(--destructive)_25%,transparent)] aria-pressed:text-[color:var(--destructive)] data-open:bg-[color:color-mix(in_oklab,var(--destructive)_25%,transparent)] data-open:text-[color:var(--destructive)] data-popup-open:bg-[color:color-mix(in_oklab,var(--destructive)_25%,transparent)] data-popup-open:text-[color:var(--destructive)] data-[state=open]:bg-[color:color-mix(in_oklab,var(--destructive)_25%,transparent)] data-[state=open]:text-[color:var(--destructive)] data-[pressed]:bg-[color:color-mix(in_oklab,var(--destructive)_25%,transparent)] data-[pressed]:text-[color:var(--destructive)]",
271
+ },
272
+ )} focus-visible:border-[color:var(--destructive)] focus-visible:ring-[color:color-mix(in_oklab,var(--destructive)_20%,transparent)] dark:focus-visible:ring-[color:color-mix(in_oklab,var(--destructive)_40%,transparent)]`,
273
+ "link-solid": `bg-[color:var(--link)] text-[color:var(--background)] ${interactiveStateClassName(
274
+ {
275
+ active:
276
+ "active:bg-[color:color-mix(in_oklab,var(--link)_82%,black)]",
277
+ hover: "hover:bg-[color:color-mix(in_oklab,var(--link)_88%,black)]",
278
+ persistent:
279
+ "aria-expanded:bg-[color:color-mix(in_oklab,var(--link)_88%,black)] aria-pressed:bg-[color:color-mix(in_oklab,var(--link)_82%,black)] data-open:bg-[color:color-mix(in_oklab,var(--link)_88%,black)] data-popup-open:bg-[color:color-mix(in_oklab,var(--link)_88%,black)] data-[state=open]:bg-[color:color-mix(in_oklab,var(--link)_88%,black)] data-[pressed]:bg-[color:color-mix(in_oklab,var(--link)_82%,black)]",
280
+ },
281
+ )}`,
282
+ link: `text-[color:var(--primary)] underline-offset-4 ${interactiveStateClassName(
283
+ {
284
+ active: "active:underline",
285
+ hover: "hover:underline",
286
+ persistent:
287
+ "aria-expanded:underline aria-pressed:underline data-open:underline data-popup-open:underline data-[state=open]:underline data-[pressed]:underline",
288
+ },
289
+ )}`,
290
+ "toolbar-ghost": "",
291
+ "toolbar-secondary": "",
292
+ },
293
+ size: {
294
+ default:
295
+ "h-7 gap-1 px-2 text-[13px] leading-[1.125rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
296
+ xxs: "h-[18px] gap-1 px-1.5 text-[11px] has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 [&_svg:not([class*='size-'])]:size-2.5",
297
+ xs: "h-[22px] gap-1 px-2 text-[12px] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-2.5",
298
+ sm: "h-6 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
299
+ lg: "h-[34px] gap-1 px-3.5 text-sm/relaxed tracking-tight has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3 [&_svg:not([class*='size-'])]:size-3.5",
300
+ xl: "button-xl-icon-size h-10 gap-1.5 px-3 text-sm/relaxed has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5",
301
+ icon: "size-7 text-[13px] leading-[1.125rem] [&_svg:not([class*='size-'])]:size-3.5",
302
+ "icon-tight":
303
+ "h-7 px-1.5 text-[13px] leading-[1.125rem] [&_svg:not([class*='size-'])]:size-3.5",
304
+ "icon-xxs":
305
+ "size-[18px] text-[11px] [&_svg:not([class*='size-'])]:size-2.5",
306
+ "icon-xs":
307
+ "size-[22px] text-[12px] [&_svg:not([class*='size-'])]:size-2.5",
308
+ "icon-sm":
309
+ "size-6 text-xs/relaxed [&_svg:not([class*='size-'])]:size-3",
310
+ "icon-lg":
311
+ "size-[34px] text-sm/relaxed tracking-tight [&_svg:not([class*='size-'])]:size-4",
312
+ "icon-xl": "button-xl-icon-size size-10 text-sm/relaxed",
313
+ },
314
+ },
315
+ defaultVariants: {
316
+ motion: "default",
317
+ radius: "default",
318
+ variant: "default",
319
+ size: "default",
320
+ },
321
+ },
322
+ );
323
+
324
+ type ButtonProps = ButtonPrimitive.Props &
325
+ VariantProps<typeof buttonVariants> & {
326
+ "data-slot"?: string;
327
+ loading?: boolean;
328
+ loadingWidth?: LoaderSize;
329
+ loadingHeight?: LoaderSize;
330
+ loadingIndicatorClassName?: string;
331
+ };
332
+
333
+ function shouldUseCompactButtonLoadingHeight(
334
+ size: VariantProps<typeof buttonVariants>["size"],
335
+ ): boolean {
336
+ return (
337
+ size === "xxs" ||
338
+ size === "xs" ||
339
+ size === "sm" ||
340
+ size === "icon-xxs" ||
341
+ size === "icon-xs" ||
342
+ size === "icon-sm"
343
+ );
344
+ }
345
+
346
+ function Button({
347
+ "aria-label": ariaLabel,
348
+ "data-slot": dataSlot,
349
+ children,
350
+ className,
351
+ disabled,
352
+ loading = false,
353
+ loadingHeight,
354
+ loadingIndicatorClassName,
355
+ loadingWidth,
356
+ motion = "default",
357
+ radius = "default",
358
+ ref,
359
+ size = "default",
360
+ style,
361
+ variant = "default",
362
+ ...props
363
+ }: ButtonProps) {
364
+ const resolvedRadius =
365
+ radius === "default" && (size === "xxs" || size === "icon-xxs")
366
+ ? "sm"
367
+ : radius;
368
+ const buttonChildren = withSteppedButtonIconWeight(
369
+ children,
370
+ shouldStepButtonIconWeight(size),
371
+ );
372
+ const {
373
+ buttonAriaBusy,
374
+ buttonAriaLabel,
375
+ buttonClassName,
376
+ buttonContent,
377
+ buttonDisabled,
378
+ buttonLoader,
379
+ buttonRef,
380
+ buttonStyle,
381
+ dataLoading,
382
+ } = useButtonLoadingState<HTMLButtonElement>({
383
+ ariaLabel,
384
+ children: buttonChildren,
385
+ className,
386
+ compactHeight: shouldUseCompactButtonLoadingHeight(size),
387
+ disabled,
388
+ iconOnly: typeof size === "string" && size.startsWith("icon"),
389
+ loading,
390
+ loadingHeight,
391
+ loadingIndicatorClassName:
392
+ loadingIndicatorClassName ??
393
+ (variant === "default"
394
+ ? "bg-[color:var(--primary-foreground)]"
395
+ : variant === "link-solid"
396
+ ? "bg-[color:var(--background)]"
397
+ : undefined),
398
+ loadingWidth,
399
+ measurementKey: [motion, resolvedRadius, size, variant].join(":"),
400
+ ref,
401
+ style,
402
+ });
403
+
404
+ return (
405
+ <ButtonPrimitive
406
+ {...props}
407
+ aria-label={buttonAriaLabel}
408
+ aria-busy={buttonAriaBusy}
409
+ data-slot={dataSlot ?? "button"}
410
+ data-loading={dataLoading}
411
+ data-radius={resolvedRadius ?? undefined}
412
+ data-size={size ?? undefined}
413
+ data-variant={variant ?? undefined}
414
+ disabled={buttonDisabled}
415
+ ref={buttonRef}
416
+ className={cn(
417
+ buttonVariants({ motion, radius: resolvedRadius, variant, size }),
418
+ className,
419
+ buttonClassName,
420
+ )}
421
+ style={buttonStyle}
422
+ >
423
+ {buttonContent}
424
+ {buttonLoader}
425
+ </ButtonPrimitive>
426
+ );
427
+ }
428
+
429
+ export { Button, buttonVariants };