@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,408 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Select as SelectPrimitive } from "@base-ui/react/select";
5
+ import { cva, type VariantProps } from "class-variance-authority";
6
+
7
+ import { outlineControlSurfaceClassName } from "../../lib/control-outline";
8
+ import { cn } from "../../lib/utils";
9
+ import {
10
+ PortalLayerContainerProvider,
11
+ usePortalLayerContainer,
12
+ } from "./portal-layer-context";
13
+ import { PrimitiveArrowIcon } from "./primitive-arrow-icon";
14
+ import { ScrollFade } from "./scroll-fade";
15
+ import { pressedSelectedItemClassName } from "./selection-state";
16
+ import { CheckIcon } from "@phosphor-icons/react";
17
+
18
+ const Select = SelectPrimitive.Root;
19
+
20
+ const dropdownHoverBorderClassName =
21
+ "[&:not(:focus):not([aria-expanded=true]):not([data-open]):not([data-popup-open]):not([data-state=open]):hover]:!border-[color:color-mix(in_oklab,var(--border)_20%,transparent)]";
22
+
23
+ const selectTriggerVariants = cva(
24
+ "flex w-fit cursor-pointer items-center justify-between gap-1.5 border whitespace-nowrap font-medium transition-colors outline-none select-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-[color:var(--destructive)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:aria-invalid:border-[color:color-mix(in_oklab,var(--destructive)_50%,transparent)] [&_svg]:pointer-events-none [&_svg]:shrink-0",
25
+ {
26
+ variants: {
27
+ placeholderTone: {
28
+ default: "data-placeholder:text-[color:var(--foreground)]",
29
+ muted: "data-placeholder:text-[color:var(--muted-foreground)]",
30
+ },
31
+ radius: {
32
+ default: "rounded-lg",
33
+ full: "rounded-full",
34
+ },
35
+ variant: {
36
+ default: `${outlineControlSurfaceClassName} ${dropdownHoverBorderClassName} focus-visible:border-[color:color-mix(in_oklab,var(--border)_30%,transparent)] aria-expanded:border-[color:color-mix(in_oklab,var(--border)_30%,transparent)] data-popup-open:border-[color:color-mix(in_oklab,var(--border)_30%,transparent)] data-open:border-[color:color-mix(in_oklab,var(--border)_30%,transparent)]`,
37
+ ghost: `border-transparent bg-transparent bg-clip-border text-[color:var(--foreground)] focus-visible:border-[color:var(--ring)] focus-visible:ring-2 focus-visible:ring-[color:color-mix(in_oklab,var(--ring)_30%,transparent)] hover:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] hover:text-[color:var(--foreground)] active:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] active:text-[color:var(--foreground)] aria-expanded:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] aria-expanded:text-[color:var(--foreground)] data-open:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] data-open:text-[color:var(--foreground)] data-popup-open:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] data-popup-open:text-[color:var(--foreground)] data-[state=open]:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] data-[state=open]:text-[color:var(--foreground)] ${pressedSelectedItemClassName}`,
38
+ },
39
+ size: {
40
+ sm: "h-6 gap-1 px-1.5 pr-1 py-0 text-xs/relaxed *:data-[slot=select-value]:gap-1 [&_svg:not([class*='size-'])]:size-3",
41
+ default:
42
+ "h-7 px-2 py-0.5 text-xs/relaxed [&_svg:not([class*='size-'])]:size-3.5",
43
+ lg: "h-8 px-2.5 py-1 text-sm/relaxed [&_svg:not([class*='size-'])]:size-4",
44
+ xl: "h-10 px-3 py-1.5 text-base/relaxed [&_svg:not([class*='size-'])]:size-4",
45
+ },
46
+ },
47
+ defaultVariants: {
48
+ placeholderTone: "muted",
49
+ radius: "default",
50
+ variant: "default",
51
+ size: "default",
52
+ },
53
+ },
54
+ );
55
+
56
+ function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
57
+ return (
58
+ <SelectPrimitive.Group
59
+ data-slot="select-group"
60
+ className={cn("scroll-my-1 p-1", className)}
61
+ {...props}
62
+ />
63
+ );
64
+ }
65
+
66
+ function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
67
+ return (
68
+ <SelectPrimitive.Value
69
+ data-slot="select-value"
70
+ className={cn("flex min-w-0 flex-1 text-left", className)}
71
+ {...props}
72
+ />
73
+ );
74
+ }
75
+
76
+ function SelectTrigger({
77
+ className,
78
+ placeholderTone = "muted",
79
+ radius = "default",
80
+ size = "default",
81
+ variant = "default",
82
+ children,
83
+ ...props
84
+ }: SelectPrimitive.Trigger.Props & VariantProps<typeof selectTriggerVariants>) {
85
+ return (
86
+ <SelectPrimitive.Trigger
87
+ data-placeholder-tone={placeholderTone}
88
+ data-radius={radius}
89
+ data-slot="select-trigger"
90
+ data-size={size}
91
+ data-variant={variant}
92
+ className={cn(
93
+ "group/select-trigger",
94
+ selectTriggerVariants({ placeholderTone, radius, size, variant }),
95
+ className,
96
+ )}
97
+ {...props}
98
+ >
99
+ {children}
100
+ <SelectPrimitive.Icon
101
+ render={
102
+ <PrimitiveArrowIcon openClassName="group-aria-expanded/select-trigger:rotate-180 group-data-popup-open/select-trigger:rotate-180 group-data-open/select-trigger:rotate-180" />
103
+ }
104
+ />
105
+ </SelectPrimitive.Trigger>
106
+ );
107
+ }
108
+
109
+ const selectTriggerArrowOpenClassName =
110
+ "group-aria-expanded/select-trigger:rotate-180 group-data-popup-open/select-trigger:rotate-180 group-data-open/select-trigger:rotate-180";
111
+
112
+ const SelectTriggerButton = React.forwardRef<
113
+ HTMLButtonElement,
114
+ React.ComponentProps<"button"> &
115
+ VariantProps<typeof selectTriggerVariants> & {
116
+ open?: boolean;
117
+ }
118
+ >(function SelectTriggerButton(
119
+ {
120
+ children,
121
+ className,
122
+ open,
123
+ placeholderTone = "muted",
124
+ radius = "default",
125
+ size = "default",
126
+ variant = "default",
127
+ ...props
128
+ },
129
+ ref,
130
+ ) {
131
+ return (
132
+ <button
133
+ data-open={open ? "" : undefined}
134
+ data-placeholder-tone={placeholderTone}
135
+ data-radius={radius}
136
+ data-slot="select-trigger"
137
+ data-size={size}
138
+ data-variant={variant}
139
+ className={cn(
140
+ "group/select-trigger",
141
+ selectTriggerVariants({ placeholderTone, radius, size, variant }),
142
+ className,
143
+ )}
144
+ ref={ref}
145
+ {...props}
146
+ >
147
+ {children}
148
+ <PrimitiveArrowIcon openClassName={selectTriggerArrowOpenClassName} />
149
+ </button>
150
+ );
151
+ });
152
+
153
+ function SelectContent({
154
+ className,
155
+ children,
156
+ side = "bottom",
157
+ sideOffset = 4,
158
+ align = "center",
159
+ alignOffset = 0,
160
+ alignItemWithTrigger = true,
161
+ ...props
162
+ }: SelectPrimitive.Popup.Props &
163
+ Pick<
164
+ SelectPrimitive.Positioner.Props,
165
+ "align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger"
166
+ >) {
167
+ const resolvedContainer = usePortalLayerContainer();
168
+ const portalNodeRef = React.useRef<HTMLDivElement | null>(null);
169
+
170
+ return (
171
+ <SelectPrimitive.Portal container={resolvedContainer} ref={portalNodeRef}>
172
+ <PortalLayerContainerProvider container={portalNodeRef}>
173
+ <SelectPrimitive.Positioner
174
+ side={side}
175
+ sideOffset={sideOffset}
176
+ align={align}
177
+ alignOffset={alignOffset}
178
+ alignItemWithTrigger={alignItemWithTrigger}
179
+ className="isolate z-50"
180
+ >
181
+ <SelectPrimitive.Popup
182
+ data-slot="select-content"
183
+ data-align-trigger={alignItemWithTrigger}
184
+ className={cn(
185
+ "floating-popup-surface relative isolate z-50 max-h-(--available-height) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg border text-[color:var(--popover-foreground)] duration-100 data-[align-trigger=true]:w-(--anchor-width) data-[align-trigger=true]:animate-none data-[align-trigger=false]:w-max data-[align-trigger=false]:min-w-(--anchor-width) 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",
186
+ "floating-popup-surface relative isolate z-50 max-h-(--available-height) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg border popup-text-xs-plus text-[color:var(--popover-foreground)] duration-100 data-[align-trigger=true]:w-(--anchor-width) data-[align-trigger=true]:animate-none data-[align-trigger=false]:w-max data-[align-trigger=false]:min-w-(--anchor-width) 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:zoom-out-95",
187
+ className,
188
+ )}
189
+ {...props}
190
+ >
191
+ <SelectScrollUpButton />
192
+ <SelectPrimitive.List>{children}</SelectPrimitive.List>
193
+ <SelectScrollDownButton />
194
+ </SelectPrimitive.Popup>
195
+ </SelectPrimitive.Positioner>
196
+ </PortalLayerContainerProvider>
197
+ </SelectPrimitive.Portal>
198
+ );
199
+ }
200
+
201
+ function SelectLabel({
202
+ className,
203
+ ...props
204
+ }: SelectPrimitive.GroupLabel.Props) {
205
+ return (
206
+ <SelectPrimitive.GroupLabel
207
+ data-slot="select-label"
208
+ className={cn(
209
+ "px-2 py-1.5 popup-text-xs-plus text-[color:color-mix(in_oklab,var(--foreground)_60%,transparent)]",
210
+ className,
211
+ )}
212
+ {...props}
213
+ />
214
+ );
215
+ }
216
+
217
+ function getTextTitle(
218
+ title: unknown,
219
+ children: React.ReactNode,
220
+ ): string | undefined {
221
+ if (typeof title === "string" && title.length > 0) {
222
+ return title;
223
+ }
224
+
225
+ if (typeof children === "string" || typeof children === "number") {
226
+ return String(children);
227
+ }
228
+
229
+ return undefined;
230
+ }
231
+
232
+ function useOverflowTitle(
233
+ viewportRef: React.RefObject<HTMLDivElement | null>,
234
+ textTitle: string | undefined,
235
+ ): string | undefined {
236
+ const [overflowing, setOverflowing] = React.useState(false);
237
+
238
+ React.useEffect(() => {
239
+ const viewport = viewportRef.current;
240
+
241
+ if (!viewport || !textTitle) {
242
+ setOverflowing(false);
243
+ return undefined;
244
+ }
245
+
246
+ const updateOverflow = () => {
247
+ setOverflowing(viewport.scrollWidth > viewport.clientWidth + 1);
248
+ };
249
+
250
+ updateOverflow();
251
+
252
+ if (typeof ResizeObserver === "undefined") {
253
+ return undefined;
254
+ }
255
+
256
+ const observer = new ResizeObserver(updateOverflow);
257
+ observer.observe(viewport);
258
+
259
+ if (viewport.firstElementChild instanceof HTMLElement) {
260
+ observer.observe(viewport.firstElementChild);
261
+ }
262
+
263
+ return () => {
264
+ observer.disconnect();
265
+ };
266
+ }, [textTitle, viewportRef]);
267
+
268
+ return overflowing ? textTitle : undefined;
269
+ }
270
+
271
+ function SelectItemTextContent({
272
+ children,
273
+ title,
274
+ }: {
275
+ children: React.ReactNode;
276
+ title: string | undefined;
277
+ }): React.JSX.Element {
278
+ const viewportRef = React.useRef<HTMLDivElement>(null);
279
+ const resolvedTitle = useOverflowTitle(viewportRef, title);
280
+
281
+ if (!title) {
282
+ return (
283
+ <span className="min-w-0 overflow-hidden text-ellipsis whitespace-nowrap">
284
+ {children}
285
+ </span>
286
+ );
287
+ }
288
+
289
+ return (
290
+ <ScrollFade
291
+ className="no-scrollbar min-w-0"
292
+ containerClassName="min-w-0 flex-1"
293
+ preset="compact"
294
+ side="right"
295
+ viewportRef={viewportRef}
296
+ watch={[title]}
297
+ >
298
+ <span
299
+ className="block min-w-max whitespace-nowrap pr-2"
300
+ title={resolvedTitle}
301
+ >
302
+ {children}
303
+ </span>
304
+ </ScrollFade>
305
+ );
306
+ }
307
+
308
+ function SelectItem({
309
+ className,
310
+ children,
311
+ title,
312
+ ...props
313
+ }: SelectPrimitive.Item.Props) {
314
+ const textTitle = getTextTitle(title, children);
315
+
316
+ return (
317
+ <SelectPrimitive.Item
318
+ data-slot="select-item"
319
+ className={cn(
320
+ "relative flex min-h-7 w-full cursor-pointer items-center gap-2 rounded-md py-1 pr-8 pl-2 popup-text-xs-plus leading-normal tracking-tight font-medium outline-hidden select-none hover:bg-[color:color-mix(in_oklab,var(--foreground)_5%,transparent)] focus:bg-[color:color-mix(in_oklab,var(--foreground)_5%,transparent)] focus:text-[color:var(--accent-foreground)] not-data-[variant=destructive]:focus:**:text-[color:var(--accent-foreground)] data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
321
+ className,
322
+ )}
323
+ {...props}
324
+ >
325
+ <SelectPrimitive.ItemText
326
+ className="flex min-w-0 flex-1 gap-2 overflow-hidden whitespace-nowrap"
327
+ data-slot="select-item-text"
328
+ >
329
+ <SelectItemTextContent title={textTitle}>
330
+ {children}
331
+ </SelectItemTextContent>
332
+ </SelectPrimitive.ItemText>
333
+ <SelectPrimitive.ItemIndicator
334
+ render={
335
+ <span className="pointer-events-none absolute right-2 flex items-center justify-center" />
336
+ }
337
+ >
338
+ <CheckIcon className="pointer-events-none" />
339
+ </SelectPrimitive.ItemIndicator>
340
+ </SelectPrimitive.Item>
341
+ );
342
+ }
343
+
344
+ function SelectSeparator({
345
+ className,
346
+ ...props
347
+ }: SelectPrimitive.Separator.Props) {
348
+ return (
349
+ <SelectPrimitive.Separator
350
+ data-slot="select-separator"
351
+ className={cn(
352
+ "floating-popup-separator pointer-events-none -mx-1 my-1 h-px",
353
+ className,
354
+ )}
355
+ {...props}
356
+ />
357
+ );
358
+ }
359
+
360
+ function SelectScrollUpButton({
361
+ className,
362
+ ...props
363
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {
364
+ return (
365
+ <SelectPrimitive.ScrollUpArrow
366
+ data-slot="select-scroll-up-button"
367
+ className={cn(
368
+ "floating-popup-fill top-0 z-10 flex w-full cursor-pointer items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-3.5",
369
+ className,
370
+ )}
371
+ {...props}
372
+ >
373
+ <PrimitiveArrowIcon direction="up" />
374
+ </SelectPrimitive.ScrollUpArrow>
375
+ );
376
+ }
377
+
378
+ function SelectScrollDownButton({
379
+ className,
380
+ ...props
381
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {
382
+ return (
383
+ <SelectPrimitive.ScrollDownArrow
384
+ data-slot="select-scroll-down-button"
385
+ className={cn(
386
+ "floating-popup-fill bottom-0 z-10 flex w-full cursor-pointer items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-3.5",
387
+ className,
388
+ )}
389
+ {...props}
390
+ >
391
+ <PrimitiveArrowIcon />
392
+ </SelectPrimitive.ScrollDownArrow>
393
+ );
394
+ }
395
+
396
+ export {
397
+ Select,
398
+ SelectContent,
399
+ SelectGroup,
400
+ SelectItem,
401
+ SelectLabel,
402
+ SelectScrollDownButton,
403
+ SelectScrollUpButton,
404
+ SelectSeparator,
405
+ SelectTrigger,
406
+ SelectTriggerButton,
407
+ SelectValue,
408
+ };
@@ -0,0 +1,31 @@
1
+ export const selectedItemSurfaceClassName =
2
+ "bg-[color:color-mix(in_oklab,var(--link)_12%,transparent)]";
3
+
4
+ export const selectedItemBorderClassName =
5
+ "border-[color:color-mix(in_oklab,var(--border)_10%,transparent)]";
6
+
7
+ export const importantSelectedItemSurfaceClassName =
8
+ "!bg-[color:color-mix(in_oklab,var(--link)_12%,transparent)]";
9
+
10
+ export const importantSelectedItemBorderClassName =
11
+ "!border-[color:color-mix(in_oklab,var(--border)_10%,transparent)]";
12
+
13
+ export const hoverImportantSelectedItemSurfaceClassName =
14
+ "hover:!bg-[color:color-mix(in_oklab,var(--link)_12%,transparent)]";
15
+
16
+ export const hoverImportantSelectedItemBorderClassName =
17
+ "hover:!border-[color:color-mix(in_oklab,var(--border)_10%,transparent)]";
18
+
19
+ export const selectedItemTextClassName = "text-[color:var(--foreground)]";
20
+
21
+ export const pressedSelectedItemClassName =
22
+ "aria-pressed:border-[color:color-mix(in_oklab,var(--border)_10%,transparent)] aria-pressed:bg-[color:color-mix(in_oklab,var(--link)_12%,transparent)] aria-pressed:text-[color:var(--foreground)] data-[pressed]:border-[color:color-mix(in_oklab,var(--border)_10%,transparent)] data-[pressed]:bg-[color:color-mix(in_oklab,var(--link)_12%,transparent)] data-[pressed]:text-[color:var(--foreground)]";
23
+
24
+ export const toggleSelectedItemClassName =
25
+ "aria-pressed:border-[color:color-mix(in_oklab,var(--border)_10%,transparent)] aria-pressed:bg-[color:color-mix(in_oklab,var(--link)_12%,transparent)] aria-pressed:text-[color:var(--foreground)] data-[pressed]:border-[color:color-mix(in_oklab,var(--border)_10%,transparent)] data-[pressed]:bg-[color:color-mix(in_oklab,var(--link)_12%,transparent)] data-[pressed]:text-[color:var(--foreground)] data-[state=on]:border-[color:color-mix(in_oklab,var(--border)_10%,transparent)] data-[state=on]:bg-[color:color-mix(in_oklab,var(--link)_12%,transparent)] data-[state=on]:text-[color:var(--foreground)]";
26
+
27
+ export const activeSelectedItemClassName =
28
+ "data-active:border-[color:color-mix(in_oklab,var(--border)_10%,transparent)] data-active:bg-[color:color-mix(in_oklab,var(--link)_12%,transparent)] data-active:text-[color:var(--foreground)]";
29
+
30
+ export const checkedSelectedItemClassName =
31
+ "data-checked:border-[color:color-mix(in_oklab,var(--border)_10%,transparent)] data-checked:bg-[color:color-mix(in_oklab,var(--link)_12%,transparent)] data-checked:text-[color:var(--foreground)] dark:data-checked:bg-[color:color-mix(in_oklab,var(--link)_12%,transparent)]";
@@ -0,0 +1,21 @@
1
+ "use client";
2
+
3
+ import { Separator as SeparatorPrimitive } from "@base-ui/react/separator";
4
+
5
+ import { cn } from "../../lib/utils";
6
+
7
+ function Separator({ className, orientation = "horizontal", ...props }: SeparatorPrimitive.Props) {
8
+ return (
9
+ <SeparatorPrimitive
10
+ data-slot="separator"
11
+ orientation={orientation}
12
+ className={cn(
13
+ "shrink-0 bg-[color:color-mix(in_oklab,var(--border)_10%,transparent)] data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
14
+ className,
15
+ )}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ export { Separator };
@@ -0,0 +1,4 @@
1
+ "use client";
2
+
3
+ export { Slider, SliderInteractionProvider } from "./slider";
4
+ export type { SliderInteractionChangeDetails } from "./slider";
@@ -0,0 +1,96 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import type { Slider as SliderPrimitive } from "@base-ui/react/slider";
5
+
6
+ type SliderRuntimeValue = number | readonly number[];
7
+ type SliderValue<Value extends number | readonly number[]> = Value extends number ? number : Value;
8
+
9
+ export type SliderInteractionChangeDetails = {
10
+ max: number;
11
+ min: number;
12
+ nextValues: readonly number[];
13
+ previousValues: readonly number[];
14
+ };
15
+
16
+ type SliderInteractionContextValue = {
17
+ onValueChange?: (details: SliderInteractionChangeDetails) => void;
18
+ };
19
+
20
+ type SliderInteractionValueChangeOptions<Value extends number | readonly number[]> = {
21
+ disabled?: boolean;
22
+ handleValueChange: (
23
+ nextValue: SliderValue<Value>,
24
+ eventDetails: SliderPrimitive.Root.ChangeEventDetails,
25
+ ) => void;
26
+ max: number;
27
+ min: number;
28
+ values: readonly number[];
29
+ };
30
+
31
+ const SliderInteractionContext = React.createContext<SliderInteractionContextValue | null>(null);
32
+
33
+ function getRuntimeSliderValues(nextValue: SliderRuntimeValue): number[] {
34
+ return typeof nextValue === "number" ? [nextValue] : [...nextValue];
35
+ }
36
+
37
+ function runtimeValuesMatch(left: readonly number[], right: readonly number[]): boolean {
38
+ return (
39
+ left.length === right.length && left.every((leftValue, index) => leftValue === right[index])
40
+ );
41
+ }
42
+
43
+ export function SliderInteractionProvider({
44
+ children,
45
+ onValueChange,
46
+ }: {
47
+ children: React.ReactNode;
48
+ onValueChange?: (details: SliderInteractionChangeDetails) => void;
49
+ }): React.JSX.Element {
50
+ const contextValue = React.useMemo(() => ({ onValueChange }), [onValueChange]);
51
+
52
+ return (
53
+ <SliderInteractionContext.Provider value={contextValue}>
54
+ {children}
55
+ </SliderInteractionContext.Provider>
56
+ );
57
+ }
58
+
59
+ export function useSliderInteractionValueChange<Value extends number | readonly number[]>({
60
+ disabled,
61
+ handleValueChange,
62
+ max,
63
+ min,
64
+ values,
65
+ }: SliderInteractionValueChangeOptions<Value>): SliderPrimitive.Root.Props<Value>["onValueChange"] {
66
+ const sliderInteraction = React.useContext(SliderInteractionContext);
67
+ const latestRuntimeValuesRef = React.useRef(values);
68
+
69
+ React.useEffect(() => {
70
+ latestRuntimeValuesRef.current = values;
71
+ }, [values]);
72
+
73
+ return React.useCallback(
74
+ (
75
+ nextValue: SliderValue<Value>,
76
+ eventDetails: SliderPrimitive.Root.ChangeEventDetails,
77
+ ) => {
78
+ handleValueChange(nextValue, eventDetails);
79
+
80
+ if (disabled) {
81
+ return;
82
+ }
83
+
84
+ const previousValues = latestRuntimeValuesRef.current;
85
+ const nextValues = getRuntimeSliderValues(nextValue as SliderRuntimeValue);
86
+
87
+ if (runtimeValuesMatch(previousValues, nextValues)) {
88
+ return;
89
+ }
90
+
91
+ sliderInteraction?.onValueChange?.({ max, min, nextValues, previousValues });
92
+ latestRuntimeValuesRef.current = nextValues;
93
+ },
94
+ [disabled, handleValueChange, max, min, sliderInteraction],
95
+ );
96
+ }