@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,298 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+
6
+ import { Button } from "./button";
7
+ import { outlineControlSurfaceClassName } from "../../lib/control-outline";
8
+ import { Input } from "./input";
9
+ import { Textarea } from "./textarea";
10
+ import { cn } from "../../lib/utils";
11
+
12
+ type InputGroupSize = "sm" | "default" | "lg" | "xl";
13
+
14
+ const InputGroupSizeContext = React.createContext<InputGroupSize>("default");
15
+
16
+ function useInputGroupSize(): InputGroupSize {
17
+ return React.useContext(InputGroupSizeContext);
18
+ }
19
+
20
+ const inputGroupVariants = cva(
21
+ cn(
22
+ "group/input-group relative flex w-full min-w-0 items-center rounded-lg border border-[color:color-mix(in_oklab,var(--border)_12%,transparent)] transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-data-[align=block-end]:rounded-lg has-data-[align=block-start]:rounded-lg has-[[data-slot][aria-invalid=true]]:border-[color:var(--destructive)] has-[textarea]:rounded-lg has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",
23
+ "[&:not(:focus-within):hover]:!border-[color:color-mix(in_oklab,var(--border)_20%,transparent)] [&:not(:focus-within):hover]:text-[color:var(--foreground)]",
24
+ ),
25
+ {
26
+ variants: {
27
+ size: {
28
+ sm: "h-6",
29
+ default: "h-7",
30
+ lg: "h-8",
31
+ xl: "h-10",
32
+ },
33
+ surfaceStyle: {
34
+ default: outlineControlSurfaceClassName,
35
+ transparent: "bg-transparent dark:bg-transparent",
36
+ "toolbar-address": "",
37
+ },
38
+ focusStyle: {
39
+ default:
40
+ "has-[[data-slot=input-group-control]:focus]:border-[color:color-mix(in_oklab,var(--border)_30%,transparent)] in-data-[slot=combobox-content]:has-[[data-slot=input-group-control]:focus]:border-inherit",
41
+ none: "",
42
+ "toolbar-address": "",
43
+ },
44
+ },
45
+ defaultVariants: {
46
+ size: "default",
47
+ surfaceStyle: "default",
48
+ focusStyle: "default",
49
+ },
50
+ },
51
+ );
52
+
53
+ function InputGroup({
54
+ className,
55
+ surfaceStyle,
56
+ focusStyle,
57
+ size = "default",
58
+ ...props
59
+ }: React.ComponentProps<"div"> &
60
+ Omit<VariantProps<typeof inputGroupVariants>, "size"> & {
61
+ size?: InputGroupSize;
62
+ }) {
63
+ const normalizedSize = size ?? "default";
64
+
65
+ return (
66
+ <InputGroupSizeContext.Provider value={normalizedSize}>
67
+ <div
68
+ data-focus-style={focusStyle ?? undefined}
69
+ data-size={normalizedSize}
70
+ data-slot="input-group"
71
+ data-surface-style={surfaceStyle ?? undefined}
72
+ role="group"
73
+ className={cn(
74
+ inputGroupVariants({
75
+ focusStyle,
76
+ size: normalizedSize,
77
+ surfaceStyle,
78
+ }),
79
+ className,
80
+ )}
81
+ {...props}
82
+ />
83
+ </InputGroupSizeContext.Provider>
84
+ );
85
+ }
86
+
87
+ const inputGroupAddonVariants = cva(
88
+ "flex h-auto cursor-text items-center justify-center gap-1 py-2 font-medium text-[color:var(--muted-foreground)] select-none group-data-[disabled=true]/input-group:opacity-50 [&>svg]:text-[color:var(--foreground)] **:data-[slot=kbd]:rounded-[calc(var(--radius-sm)-2px)] **:data-[slot=kbd]:bg-[color:color-mix(in_oklab,var(--muted-foreground)_10%,transparent)] **:data-[slot=kbd]:px-1 **:data-[slot=kbd]:text-[0.625rem] [&>svg:not([class*='size-'])]:size-3.5",
89
+ {
90
+ variants: {
91
+ align: {
92
+ "inline-start":
93
+ "order-first pl-2 has-[>button]:pl-px has-[>kbd]:ml-[-0.275rem]",
94
+ "inline-end":
95
+ "order-last pr-1.5 has-[>button]:pr-0.5 has-[>kbd]:mr-[-0.275rem]",
96
+ "block-start":
97
+ "order-first w-full justify-start px-2 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2",
98
+ "block-end":
99
+ "order-last w-full justify-start px-2 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2",
100
+ },
101
+ size: {
102
+ sm: "text-xs/relaxed",
103
+ default: "text-xs/relaxed",
104
+ lg: "gap-1.5 text-sm/relaxed data-[align=inline-start]:pl-2.5 data-[align=inline-start]:has-[>button]:pl-px data-[align=inline-end]:pr-2 data-[align=inline-end]:has-[>button]:pr-0.5 data-[align=block-start]:px-2.5 data-[align=block-end]:px-2.5 [&>svg:not([class*='size-'])]:size-4",
105
+ xl: "gap-2 text-base/relaxed data-[align=inline-start]:pl-3 data-[align=inline-start]:has-[>button]:pl-px data-[align=inline-end]:pr-2.5 data-[align=inline-end]:has-[>button]:pr-0.5 data-[align=block-start]:px-3 data-[align=block-end]:px-3 [&>svg:not([class*='size-'])]:size-4",
106
+ },
107
+ },
108
+ defaultVariants: {
109
+ align: "inline-start",
110
+ size: "default",
111
+ },
112
+ },
113
+ );
114
+
115
+ function InputGroupAddon({
116
+ className,
117
+ align = "inline-start",
118
+ ...props
119
+ }: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
120
+ const size = useInputGroupSize();
121
+
122
+ return (
123
+ <div
124
+ role="group"
125
+ data-slot="input-group-addon"
126
+ data-align={align}
127
+ className={cn(inputGroupAddonVariants({ align, size }), className)}
128
+ onMouseDown={(e) => {
129
+ if ((e.target as HTMLElement).closest("button")) {
130
+ return;
131
+ }
132
+ e.preventDefault();
133
+ e.currentTarget.parentElement?.querySelector("input")?.focus();
134
+ }}
135
+ {...props}
136
+ />
137
+ );
138
+ }
139
+
140
+ const inputGroupButtonVariants = cva(
141
+ "items-center justify-center shadow-none",
142
+ {
143
+ variants: {
144
+ groupSize: {
145
+ sm: "text-xs/relaxed",
146
+ default: "",
147
+ lg: "data-[size=xs]:h-6 data-[size=xs]:px-2 data-[size=xs]:text-sm/relaxed data-[size=xs]:[&>svg:not([class*='size-'])]:size-3.5 data-[size=sm]:h-7 data-[size=sm]:text-sm/relaxed data-[size=icon-xs]:size-7 data-[size=icon-xs]:[&>svg:not([class*='size-'])]:size-3.5 data-[size=icon-sm]:size-8 data-[size=icon-sm]:[&>svg:not([class*='size-'])]:size-3.5",
148
+ xl: "data-[size=xs]:h-8 data-[size=xs]:px-2.5 data-[size=xs]:text-base/relaxed data-[size=xs]:[&>svg:not([class*='size-'])]:size-4 data-[size=sm]:h-9 data-[size=sm]:px-3 data-[size=sm]:text-base/relaxed data-[size=sm]:[&>svg:not([class*='size-'])]:size-4 data-[size=icon-xs]:size-9 data-[size=icon-xs]:[&>svg:not([class*='size-'])]:size-4 data-[size=icon-sm]:size-10 data-[size=icon-sm]:[&>svg:not([class*='size-'])]:size-4",
149
+ },
150
+ size: {
151
+ xxs: "h-[18px] gap-1 px-1.5 text-[11px] [&>svg:not([class*='size-'])]:size-2.5",
152
+ xs: "h-5 gap-1 px-1.5 [&>svg:not([class*='size-'])]:size-3",
153
+ sm: "h-6 gap-1 px-2 [&>svg:not([class*='size-'])]:size-3",
154
+ "icon-xxs": "size-[18px] p-0 [&>svg:not([class*='size-'])]:size-2.5",
155
+ "icon-xs": "size-6 p-0 [&>svg:not([class*='size-'])]:size-3.5",
156
+ "icon-sm": "size-7 p-0 [&>svg:not([class*='size-'])]:size-3",
157
+ },
158
+ },
159
+ defaultVariants: {
160
+ groupSize: "default",
161
+ size: "xs",
162
+ },
163
+ },
164
+ );
165
+
166
+ function InputGroupButton({
167
+ className,
168
+ type = "button",
169
+ variant = "ghost",
170
+ size = "xs",
171
+ ...props
172
+ }: Omit<React.ComponentProps<typeof Button>, "size" | "type"> &
173
+ Omit<VariantProps<typeof inputGroupButtonVariants>, "groupSize"> & {
174
+ type?: "button" | "submit" | "reset";
175
+ }) {
176
+ const groupSize = useInputGroupSize();
177
+
178
+ return (
179
+ <Button
180
+ type={type}
181
+ size={size}
182
+ variant={variant}
183
+ className={cn(inputGroupButtonVariants({ groupSize, size }), className)}
184
+ {...props}
185
+ />
186
+ );
187
+ }
188
+
189
+ const inputGroupTextVariants = cva(
190
+ "flex items-center gap-2 text-[color:color-mix(in_oklab,var(--foreground)_40%,transparent)] group-hover/input-group:text-[color:var(--foreground)] group-has-[[data-slot=input-group-control]:focus]/input-group:text-[color:var(--foreground)] [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
191
+ {
192
+ variants: {
193
+ size: {
194
+ sm: "text-xs/relaxed",
195
+ default: "text-xs/relaxed",
196
+ lg: "text-sm/relaxed [&_svg:not([class*='size-'])]:size-4",
197
+ xl: "text-base/relaxed [&_svg:not([class*='size-'])]:size-4",
198
+ },
199
+ },
200
+ defaultVariants: {
201
+ size: "default",
202
+ },
203
+ },
204
+ );
205
+
206
+ function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
207
+ const size = useInputGroupSize();
208
+
209
+ return (
210
+ <span
211
+ data-slot="input-group-text"
212
+ className={cn(inputGroupTextVariants({ size }), className)}
213
+ {...props}
214
+ />
215
+ );
216
+ }
217
+
218
+ const inputGroupInputVariants = cva(
219
+ "h-full flex-1 border-0 shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0",
220
+ {
221
+ variants: {
222
+ size: {
223
+ sm: "px-2 py-0 text-xs/relaxed",
224
+ default: "px-2 py-0.5 text-xs/relaxed",
225
+ lg: "px-2.5 py-1 text-sm/relaxed",
226
+ xl: "px-3 py-1.5 text-base/relaxed",
227
+ },
228
+ surfaceStyle: {
229
+ default: "rounded-none bg-transparent dark:bg-transparent",
230
+ hoverInput:
231
+ "rounded-lg bg-transparent hover:bg-[color:color-mix(in_oklab,var(--input)_30%,transparent)] dark:bg-transparent dark:hover:bg-[color:color-mix(in_oklab,var(--input)_30%,transparent)]",
232
+ "toolbar-address": "rounded-none bg-transparent dark:bg-transparent",
233
+ },
234
+ typographyStyle: {
235
+ default: "",
236
+ addressBar: "text-xs-plus tracking-normal font-normal md:text-xs-plus",
237
+ popup: "popup-text-xs-plus leading-normal tracking-tight font-normal",
238
+ },
239
+ },
240
+ defaultVariants: {
241
+ size: "default",
242
+ surfaceStyle: "default",
243
+ typographyStyle: "default",
244
+ },
245
+ },
246
+ );
247
+
248
+ function InputGroupInput({
249
+ className,
250
+ surfaceStyle,
251
+ typographyStyle,
252
+ ...props
253
+ }: Omit<React.ComponentProps<typeof Input>, "size"> &
254
+ Omit<VariantProps<typeof inputGroupInputVariants>, "size">) {
255
+ const size = useInputGroupSize();
256
+
257
+ return (
258
+ <Input
259
+ data-surface-style={surfaceStyle ?? undefined}
260
+ data-typography-style={typographyStyle ?? undefined}
261
+ data-slot="input-group-control"
262
+ className={cn(
263
+ inputGroupInputVariants({ size, surfaceStyle, typographyStyle }),
264
+ className,
265
+ )}
266
+ {...props}
267
+ />
268
+ );
269
+ }
270
+
271
+ const InputGroupTextarea = React.forwardRef<
272
+ HTMLTextAreaElement,
273
+ Omit<React.ComponentProps<typeof Textarea>, "size">
274
+ >(function InputGroupTextarea({ className, ...props }, ref) {
275
+ const size = useInputGroupSize();
276
+
277
+ return (
278
+ <Textarea
279
+ ref={ref}
280
+ data-slot="input-group-control"
281
+ className={cn(
282
+ "flex-1 resize-none rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",
283
+ className,
284
+ )}
285
+ size={size}
286
+ {...props}
287
+ />
288
+ );
289
+ });
290
+
291
+ export {
292
+ InputGroup,
293
+ InputGroupAddon,
294
+ InputGroupButton,
295
+ InputGroupText,
296
+ InputGroupInput,
297
+ InputGroupTextarea,
298
+ };
@@ -0,0 +1,61 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Input as InputPrimitive } from "@base-ui/react/input";
5
+ import { cva, type VariantProps } from "class-variance-authority";
6
+ import {
7
+ SHARED_INPUT_CONTROL_BASE_CLASS_NAME,
8
+ SHARED_INPUT_CONTROL_SIZE_CLASS_NAMES,
9
+ } from "../../lib/input-control-style";
10
+
11
+ import { cn } from "../../lib/utils";
12
+
13
+ const inputVariants = cva(SHARED_INPUT_CONTROL_BASE_CLASS_NAME, {
14
+ variants: {
15
+ size: SHARED_INPUT_CONTROL_SIZE_CLASS_NAMES,
16
+ },
17
+ defaultVariants: {
18
+ size: "default",
19
+ },
20
+ });
21
+
22
+ type InputProps = Omit<React.ComponentProps<"input">, "size"> &
23
+ VariantProps<typeof inputVariants> & {
24
+ collapseSelectionOnBlur?: boolean;
25
+ };
26
+
27
+ function collapseSelection(target: HTMLInputElement): void {
28
+ try {
29
+ const caretPosition = target.selectionEnd ?? target.value.length;
30
+ target.setSelectionRange(caretPosition, caretPosition);
31
+ } catch {
32
+ // Ignore unsupported input types.
33
+ }
34
+ }
35
+
36
+ function Input({
37
+ className,
38
+ collapseSelectionOnBlur = false,
39
+ onBlur,
40
+ size,
41
+ type,
42
+ ...props
43
+ }: InputProps) {
44
+ return (
45
+ <InputPrimitive
46
+ type={type}
47
+ data-slot="input"
48
+ className={cn(inputVariants({ size }), className)}
49
+ onBlur={(event) => {
50
+ if (collapseSelectionOnBlur) {
51
+ collapseSelection(event.currentTarget);
52
+ }
53
+
54
+ onBlur?.(event);
55
+ }}
56
+ {...props}
57
+ />
58
+ );
59
+ }
60
+
61
+ export { Input, inputVariants };
@@ -0,0 +1,178 @@
1
+ import * as React from "react";
2
+ import { type Button as BaseButtonPrimitive } from "@base-ui/react/button";
3
+
4
+ import {
5
+ AnimatedLoader,
6
+ DEFAULT_ANIMATED_LOADER_HEIGHT,
7
+ type LoaderSize,
8
+ } from "../animated-loader";
9
+
10
+ export const DEFAULT_BUTTON_LOADING_WIDTH = 96;
11
+ export const DEFAULT_BUTTON_LOADING_EDGE_INSET = 16;
12
+ export const COMPACT_BUTTON_LOADING_EDGE_INSET = 4;
13
+ export const COMPACT_BUTTON_LOADING_HEIGHT = DEFAULT_ANIMATED_LOADER_HEIGHT - 2;
14
+
15
+ type UseButtonLoadingStateOptions<TElement extends HTMLElement> = {
16
+ ariaLabel?: string;
17
+ children: React.ReactNode;
18
+ className?: unknown;
19
+ compactHeight?: boolean;
20
+ disabled?: boolean;
21
+ iconOnly?: boolean;
22
+ loading?: boolean;
23
+ loadingHeight?: LoaderSize;
24
+ loadingIndicatorClassName?: string;
25
+ loadingWidth?: LoaderSize;
26
+ measurementKey?: string;
27
+ ref?: React.Ref<TElement>;
28
+ style?: ButtonStyle;
29
+ };
30
+
31
+ type ButtonStyle =
32
+ | React.CSSProperties
33
+ | ((state: BaseButtonPrimitive.State) => React.CSSProperties | undefined);
34
+
35
+ function extractButtonTextContent(children: React.ReactNode): string {
36
+ if (typeof children === "string" || typeof children === "number") {
37
+ return String(children);
38
+ }
39
+
40
+ if (Array.isArray(children)) {
41
+ return children.map((child) => extractButtonTextContent(child)).join("");
42
+ }
43
+
44
+ if (React.isValidElement<{ children?: React.ReactNode }>(children)) {
45
+ return extractButtonTextContent(children.props.children);
46
+ }
47
+
48
+ return "";
49
+ }
50
+
51
+ function assignButtonRef<T>(ref: React.Ref<T> | undefined, value: T): void {
52
+ if (typeof ref === "function") {
53
+ ref(value);
54
+ return;
55
+ }
56
+
57
+ if (ref && typeof ref === "object") {
58
+ ref.current = value;
59
+ }
60
+ }
61
+
62
+ function withButtonInlineSize(style: ButtonStyle | undefined, inlineSize: number): ButtonStyle {
63
+ const inlineSizeStyle = {
64
+ maxWidth: `${inlineSize}px`,
65
+ minWidth: `${inlineSize}px`,
66
+ width: `${inlineSize}px`,
67
+ };
68
+
69
+ if (typeof style === "function") {
70
+ return (state) => ({
71
+ ...style(state),
72
+ ...inlineSizeStyle,
73
+ });
74
+ }
75
+
76
+ return {
77
+ ...style,
78
+ ...inlineSizeStyle,
79
+ };
80
+ }
81
+
82
+ export function useButtonLoadingState<TElement extends HTMLElement>({
83
+ ariaLabel,
84
+ children,
85
+ className,
86
+ compactHeight = false,
87
+ disabled,
88
+ iconOnly = false,
89
+ loading = false,
90
+ loadingHeight,
91
+ loadingIndicatorClassName,
92
+ loadingWidth,
93
+ measurementKey,
94
+ ref,
95
+ style,
96
+ }: UseButtonLoadingStateOptions<TElement>): {
97
+ buttonAriaBusy: true | undefined;
98
+ buttonAriaLabel: string | undefined;
99
+ buttonClassName: string | undefined;
100
+ buttonContent: React.ReactNode;
101
+ buttonDisabled: boolean;
102
+ buttonLoader: React.ReactNode;
103
+ buttonRef: (node: TElement | null) => void;
104
+ buttonStyle: ButtonStyle | undefined;
105
+ dataLoading: "true" | undefined;
106
+ } {
107
+ const buttonElementRef = React.useRef<TElement | null>(null);
108
+ const stableInlineSizeRef = React.useRef<number | null>(null);
109
+ const loadingInsetX = iconOnly
110
+ ? COMPACT_BUTTON_LOADING_EDGE_INSET
111
+ : DEFAULT_BUTTON_LOADING_EDGE_INSET;
112
+ const resolvedLoadingHeight =
113
+ loadingHeight ??
114
+ (compactHeight ? COMPACT_BUTTON_LOADING_HEIGHT : DEFAULT_ANIMATED_LOADER_HEIGHT);
115
+ const resolvedLoadingAriaLabel =
116
+ (ariaLabel ?? extractButtonTextContent(children).trim()) || undefined;
117
+ const buttonRef = React.useCallback(
118
+ (node: TElement | null) => {
119
+ buttonElementRef.current = node;
120
+ assignButtonRef(ref, node);
121
+ },
122
+ [ref],
123
+ );
124
+
125
+ React.useLayoutEffect(() => {
126
+ if (loading) {
127
+ return;
128
+ }
129
+
130
+ const buttonElement = buttonElementRef.current;
131
+ if (!buttonElement) {
132
+ return;
133
+ }
134
+
135
+ const measuredInlineSize = buttonElement.getBoundingClientRect().width;
136
+ if (measuredInlineSize > 0) {
137
+ stableInlineSizeRef.current = measuredInlineSize;
138
+ }
139
+ }, [children, className, loading, measurementKey, style]);
140
+
141
+ return {
142
+ buttonAriaBusy: loading || undefined,
143
+ buttonAriaLabel: loading ? resolvedLoadingAriaLabel : ariaLabel,
144
+ buttonClassName: loading ? "relative overflow-hidden transition-none !opacity-100" : undefined,
145
+ buttonContent: loading ? (
146
+ <span
147
+ aria-hidden="true"
148
+ className="invisible inline-flex items-center"
149
+ data-slot="button-content"
150
+ style={{ gap: "inherit" }}
151
+ >
152
+ {children}
153
+ </span>
154
+ ) : (
155
+ children
156
+ ),
157
+ buttonDisabled: Boolean(disabled || loading),
158
+ buttonLoader: loading ? (
159
+ <span
160
+ className="pointer-events-none absolute inset-0 flex items-center justify-center"
161
+ data-slot="button-loader"
162
+ >
163
+ <AnimatedLoader
164
+ height={resolvedLoadingHeight}
165
+ indicatorClassName={loadingIndicatorClassName}
166
+ insetX={loadingInsetX}
167
+ width={loadingWidth ?? DEFAULT_BUTTON_LOADING_WIDTH}
168
+ />
169
+ </span>
170
+ ) : null,
171
+ buttonRef,
172
+ buttonStyle:
173
+ loading && stableInlineSizeRef.current
174
+ ? withButtonInlineSize(style, stableInlineSizeRef.current)
175
+ : style,
176
+ dataLoading: loading ? "true" : undefined,
177
+ };
178
+ }
@@ -0,0 +1,16 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "../../lib/utils";
4
+
5
+ function Label({ className, ...props }: React.ComponentPropsWithoutRef<"label">) {
6
+ return React.createElement("label", {
7
+ ...props,
8
+ "data-slot": "label",
9
+ className: cn(
10
+ "flex items-center gap-2 text-xs/relaxed leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
11
+ className,
12
+ ),
13
+ });
14
+ }
15
+
16
+ export { Label };
@@ -0,0 +1,126 @@
1
+ import * as React from "react";
2
+ import { Popover as PopoverPrimitive } from "@base-ui/react/popover";
3
+
4
+ import {
5
+ PortalLayerContainerProvider,
6
+ type PortalLayerContainer,
7
+ usePortalLayerContainer,
8
+ } from "./portal-layer-context";
9
+ import { cn } from "../../lib/utils";
10
+
11
+ const popoverContentSurfaceClassName =
12
+ "floating-popup-surface z-50 flex w-72 origin-(--transform-origin) flex-col gap-4 rounded-lg border p-2.5 popup-text-xs-plus text-[color:var(--popover-foreground)] outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95";
13
+
14
+ const embeddedPopoverCardSurfaceClassName =
15
+ "floating-popup-surface flex min-h-0 w-full flex-col overflow-hidden rounded-xl border border-[color:color-mix(in_oklab,var(--border)_12%,transparent)] bg-[color:color-mix(in_oklab,var(--foreground)_5%,transparent)] p-0 text-[color:var(--foreground)]";
16
+
17
+ function PopoverSurface({
18
+ className,
19
+ variant = "default",
20
+ ...props
21
+ }: React.ComponentProps<"div"> & {
22
+ variant?: "default" | "embedded-card";
23
+ }) {
24
+ return (
25
+ <div
26
+ data-slot="popover-content"
27
+ data-variant={variant}
28
+ className={cn(
29
+ variant === "embedded-card"
30
+ ? embeddedPopoverCardSurfaceClassName
31
+ : popoverContentSurfaceClassName,
32
+ className,
33
+ )}
34
+ {...props}
35
+ />
36
+ );
37
+ }
38
+
39
+ function Popover({ ...props }: PopoverPrimitive.Root.Props) {
40
+ return <PopoverPrimitive.Root data-slot="popover" {...props} />;
41
+ }
42
+
43
+ function PopoverTrigger({ ...props }: PopoverPrimitive.Trigger.Props) {
44
+ return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
45
+ }
46
+
47
+ function PopoverContent({
48
+ className,
49
+ align = "center",
50
+ alignOffset = 0,
51
+ anchor,
52
+ portalContainer,
53
+ side = "bottom",
54
+ sideOffset = 4,
55
+ ...props
56
+ }: PopoverPrimitive.Popup.Props &
57
+ Pick<
58
+ PopoverPrimitive.Positioner.Props,
59
+ "align" | "alignOffset" | "anchor" | "side" | "sideOffset"
60
+ > & {
61
+ portalContainer?: PortalLayerContainer;
62
+ }) {
63
+ const resolvedContainer = usePortalLayerContainer(portalContainer);
64
+ const portalNodeRef = React.useRef<HTMLDivElement | null>(null);
65
+
66
+ return (
67
+ <PopoverPrimitive.Portal container={resolvedContainer} ref={portalNodeRef}>
68
+ <PortalLayerContainerProvider container={portalNodeRef}>
69
+ <PopoverPrimitive.Positioner
70
+ align={align}
71
+ alignOffset={alignOffset}
72
+ anchor={anchor}
73
+ side={side}
74
+ sideOffset={sideOffset}
75
+ className="isolate z-50"
76
+ >
77
+ <PopoverPrimitive.Popup
78
+ render={<PopoverSurface />}
79
+ className={cn(className)}
80
+ {...props}
81
+ />
82
+ </PopoverPrimitive.Positioner>
83
+ </PortalLayerContainerProvider>
84
+ </PopoverPrimitive.Portal>
85
+ );
86
+ }
87
+
88
+ function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) {
89
+ return (
90
+ <div
91
+ data-slot="popover-header"
92
+ className={cn("flex flex-col gap-1 popup-text-xs-plus", className)}
93
+ {...props}
94
+ />
95
+ );
96
+ }
97
+
98
+ function PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {
99
+ return (
100
+ <PopoverPrimitive.Title
101
+ data-slot="popover-title"
102
+ className={cn("popup-text-xs-plus font-medium", className)}
103
+ {...props}
104
+ />
105
+ );
106
+ }
107
+
108
+ function PopoverDescription({ className, ...props }: PopoverPrimitive.Description.Props) {
109
+ return (
110
+ <PopoverPrimitive.Description
111
+ data-slot="popover-description"
112
+ className={cn("text-[color:var(--muted-foreground)]", className)}
113
+ {...props}
114
+ />
115
+ );
116
+ }
117
+
118
+ export {
119
+ Popover,
120
+ PopoverContent,
121
+ PopoverDescription,
122
+ PopoverHeader,
123
+ PopoverSurface,
124
+ PopoverTitle,
125
+ PopoverTrigger,
126
+ };