@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,62 @@
1
+ "use client";
2
+
3
+ import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+
6
+ import { cn } from "../../lib/utils";
7
+ import { CheckIcon } from "@phosphor-icons/react";
8
+
9
+ const checkboxVariants = cva(
10
+ "peer relative flex shrink-0 cursor-pointer items-center justify-center rounded-[4px] border border-[color:color-mix(in_oklab,var(--border)_12%,transparent)] bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] bg-clip-padding text-[color:var(--foreground)] transition-all outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 hover:!border-[color:color-mix(in_oklab,var(--border)_20%,transparent)] hover:bg-[color:color-mix(in_oklab,var(--input)_15%,transparent)] focus-visible:ring focus-visible:ring-[color:color-mix(in_oklab,var(--ring)_30%,transparent)] data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:hover:border-[color:color-mix(in_oklab,var(--border)_12%,transparent)] data-disabled:hover:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] aria-invalid:border-[color:var(--destructive)] aria-invalid:ring 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)] data-checked:border-[color:var(--primary)] data-checked:hover:border-[color:var(--primary)] data-checked:active:border-[color:var(--primary)] data-checked:focus-visible:border-[color:var(--primary)] data-checked:bg-[color:var(--primary)] data-checked:bg-clip-border data-checked:text-[color:var(--primary-foreground)] dark:data-checked:bg-[color:var(--primary)]",
11
+ {
12
+ variants: {
13
+ size: {
14
+ sm: "size-3.5",
15
+ default: "size-4",
16
+ lg: "size-4.5",
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ size: "default",
21
+ },
22
+ },
23
+ );
24
+
25
+ const checkboxIndicatorVariants = cva("grid place-content-center text-current transition-none", {
26
+ variants: {
27
+ size: {
28
+ sm: "[&>svg]:size-3",
29
+ default: "[&>svg]:size-3.5",
30
+ lg: "[&>svg]:size-3.5",
31
+ },
32
+ },
33
+ defaultVariants: {
34
+ size: "default",
35
+ },
36
+ });
37
+
38
+ function Checkbox({
39
+ className,
40
+ size,
41
+ ...props
42
+ }: CheckboxPrimitive.Root.Props & VariantProps<typeof checkboxVariants>) {
43
+ const resolvedSize = size ?? "default";
44
+
45
+ return (
46
+ <CheckboxPrimitive.Root
47
+ data-slot="checkbox"
48
+ data-size={resolvedSize}
49
+ className={cn(checkboxVariants({ size: resolvedSize }), className)}
50
+ {...props}
51
+ >
52
+ <CheckboxPrimitive.Indicator
53
+ data-slot="checkbox-indicator"
54
+ className={checkboxIndicatorVariants({ size: resolvedSize })}
55
+ >
56
+ <CheckIcon />
57
+ </CheckboxPrimitive.Indicator>
58
+ </CheckboxPrimitive.Root>
59
+ );
60
+ }
61
+
62
+ export { Checkbox };
@@ -0,0 +1,337 @@
1
+ "use client";
2
+
3
+ import { Children, isValidElement, useEffect, useRef, useState } from "react";
4
+
5
+ import { Slider } from "./slider";
6
+
7
+ const editableValueTextBaseClassName =
8
+ "block h-full min-w-0 overflow-hidden whitespace-nowrap font-sans text-xs leading-5 tabular-nums";
9
+ const valueLabelContainerClassName = "inline-grid h-5 shrink-0";
10
+
11
+ type EditableSliderValueLabelLayout = "content" | "reference";
12
+ type EditableSliderValueLabelTextAlign = "left" | "right";
13
+
14
+ type SliderMetadataProps = {
15
+ max?: number;
16
+ min?: number;
17
+ };
18
+
19
+ type EditableSliderValueLabelProps = {
20
+ ariaLabel: string;
21
+ disabled?: boolean;
22
+ layout?: EditableSliderValueLabelLayout;
23
+ maxValueLabel?: string;
24
+ onCommit?: (nextValue: string) => void;
25
+ onStep?: (direction: -1 | 1, currentDraft: string) => string | undefined;
26
+ textAlign?: EditableSliderValueLabelTextAlign;
27
+ valueLabel: string;
28
+ };
29
+
30
+ export function EditableSliderValueLabel({
31
+ ariaLabel,
32
+ disabled = false,
33
+ layout = "reference",
34
+ maxValueLabel,
35
+ onCommit,
36
+ onStep,
37
+ textAlign = "right",
38
+ valueLabel,
39
+ }: EditableSliderValueLabelProps): React.JSX.Element {
40
+ const [editing, setEditing] = useState(false);
41
+ const editorRef = useRef<HTMLSpanElement>(null);
42
+ const valueLabelRef = useRef(valueLabel);
43
+ const valueTextClassName = getEditableValueTextClassName({ layout, textAlign });
44
+ const widestValueLabel = getWidestValueLabel(valueLabel, maxValueLabel);
45
+
46
+ useEffect(() => {
47
+ valueLabelRef.current = valueLabel;
48
+ }, [valueLabel]);
49
+
50
+ useEffect(() => {
51
+ if (editing) {
52
+ const editor = editorRef.current;
53
+
54
+ if (!editor) {
55
+ return;
56
+ }
57
+
58
+ editor.textContent = valueLabelRef.current;
59
+ editor.focus();
60
+ selectEditableText(editor);
61
+ }
62
+ }, [editing]);
63
+
64
+ if (disabled || !onCommit) {
65
+ const valueTextToneClassName = disabled
66
+ ? "text-[color:color-mix(in_oklab,var(--foreground)_60%,transparent)] opacity-60"
67
+ : "text-[color:var(--muted-foreground)]";
68
+
69
+ return (
70
+ <span className={getValueLabelContainerClassName(layout, "cursor-default")}>
71
+ {layout === "reference" ? <SliderValueLabelMeasure valueLabel={widestValueLabel} /> : null}
72
+ <span
73
+ className={`col-start-1 row-start-1 cursor-default ${valueTextToneClassName} ${valueTextClassName}`}
74
+ >
75
+ {valueLabel}
76
+ </span>
77
+ </span>
78
+ );
79
+ }
80
+
81
+ function commitDraft(): void {
82
+ onCommit?.(editorRef.current?.textContent ?? valueLabelRef.current);
83
+ setEditing(false);
84
+ }
85
+
86
+ return (
87
+ <span className={getValueLabelContainerClassName(layout)}>
88
+ {layout === "reference" ? <SliderValueLabelMeasure valueLabel={widestValueLabel} /> : null}
89
+ {editing ? (
90
+ <EditableSliderValueEditor
91
+ ariaLabel={ariaLabel}
92
+ editorRef={editorRef}
93
+ layout={layout}
94
+ onCancel={() => setEditing(false)}
95
+ onCommit={commitDraft}
96
+ onStep={onStep}
97
+ textClassName={valueTextClassName}
98
+ />
99
+ ) : (
100
+ <EditableSliderValueButton
101
+ ariaLabel={ariaLabel}
102
+ layout={layout}
103
+ onBeginEditing={() => setEditing(true)}
104
+ textClassName={valueTextClassName}
105
+ valueLabel={valueLabel}
106
+ />
107
+ )}
108
+ </span>
109
+ );
110
+ }
111
+
112
+ function SliderValueLabelMeasure({ valueLabel }: { valueLabel: string }): React.JSX.Element {
113
+ return (
114
+ <span
115
+ aria-hidden="true"
116
+ className={`invisible col-start-1 row-start-1 min-w-[4ch] pointer-events-none text-[color:var(--muted-foreground)] ${getEditableValueTextClassName(
117
+ {
118
+ layout: "reference",
119
+ textAlign: "right",
120
+ },
121
+ )}`}
122
+ data-slider-value-label-measure=""
123
+ >
124
+ {valueLabel}
125
+ </span>
126
+ );
127
+ }
128
+
129
+ function getValueLabelContainerClassName(
130
+ layout: EditableSliderValueLabelLayout,
131
+ className = "",
132
+ ): string {
133
+ return [
134
+ valueLabelContainerClassName,
135
+ layout === "content" ? "w-fit justify-items-start" : "place-items-center",
136
+ className,
137
+ ]
138
+ .filter(Boolean)
139
+ .join(" ");
140
+ }
141
+
142
+ function getEditableValueTextClassName({
143
+ layout,
144
+ textAlign,
145
+ }: {
146
+ layout: EditableSliderValueLabelLayout;
147
+ textAlign: EditableSliderValueLabelTextAlign;
148
+ }): string {
149
+ return [
150
+ editableValueTextBaseClassName,
151
+ layout === "content" ? "w-auto" : "w-full",
152
+ textAlign === "left" ? "text-left" : "text-right",
153
+ ].join(" ");
154
+ }
155
+
156
+ function EditableSliderValueEditor({
157
+ ariaLabel,
158
+ editorRef,
159
+ layout,
160
+ onCancel,
161
+ onCommit,
162
+ onStep,
163
+ textClassName,
164
+ }: {
165
+ ariaLabel: string;
166
+ editorRef: React.RefObject<HTMLSpanElement | null>;
167
+ layout: EditableSliderValueLabelLayout;
168
+ onCancel: () => void;
169
+ onCommit: () => void;
170
+ onStep?: (direction: -1 | 1, currentDraft: string) => string | undefined;
171
+ textClassName: string;
172
+ }): React.JSX.Element {
173
+ return (
174
+ <span
175
+ aria-label={ariaLabel}
176
+ className={`col-start-1 row-start-1 cursor-text p-0 text-[color:var(--foreground)] outline-none ${layout === "content" ? "justify-self-start" : ""} ${textClassName}`}
177
+ contentEditable
178
+ onBlur={onCommit}
179
+ onFocus={(event) => selectEditableText(event.currentTarget)}
180
+ onKeyDown={(event) => {
181
+ if (event.key === "Enter") {
182
+ event.preventDefault();
183
+ onCommit();
184
+ }
185
+ if (event.key === "Escape") {
186
+ event.preventDefault();
187
+ onCancel();
188
+ }
189
+ if (event.key === "ArrowUp" || event.key === "ArrowDown") {
190
+ const nextValueLabel = onStep?.(
191
+ event.key === "ArrowUp" ? 1 : -1,
192
+ event.currentTarget.textContent ?? "",
193
+ );
194
+
195
+ if (typeof nextValueLabel === "string") {
196
+ event.preventDefault();
197
+ event.currentTarget.textContent = nextValueLabel;
198
+ selectEditableText(event.currentTarget);
199
+ }
200
+ }
201
+ }}
202
+ onPointerDown={(event) => event.stopPropagation()}
203
+ ref={editorRef}
204
+ role="textbox"
205
+ suppressContentEditableWarning
206
+ tabIndex={0}
207
+ />
208
+ );
209
+ }
210
+
211
+ function EditableSliderValueButton({
212
+ ariaLabel,
213
+ layout,
214
+ onBeginEditing,
215
+ textClassName,
216
+ valueLabel,
217
+ }: {
218
+ ariaLabel: string;
219
+ layout: EditableSliderValueLabelLayout;
220
+ onBeginEditing: () => void;
221
+ textClassName: string;
222
+ valueLabel: string;
223
+ }): React.JSX.Element {
224
+ function beginEditableActivation(event: React.MouseEvent | React.PointerEvent): void {
225
+ event.stopPropagation();
226
+ onBeginEditing();
227
+ }
228
+
229
+ return (
230
+ <button
231
+ aria-label={`Edit ${ariaLabel}`}
232
+ className={`col-start-1 row-start-1 h-full min-w-0 appearance-none cursor-text border-0 bg-transparent p-0 transition-colors ${layout === "content" ? "w-auto justify-self-start" : "w-full"}`}
233
+ onClick={beginEditableActivation}
234
+ onKeyDown={(event) => {
235
+ if (event.key === "Enter" || event.key === " ") {
236
+ event.preventDefault();
237
+ event.stopPropagation();
238
+ onBeginEditing();
239
+ }
240
+ }}
241
+ onMouseDown={stopEditableActivation}
242
+ onMouseUp={beginEditableActivation}
243
+ onPointerDown={stopEditableActivation}
244
+ onPointerUp={beginEditableActivation}
245
+ type="button"
246
+ >
247
+ <span
248
+ className={`cursor-text text-[color:var(--muted-foreground)] transition-colors duration-200 ease-out hover:text-[color:var(--foreground)] ${textClassName}`}
249
+ >
250
+ {valueLabel}
251
+ </span>
252
+ </button>
253
+ );
254
+ }
255
+
256
+ function stopEditableActivation(event: React.MouseEvent | React.PointerEvent): void {
257
+ event.stopPropagation();
258
+ }
259
+
260
+ export function getInferredValueLabelWidthReference(
261
+ valueLabel: string,
262
+ children: React.ReactNode,
263
+ ): string | undefined {
264
+ const sliderMetadata = getSliderMetadata(children);
265
+
266
+ if (!sliderMetadata) {
267
+ return undefined;
268
+ }
269
+
270
+ return getNumericValueLabelWidthReference(valueLabel, sliderMetadata);
271
+ }
272
+
273
+ function getSliderMetadata(children: React.ReactNode): SliderMetadataProps | undefined {
274
+ for (const child of Children.toArray(children)) {
275
+ if (!isValidElement<SliderMetadataProps>(child) || child.type !== Slider) {
276
+ continue;
277
+ }
278
+
279
+ return {
280
+ max: child.props.max ?? 100,
281
+ min: child.props.min ?? 0,
282
+ };
283
+ }
284
+
285
+ return undefined;
286
+ }
287
+
288
+ export function getNumericValueLabelWidthReference(
289
+ valueLabel: string,
290
+ { max = 100, min = 0 }: SliderMetadataProps,
291
+ ): string | undefined {
292
+ const numericMatches = Array.from(valueLabel.matchAll(/-?\d+(?:\.\d+)?/g));
293
+
294
+ if (numericMatches.length === 0) {
295
+ return undefined;
296
+ }
297
+
298
+ const decimalPrecision = Math.max(...numericMatches.map(getNumericMatchDecimalPrecision));
299
+ const widestEndpoint = [min, max]
300
+ .map((value) => formatNumericEndpoint(value, decimalPrecision))
301
+ .sort((left, right) => right.length - left.length)[0];
302
+
303
+ return valueLabel.replaceAll(/-?\d+(?:\.\d+)?/g, widestEndpoint ?? "");
304
+ }
305
+
306
+ function getNumericMatchDecimalPrecision(match: RegExpMatchArray): number {
307
+ return match[0].split(".")[1]?.length ?? 0;
308
+ }
309
+
310
+ function formatNumericEndpoint(value: number, decimalPrecision: number): string {
311
+ if (decimalPrecision > 0) {
312
+ return value.toFixed(decimalPrecision);
313
+ }
314
+
315
+ return `${Math.round(value)}`;
316
+ }
317
+
318
+ function getWidestValueLabel(valueLabel: string, maxValueLabel?: string): string {
319
+ if (!maxValueLabel || valueLabel.length > maxValueLabel.length) {
320
+ return valueLabel;
321
+ }
322
+
323
+ return maxValueLabel;
324
+ }
325
+
326
+ function selectEditableText(node: HTMLElement): void {
327
+ const selection = window.getSelection();
328
+
329
+ if (!selection) {
330
+ return;
331
+ }
332
+
333
+ const range = document.createRange();
334
+ range.selectNodeContents(node);
335
+ selection.removeAllRanges();
336
+ selection.addRange(range);
337
+ }
@@ -0,0 +1,225 @@
1
+ import { useMemo } from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+
4
+ import { cn } from "../../lib/utils";
5
+ import { Label } from "./label";
6
+ import { Separator } from "./separator";
7
+
8
+ function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
9
+ return (
10
+ <fieldset
11
+ data-slot="field-set"
12
+ className={cn(
13
+ "flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
14
+ className,
15
+ )}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ function FieldLegend({
22
+ className,
23
+ variant = "legend",
24
+ ...props
25
+ }: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
26
+ return (
27
+ <legend
28
+ data-slot="field-legend"
29
+ data-variant={variant}
30
+ className={cn(
31
+ "mb-2 font-medium data-[variant=label]:text-xs/relaxed data-[variant=legend]:text-sm",
32
+ className,
33
+ )}
34
+ {...props}
35
+ />
36
+ );
37
+ }
38
+
39
+ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
40
+ return (
41
+ <div
42
+ data-slot="field-group"
43
+ className={cn(
44
+ "group/field-group @container/field-group flex w-full flex-col gap-4 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4",
45
+ className,
46
+ )}
47
+ {...props}
48
+ />
49
+ );
50
+ }
51
+
52
+ const fieldVariants = cva(
53
+ "group/field flex w-full gap-field-control data-[invalid=true]:text-[color:var(--destructive)]",
54
+ {
55
+ variants: {
56
+ orientation: {
57
+ vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
58
+ horizontal:
59
+ "flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px has-[>[data-slot=field-content]]:[&>[role=radio][data-size=lg]]:mt-0.5",
60
+ responsive:
61
+ "flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px @md/field-group:has-[>[data-slot=field-content]]:[&>[role=radio][data-size=lg]]:mt-0.5",
62
+ },
63
+ },
64
+ defaultVariants: {
65
+ orientation: "vertical",
66
+ },
67
+ },
68
+ );
69
+
70
+ function Field({
71
+ className,
72
+ orientation = "vertical",
73
+ ...props
74
+ }: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
75
+ return (
76
+ <div
77
+ role="group"
78
+ data-slot="field"
79
+ data-orientation={orientation}
80
+ className={cn(fieldVariants({ orientation }), className)}
81
+ {...props}
82
+ />
83
+ );
84
+ }
85
+
86
+ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
87
+ return (
88
+ <div
89
+ data-slot="field-content"
90
+ className={cn("group/field-content flex flex-1 flex-col gap-0.5 leading-snug", className)}
91
+ {...props}
92
+ />
93
+ );
94
+ }
95
+
96
+ function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
97
+ return (
98
+ <Label
99
+ data-slot="field-label"
100
+ className={cn(
101
+ "group/field-label peer/field-label flex w-fit gap-2 text-[color:color-mix(in_oklab,var(--foreground)_60%,transparent)] leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:bg-[color:color-mix(in_oklab,var(--primary)_5%,transparent)] has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border has-[>[data-slot=field]]:border-[color:color-mix(in_oklab,var(--border)_12%,transparent)] *:data-[slot=field]:p-2 dark:has-data-checked:bg-[color:color-mix(in_oklab,var(--primary)_10%,transparent)]",
102
+ "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col group-has-[>[data-slot=checkbox][data-size=sm]]/field:text-xs-plus group-has-[>[data-slot=checkbox][data-checked]]/field:text-[color:var(--foreground)] group-has-[>[data-slot=checkbox][data-size=lg]]/field:text-sm/relaxed group-has-[>[data-slot=radio-group-item][data-size=lg]]/field:text-sm/relaxed",
103
+ className,
104
+ )}
105
+ {...props}
106
+ />
107
+ );
108
+ }
109
+
110
+ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
111
+ return (
112
+ <div
113
+ data-slot="field-label"
114
+ className={cn(
115
+ "flex w-fit items-center gap-2 text-xs/relaxed leading-snug font-medium group-data-[disabled=true]/field:opacity-50 group-has-[>[role=radio][data-size=lg]]/field:text-sm/relaxed",
116
+ className,
117
+ )}
118
+ {...props}
119
+ />
120
+ );
121
+ }
122
+
123
+ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
124
+ return (
125
+ <p
126
+ data-slot="field-description"
127
+ className={cn(
128
+ "text-left text-xs/relaxed leading-normal font-normal text-[color:var(--muted-foreground)] group-has-data-horizontal/field:text-balance group-has-[>[role=radio][data-size=lg]]/field:text-sm/relaxed [[data-variant=legend]+&]:-mt-1.5",
129
+ "last:mt-0 nth-last-2:-mt-1",
130
+ "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-[color:var(--primary)]",
131
+ className,
132
+ )}
133
+ {...props}
134
+ />
135
+ );
136
+ }
137
+
138
+ function FieldSeparator({
139
+ children,
140
+ className,
141
+ ...props
142
+ }: React.ComponentProps<"div"> & {
143
+ children?: React.ReactNode;
144
+ }) {
145
+ return (
146
+ <div
147
+ data-slot="field-separator"
148
+ data-content={!!children}
149
+ className={cn(
150
+ "relative -my-2 h-5 text-xs/relaxed group-data-[variant=outline]/field-group:-mb-2",
151
+ className,
152
+ )}
153
+ {...props}
154
+ >
155
+ <Separator className="absolute inset-0 top-1/2" />
156
+ {children && (
157
+ <span
158
+ className="relative mx-auto block w-fit bg-[color:var(--background)] px-2 text-[color:var(--muted-foreground)]"
159
+ data-slot="field-separator-content"
160
+ >
161
+ {children}
162
+ </span>
163
+ )}
164
+ </div>
165
+ );
166
+ }
167
+
168
+ function FieldError({
169
+ className,
170
+ children,
171
+ errors,
172
+ ...props
173
+ }: React.ComponentProps<"div"> & {
174
+ errors?: Array<{ message?: string } | undefined>;
175
+ }) {
176
+ const content = useMemo(() => {
177
+ if (children) {
178
+ return children;
179
+ }
180
+
181
+ if (!errors?.length) {
182
+ return null;
183
+ }
184
+
185
+ const uniqueErrors = [...new Map(errors.map((error) => [error?.message, error])).values()];
186
+
187
+ if (uniqueErrors?.length == 1) {
188
+ return uniqueErrors[0]?.message;
189
+ }
190
+
191
+ return (
192
+ <ul className="ml-4 flex list-disc flex-col gap-1">
193
+ {uniqueErrors.map((error, index) => error?.message && <li key={index}>{error.message}</li>)}
194
+ </ul>
195
+ );
196
+ }, [children, errors]);
197
+
198
+ if (!content) {
199
+ return null;
200
+ }
201
+
202
+ return (
203
+ <div
204
+ role="alert"
205
+ data-slot="field-error"
206
+ className={cn("text-xs/relaxed font-normal text-[color:var(--destructive)]", className)}
207
+ {...props}
208
+ >
209
+ {content}
210
+ </div>
211
+ );
212
+ }
213
+
214
+ export {
215
+ Field,
216
+ FieldLabel,
217
+ FieldDescription,
218
+ FieldError,
219
+ FieldGroup,
220
+ FieldLegend,
221
+ FieldSeparator,
222
+ FieldSet,
223
+ FieldContent,
224
+ FieldTitle,
225
+ };
@@ -0,0 +1,82 @@
1
+ "use client";
2
+
3
+ export * from "./animated-loader";
4
+ export { Button, buttonVariants } from "./button";
5
+ export {
6
+ ButtonGroup,
7
+ ButtonGroupSeparator,
8
+ ButtonGroupText,
9
+ buttonGroupVariants,
10
+ } from "./button-group";
11
+ export { Checkbox } from "./checkbox";
12
+ export {
13
+ EditableSliderValueLabel,
14
+ getInferredValueLabelWidthReference,
15
+ getNumericValueLabelWidthReference,
16
+ } from "./editable-slider-value-label";
17
+ export {
18
+ Field,
19
+ FieldContent,
20
+ FieldDescription,
21
+ FieldError,
22
+ FieldGroup,
23
+ FieldLabel,
24
+ FieldLegend,
25
+ FieldSeparator,
26
+ FieldSet,
27
+ FieldTitle,
28
+ } from "./field";
29
+ export { Input, inputVariants } from "./input";
30
+ export {
31
+ InputGroup,
32
+ InputGroupAddon,
33
+ InputGroupButton,
34
+ InputGroupInput,
35
+ InputGroupText,
36
+ InputGroupTextarea,
37
+ } from "./input-group";
38
+ export { Label } from "./label";
39
+ export {
40
+ PortalLayerContainerProvider,
41
+ usePortalLayerContainer,
42
+ type PortalLayerContainer,
43
+ } from "./portal-layer-context";
44
+ export {
45
+ Popover,
46
+ PopoverContent,
47
+ PopoverDescription,
48
+ PopoverHeader,
49
+ PopoverSurface,
50
+ PopoverTitle,
51
+ PopoverTrigger,
52
+ } from "./popover";
53
+ export { PrimitiveArrowIcon } from "./primitive-arrow-icon";
54
+ export type { PrimitiveArrowDirection } from "./primitive-arrow-icon";
55
+ export { ScrollFade } from "./scroll-fade";
56
+ export * from "./selection-state";
57
+ export {
58
+ Select,
59
+ SelectContent,
60
+ SelectGroup,
61
+ SelectItem,
62
+ SelectLabel,
63
+ SelectScrollDownButton,
64
+ SelectScrollUpButton,
65
+ SelectSeparator,
66
+ SelectTrigger,
67
+ SelectTriggerButton,
68
+ SelectValue,
69
+ } from "./select";
70
+ export { Separator } from "./separator";
71
+ export { Slider, SliderInteractionProvider } from "./slider";
72
+ export type { SliderInteractionChangeDetails } from "./slider";
73
+ export { Switch } from "./switch";
74
+ export { Textarea, textareaVariants } from "./textarea";
75
+ export {
76
+ Tooltip,
77
+ TooltipContent,
78
+ TooltipProvider,
79
+ TooltipTrigger,
80
+ } from "./tooltip";
81
+ export { Toggle, toggleVariants } from "./toggle";
82
+ export { ToggleGroup, ToggleGroupItem } from "./toggle-group";