@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,503 @@
1
+ "use client";
2
+
3
+ import {
4
+ useCallback,
5
+ useEffect,
6
+ useId,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ type ChangeEvent,
11
+ type KeyboardEvent,
12
+ type MutableRefObject,
13
+ type PointerEvent as ReactPointerEvent,
14
+ type RefObject,
15
+ } from "react";
16
+ import {
17
+ ColorFooter,
18
+ ColorModelSlider,
19
+ ColorSurface,
20
+ getColorSurfaceSliderConfig,
21
+ } from "./style-guide-color-picker-parts";
22
+ import {
23
+ useColorModel,
24
+ useHueHandlers,
25
+ useInteractionState,
26
+ useSurfaceDrag,
27
+ useSurfacePreview,
28
+ type ColorSurfacePosition,
29
+ type DragBounds,
30
+ } from "./style-guide-color-picker-logic";
31
+ import {
32
+ useHexInputHandlers,
33
+ useSurfacePointerDown,
34
+ } from "./style-guide-color-picker-interactions";
35
+ import { hsvToHex, type HsvColor } from "../../../lib/style-guide-color-utils";
36
+ import {
37
+ getColorChannels,
38
+ getColorSurfaceModel,
39
+ rgbChannelsToHex,
40
+ type ColorFormatMode,
41
+ type ColorSurfaceModel,
42
+ } from "./style-guide-color-picker-channel-utils";
43
+
44
+ type StyleGuideColorPickerProps = {
45
+ value: string;
46
+ disabled?: boolean;
47
+ hexInputId?: string;
48
+ hexInputLabel?: string;
49
+ surfaceLabel?: string;
50
+ hueLabel?: string;
51
+ showOpacity?: boolean;
52
+ surfaceClassName?: string;
53
+ onChange: (hex: string) => void;
54
+ onCommit?: () => void;
55
+ onInteractionStateChange?: (isInteracting: boolean) => void;
56
+ };
57
+
58
+ type ColorPickerViewProps = {
59
+ disabled: boolean;
60
+ surfaceLabel: string;
61
+ hueLabel: string;
62
+ hexInputLabel: string;
63
+ showOpacity: boolean;
64
+ surfaceClassName?: string;
65
+ surfaceRef: RefObject<HTMLDivElement | null>;
66
+ resolvedHexInputId: string;
67
+ optimisticColor: HsvColor;
68
+ draftHexValue: string;
69
+ isSurfaceDragging: boolean;
70
+ hueColor: string;
71
+ currentColorHex: string;
72
+ colorFormatMode: ColorFormatMode;
73
+ colorSurfaceModel: ColorSurfaceModel;
74
+ surfacePosition: ColorSurfacePosition | null;
75
+ sliderConfig: ReturnType<typeof getColorSurfaceSliderConfig>;
76
+ sliderHandlers: {
77
+ handleSliderCommit: (nextValue: number) => void;
78
+ handleSliderDragStateChange: (nextIsDragging: boolean) => void;
79
+ handleSliderPreviewChange: (nextValue: number) => void;
80
+ };
81
+ onColorFormatModeChange: (nextMode: ColorFormatMode) => void;
82
+ onSurfacePointerDown: (event: ReactPointerEvent<HTMLDivElement>) => void;
83
+ onHexFocus: () => void;
84
+ onHexChange: (event: ChangeEvent<HTMLInputElement>) => void;
85
+ onHexBlur: () => void;
86
+ onHexKeyDown: (event: KeyboardEvent<HTMLInputElement>) => void;
87
+ onColorValueFocus: () => void;
88
+ onColorValueChange: (nextHex: string) => void;
89
+ onColorValueBlur: () => void;
90
+ };
91
+
92
+ type ColorPickerRefs = {
93
+ surfaceRef: RefObject<HTMLDivElement | null>;
94
+ isHexInputFocusedRef: MutableRefObject<boolean>;
95
+ surfaceBoundsRef: MutableRefObject<DragBounds | null>;
96
+ surfaceDragStartHexRef: MutableRefObject<string | null>;
97
+ surfaceDragStartColorRef: MutableRefObject<HsvColor | null>;
98
+ pendingSurfaceCommitHexRef: MutableRefObject<string | null>;
99
+ pendingSurfaceBaseHexRef: MutableRefObject<string | null>;
100
+ hueDragStartHexRef: MutableRefObject<string | null>;
101
+ };
102
+
103
+ export const DEFAULT_COLOR_FORMAT_MODE = "hsl" satisfies ColorFormatMode;
104
+
105
+ function useColorPickerRefs(): ColorPickerRefs {
106
+ const surfaceRef = useRef<HTMLDivElement | null>(null);
107
+ const isHexInputFocusedRef = useRef(false);
108
+ const surfaceBoundsRef = useRef<DragBounds | null>(null);
109
+ const surfaceDragStartHexRef = useRef<string | null>(null);
110
+ const surfaceDragStartColorRef = useRef<HsvColor | null>(null);
111
+ const pendingSurfaceCommitHexRef = useRef<string | null>(null);
112
+ const pendingSurfaceBaseHexRef = useRef<string | null>(null);
113
+ const hueDragStartHexRef = useRef<string | null>(null);
114
+
115
+ return useMemo(
116
+ () => ({
117
+ surfaceRef,
118
+ isHexInputFocusedRef,
119
+ surfaceBoundsRef,
120
+ surfaceDragStartHexRef,
121
+ surfaceDragStartColorRef,
122
+ pendingSurfaceCommitHexRef,
123
+ pendingSurfaceBaseHexRef,
124
+ hueDragStartHexRef,
125
+ }),
126
+ [],
127
+ );
128
+ }
129
+
130
+ function useRgbBlueHandlers({
131
+ latestHsvRef,
132
+ hueDragStartHexRef,
133
+ applyOptimisticHex,
134
+ setInteractionSourceState,
135
+ emitChange,
136
+ onCommit,
137
+ }: {
138
+ latestHsvRef: MutableRefObject<HsvColor>;
139
+ hueDragStartHexRef: MutableRefObject<string | null>;
140
+ applyOptimisticHex: (nextHex: string) => string | null;
141
+ setInteractionSourceState: (source: "hue", nextIsActive: boolean) => void;
142
+ emitChange: (hex: string) => void;
143
+ onCommit?: () => void;
144
+ }) {
145
+ const handleSliderDragStateChange = useCallback(
146
+ (nextIsDragging: boolean) => {
147
+ if (nextIsDragging) {
148
+ hueDragStartHexRef.current = hsvToHex(latestHsvRef.current);
149
+ setInteractionSourceState("hue", true);
150
+ return;
151
+ }
152
+ setInteractionSourceState("hue", false);
153
+ },
154
+ [hueDragStartHexRef, latestHsvRef, setInteractionSourceState],
155
+ );
156
+
157
+ const applyBlueChannel = useCallback(
158
+ (nextBlue: number) => {
159
+ const channels = getColorChannels(hsvToHex(latestHsvRef.current)).rgb;
160
+ const nextHex = rgbChannelsToHex([
161
+ channels[0],
162
+ channels[1],
163
+ Math.round(Math.max(0, Math.min(255, nextBlue))),
164
+ ]);
165
+
166
+ return applyOptimisticHex(nextHex) ?? nextHex;
167
+ },
168
+ [applyOptimisticHex, latestHsvRef],
169
+ );
170
+
171
+ const handleSliderPreviewChange = useCallback(
172
+ (nextValue: number) => {
173
+ applyBlueChannel(nextValue);
174
+ },
175
+ [applyBlueChannel],
176
+ );
177
+
178
+ const handleSliderCommit = useCallback(
179
+ (nextValue: number) => {
180
+ const nextHex = applyBlueChannel(nextValue);
181
+ const dragStartHex = hueDragStartHexRef.current;
182
+ hueDragStartHexRef.current = null;
183
+ if (!dragStartHex || nextHex === dragStartHex) return;
184
+
185
+ emitChange(nextHex);
186
+ onCommit?.();
187
+ },
188
+ [applyBlueChannel, emitChange, hueDragStartHexRef, onCommit],
189
+ );
190
+
191
+ return { handleSliderCommit, handleSliderDragStateChange, handleSliderPreviewChange };
192
+ }
193
+
194
+ type SurfacePositionOverride = {
195
+ colorModel: ColorSurfaceModel;
196
+ hex: string;
197
+ position: ColorSurfacePosition;
198
+ };
199
+
200
+ function useColorPickerSurfaceDrag(
201
+ refs: ColorPickerRefs,
202
+ model: ReturnType<typeof useColorModel>,
203
+ preview: ReturnType<typeof useSurfacePreview>,
204
+ interaction: ReturnType<typeof useInteractionState>,
205
+ surfaceModelRef: MutableRefObject<ColorSurfaceModel>,
206
+ setSurfacePositionOverride: (
207
+ position: ColorSurfacePosition,
208
+ hex: string,
209
+ surfaceModel: ColorSurfaceModel,
210
+ ) => void,
211
+ isSurfaceDragging: boolean,
212
+ setIsSurfaceDragging: (nextIsDragging: boolean) => void,
213
+ onCommit?: () => void,
214
+ ) {
215
+ const surfaceDragOptions = useMemo(
216
+ () => ({
217
+ isSurfaceDragging,
218
+ setIsSurfaceDragging,
219
+ surfaceBoundsRef: refs.surfaceBoundsRef,
220
+ surfaceDragStartHexRef: refs.surfaceDragStartHexRef,
221
+ surfaceDragStartColorRef: refs.surfaceDragStartColorRef,
222
+ pendingSurfaceCommitHexRef: refs.pendingSurfaceCommitHexRef,
223
+ pendingSurfaceBaseHexRef: refs.pendingSurfaceBaseHexRef,
224
+ latestHsvRef: model.latestHsvRef,
225
+ surfaceModelRef,
226
+ applyOptimisticColor: model.applyOptimisticColor,
227
+ scheduleSurfacePreview: preview.scheduleSurfacePreview,
228
+ flushPendingSurfacePreview: preview.flushPendingSurfacePreview,
229
+ setSurfacePositionOverride,
230
+ setInteractionSourceState: interaction.setInteractionSourceState,
231
+ emitChange: model.emitChange,
232
+ onCommit,
233
+ }),
234
+ [
235
+ interaction.setInteractionSourceState,
236
+ isSurfaceDragging,
237
+ model,
238
+ onCommit,
239
+ preview,
240
+ refs,
241
+ setSurfacePositionOverride,
242
+ setIsSurfaceDragging,
243
+ surfaceModelRef,
244
+ ],
245
+ );
246
+ useSurfaceDrag(surfaceDragOptions);
247
+ }
248
+
249
+ function useColorValueHandlers(
250
+ refs: ColorPickerRefs,
251
+ model: ReturnType<typeof useColorModel>,
252
+ interaction: ReturnType<typeof useInteractionState>,
253
+ onCommit?: () => void,
254
+ ) {
255
+ const { setInteractionSourceState } = interaction;
256
+ const handleColorValueFocus = useCallback(() => {
257
+ refs.isHexInputFocusedRef.current = true;
258
+ setInteractionSourceState("hex", true);
259
+ }, [refs.isHexInputFocusedRef, setInteractionSourceState]);
260
+ const handleColorValueChange = useCallback(
261
+ (nextHex: string) => {
262
+ const nextDraftHex = nextHex.toUpperCase();
263
+
264
+ refs.isHexInputFocusedRef.current = true;
265
+ setInteractionSourceState("hex", true);
266
+ model.setDraftHexValue(nextDraftHex);
267
+ model.applyOptimisticHex(nextDraftHex);
268
+ model.emitChange(nextDraftHex);
269
+ },
270
+ [model, refs.isHexInputFocusedRef, setInteractionSourceState],
271
+ );
272
+ const handleColorValueBlur = useCallback(() => {
273
+ refs.isHexInputFocusedRef.current = false;
274
+ setInteractionSourceState("hex", false);
275
+ onCommit?.();
276
+ }, [onCommit, refs.isHexInputFocusedRef, setInteractionSourceState]);
277
+
278
+ return {
279
+ handleColorValueBlur,
280
+ handleColorValueChange,
281
+ handleColorValueFocus,
282
+ };
283
+ }
284
+
285
+ function useColorPickerController({
286
+ value,
287
+ disabled = false,
288
+ hexInputId,
289
+ hexInputLabel = "Hex color",
290
+ surfaceLabel = "Color saturation and brightness",
291
+ hueLabel = "Color hue",
292
+ showOpacity = false,
293
+ surfaceClassName,
294
+ onChange,
295
+ onCommit,
296
+ onInteractionStateChange,
297
+ }: StyleGuideColorPickerProps): ColorPickerViewProps {
298
+ const generatedHexInputId = useId();
299
+ const [isSurfaceDragging, setIsSurfaceDragging] = useState(false);
300
+ const [colorFormatMode, setColorFormatMode] = useState<ColorFormatMode>(
301
+ DEFAULT_COLOR_FORMAT_MODE,
302
+ );
303
+ const colorSurfaceModel = getColorSurfaceModel(colorFormatMode);
304
+ const surfaceModelRef = useRef<ColorSurfaceModel>(colorSurfaceModel);
305
+ const [surfacePositionOverride, setSurfacePositionOverrideState] =
306
+ useState<SurfacePositionOverride | null>(null);
307
+ const refs = useColorPickerRefs();
308
+ const interaction = useInteractionState(onInteractionStateChange);
309
+ const model = useColorModel({
310
+ value,
311
+ isSurfaceDragging,
312
+ hueDragStartHexRef: refs.hueDragStartHexRef,
313
+ isHexInputFocusedRef: refs.isHexInputFocusedRef,
314
+ pendingSurfaceCommitHexRef: refs.pendingSurfaceCommitHexRef,
315
+ pendingSurfaceBaseHexRef: refs.pendingSurfaceBaseHexRef,
316
+ onChange,
317
+ });
318
+ const preview = useSurfacePreview(model.emitChange);
319
+ useEffect(() => {
320
+ surfaceModelRef.current = colorSurfaceModel;
321
+ setSurfacePositionOverrideState(null);
322
+ }, [colorSurfaceModel]);
323
+ const setSurfacePositionOverride = useCallback(
324
+ (position: ColorSurfacePosition, hex: string, surfaceModel: ColorSurfaceModel) => {
325
+ setSurfacePositionOverrideState({ colorModel: surfaceModel, hex, position });
326
+ },
327
+ [],
328
+ );
329
+ const hueHandlers = useHueHandlers({
330
+ latestHsvRef: model.latestHsvRef,
331
+ hueDragStartHexRef: refs.hueDragStartHexRef,
332
+ applyOptimisticColor: model.applyOptimisticColor,
333
+ setInteractionSourceState: interaction.setInteractionSourceState,
334
+ emitChange: model.emitChange,
335
+ onCommit,
336
+ });
337
+ const rgbBlueHandlers = useRgbBlueHandlers({
338
+ latestHsvRef: model.latestHsvRef,
339
+ hueDragStartHexRef: refs.hueDragStartHexRef,
340
+ applyOptimisticHex: model.applyOptimisticHex,
341
+ setInteractionSourceState: interaction.setInteractionSourceState,
342
+ emitChange: model.emitChange,
343
+ onCommit,
344
+ });
345
+ useColorPickerSurfaceDrag(
346
+ refs,
347
+ model,
348
+ preview,
349
+ interaction,
350
+ surfaceModelRef,
351
+ setSurfacePositionOverride,
352
+ isSurfaceDragging,
353
+ setIsSurfaceDragging,
354
+ onCommit,
355
+ );
356
+ const hexHandlers = useHexInputHandlers({
357
+ isHexInputFocusedRef: refs.isHexInputFocusedRef,
358
+ draftHexValue: model.draftHexValue,
359
+ normalizedHex: model.normalizedHex,
360
+ latestHsvRef: model.latestHsvRef,
361
+ setDraftHexValue: model.setDraftHexValue,
362
+ applyOptimisticHex: model.applyOptimisticHex,
363
+ emitChange: model.emitChange,
364
+ setInteractionSourceState: interaction.setInteractionSourceState,
365
+ onCommit,
366
+ });
367
+ const colorValueHandlers = useColorValueHandlers(
368
+ refs,
369
+ model,
370
+ interaction,
371
+ onCommit,
372
+ );
373
+ const onSurfacePointerDown = useSurfacePointerDown({
374
+ disabled,
375
+ surfaceRef: refs.surfaceRef,
376
+ surfaceBoundsRef: refs.surfaceBoundsRef,
377
+ surfaceDragStartHexRef: refs.surfaceDragStartHexRef,
378
+ surfaceDragStartColorRef: refs.surfaceDragStartColorRef,
379
+ pendingSurfacePreviewHexRef: preview.pendingSurfacePreviewHexRef,
380
+ pendingSurfaceCommitHexRef: refs.pendingSurfaceCommitHexRef,
381
+ pendingSurfaceBaseHexRef: refs.pendingSurfaceBaseHexRef,
382
+ latestHsvRef: model.latestHsvRef,
383
+ surfaceModel: colorSurfaceModel,
384
+ clearScheduledSurfacePreview: preview.clearScheduledSurfacePreview,
385
+ applyOptimisticColor: model.applyOptimisticColor,
386
+ setSurfacePositionOverride,
387
+ setInteractionSourceState: interaction.setInteractionSourceState,
388
+ emitChange: model.emitChange,
389
+ setIsSurfaceDragging,
390
+ });
391
+ const currentColorHex = hsvToHex(model.optimisticColor);
392
+ const surfacePosition =
393
+ surfacePositionOverride?.colorModel === colorSurfaceModel &&
394
+ surfacePositionOverride.hex === currentColorHex
395
+ ? surfacePositionOverride.position
396
+ : null;
397
+
398
+ return {
399
+ disabled,
400
+ surfaceLabel,
401
+ hueLabel,
402
+ hexInputLabel,
403
+ showOpacity,
404
+ surfaceClassName,
405
+ surfaceRef: refs.surfaceRef,
406
+ resolvedHexInputId: hexInputId ?? generatedHexInputId,
407
+ optimisticColor: model.optimisticColor,
408
+ draftHexValue: model.draftHexValue,
409
+ isSurfaceDragging,
410
+ hueColor: hsvToHex({ h: model.optimisticColor.h, s: 1, v: 1 }),
411
+ currentColorHex,
412
+ colorFormatMode,
413
+ colorSurfaceModel,
414
+ surfacePosition,
415
+ sliderConfig: getColorSurfaceSliderConfig({
416
+ colorModel: colorSurfaceModel,
417
+ currentColorHex,
418
+ hueLabel,
419
+ optimisticColor: model.optimisticColor,
420
+ }),
421
+ sliderHandlers:
422
+ colorSurfaceModel === "rgb"
423
+ ? rgbBlueHandlers
424
+ : {
425
+ handleSliderCommit: hueHandlers.handleHueCommit,
426
+ handleSliderDragStateChange: hueHandlers.handleHueDragStateChange,
427
+ handleSliderPreviewChange: hueHandlers.handleHuePreviewChange,
428
+ },
429
+ onColorFormatModeChange: setColorFormatMode,
430
+ onSurfacePointerDown,
431
+ onColorValueFocus: colorValueHandlers.handleColorValueFocus,
432
+ onColorValueChange: colorValueHandlers.handleColorValueChange,
433
+ onColorValueBlur: colorValueHandlers.handleColorValueBlur,
434
+ ...hexHandlers,
435
+ };
436
+ }
437
+
438
+ function ColorPickerView(props: ColorPickerViewProps) {
439
+ return (
440
+ <div
441
+ data-slot="style-guide-color-picker"
442
+ className="flex h-full min-h-0 w-full flex-1 flex-col"
443
+ >
444
+ <ColorSurface
445
+ surfaceRef={props.surfaceRef}
446
+ surfaceLabel={props.surfaceLabel}
447
+ surfaceClassName={props.surfaceClassName}
448
+ disabled={props.disabled}
449
+ hueColor={props.hueColor}
450
+ currentColorHex={props.currentColorHex}
451
+ colorModel={props.colorSurfaceModel}
452
+ optimisticColor={props.optimisticColor}
453
+ surfacePosition={props.surfacePosition}
454
+ isSurfaceDragging={props.isSurfaceDragging}
455
+ onPointerDown={props.onSurfacePointerDown}
456
+ onThumbPointerDown={(event) => {
457
+ event.stopPropagation();
458
+ props.onSurfacePointerDown(event);
459
+ }}
460
+ />
461
+ <div
462
+ data-slot="style-guide-color-controls"
463
+ className="flex w-full shrink-0 flex-col"
464
+ >
465
+ <div
466
+ data-slot="style-guide-color-slider-wrap"
467
+ className="flex h-9 w-full shrink-0 items-center px-3"
468
+ >
469
+ <ColorModelSlider
470
+ label={props.sliderConfig.label}
471
+ disabled={props.disabled}
472
+ max={props.sliderConfig.max}
473
+ railBackground={props.sliderConfig.railBackground}
474
+ value={props.sliderConfig.value}
475
+ onDragStateChange={props.sliderHandlers.handleSliderDragStateChange}
476
+ onPreviewChange={props.sliderHandlers.handleSliderPreviewChange}
477
+ onCommit={props.sliderHandlers.handleSliderCommit}
478
+ />
479
+ </div>
480
+ <ColorFooter
481
+ resolvedHexInputId={props.resolvedHexInputId}
482
+ hexInputLabel={props.hexInputLabel}
483
+ disabled={props.disabled}
484
+ draftHexValue={props.draftHexValue}
485
+ onHexFocus={props.onHexFocus}
486
+ onHexChange={props.onHexChange}
487
+ onHexBlur={props.onHexBlur}
488
+ onHexKeyDown={props.onHexKeyDown}
489
+ onColorValueFocus={props.onColorValueFocus}
490
+ onColorValueChange={props.onColorValueChange}
491
+ onColorValueBlur={props.onColorValueBlur}
492
+ mode={props.colorFormatMode}
493
+ onModeChange={props.onColorFormatModeChange}
494
+ showOpacity={props.showOpacity}
495
+ />
496
+ </div>
497
+ </div>
498
+ );
499
+ }
500
+
501
+ export function StyleGuideColorPicker(props: StyleGuideColorPickerProps) {
502
+ return <ColorPickerView {...useColorPickerController(props)} />;
503
+ }
@@ -0,0 +1,43 @@
1
+ "use client";
2
+
3
+ export type ControlOption = {
4
+ label: string;
5
+ value: string;
6
+ };
7
+
8
+ export type ControlChangeHistoryMode = "merge" | "record" | "skip";
9
+
10
+ export type ControlChangeMeta = {
11
+ history?: ControlChangeHistoryMode;
12
+ historyGroup?: string;
13
+ };
14
+
15
+ export type ControlValueChangeHandler<Value> = (
16
+ value: Value,
17
+ meta?: ControlChangeMeta,
18
+ ) => void;
19
+
20
+ export type CurveChannel = "RGB" | "R" | "G" | "B";
21
+
22
+ export type CurvePoint = {
23
+ x: number;
24
+ y: number;
25
+ };
26
+
27
+ export type GradientType = "linear" | "radial" | "angular" | "diamond";
28
+
29
+ export type GradientStop = {
30
+ color: string;
31
+ opacity?: number;
32
+ position: string;
33
+ };
34
+
35
+ export type MixerChannel = "R" | "G" | "B";
36
+
37
+ let controlHistoryGroupIndex = 0;
38
+
39
+ export function createControlHistoryGroupId(scope: string): string {
40
+ controlHistoryGroupIndex += 1;
41
+
42
+ return `${scope}:${controlHistoryGroupIndex}`;
43
+ }