@music-vine/cadence 2.6.2 → 3.0.0

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 (213) hide show
  1. package/README.md +5 -44
  2. package/dist/components/accordion.d.ts +71 -0
  3. package/dist/components/accordion.d.ts.map +1 -0
  4. package/dist/components/accordion.js +2 -2
  5. package/dist/components/accordion.js.map +1 -1
  6. package/dist/components/badge.d.ts +62 -0
  7. package/dist/components/badge.d.ts.map +1 -0
  8. package/dist/components/badge.js +1 -1
  9. package/dist/components/badge.js.map +1 -1
  10. package/dist/components/breadcrumb.d.ts +42 -0
  11. package/dist/components/breadcrumb.d.ts.map +1 -0
  12. package/dist/components/button.d.ts +117 -0
  13. package/dist/components/button.d.ts.map +1 -0
  14. package/dist/components/button.js +3 -3
  15. package/dist/components/button.js.map +1 -1
  16. package/dist/components/card.d.ts +56 -0
  17. package/dist/components/card.d.ts.map +1 -0
  18. package/dist/components/card.js.map +1 -1
  19. package/dist/components/carousel-dots.d.ts +17 -0
  20. package/dist/components/carousel-dots.d.ts.map +1 -0
  21. package/dist/components/carousel-dots.js +1 -1
  22. package/dist/components/carousel-dots.js.map +1 -1
  23. package/dist/components/carousel.d.ts +99 -0
  24. package/dist/components/carousel.d.ts.map +1 -0
  25. package/dist/components/carousel.js +2 -2
  26. package/dist/components/carousel.js.map +1 -1
  27. package/dist/components/checkbox.d.ts +34 -0
  28. package/dist/components/checkbox.d.ts.map +1 -0
  29. package/dist/components/checkbox.js +1 -1
  30. package/dist/components/checkbox.js.map +1 -1
  31. package/dist/components/context-menu.d.ts +126 -0
  32. package/dist/components/context-menu.d.ts.map +1 -0
  33. package/dist/components/context-menu.js +6 -6
  34. package/dist/components/context-menu.js.map +1 -1
  35. package/dist/components/dialog.d.ts +85 -0
  36. package/dist/components/dialog.d.ts.map +1 -0
  37. package/dist/components/dialog.js +1 -1
  38. package/dist/components/dialog.js.map +1 -1
  39. package/dist/components/drawer.d.ts +90 -0
  40. package/dist/components/drawer.d.ts.map +1 -0
  41. package/dist/components/drawer.js.map +1 -1
  42. package/dist/components/index.d.ts +36 -0
  43. package/dist/components/index.d.ts.map +1 -0
  44. package/dist/components/input.d.ts +69 -0
  45. package/dist/components/input.d.ts.map +1 -0
  46. package/dist/components/input.js +61 -57
  47. package/dist/components/input.js.map +2 -2
  48. package/dist/components/label.d.ts +36 -0
  49. package/dist/components/label.d.ts.map +1 -0
  50. package/dist/components/popover.d.ts +61 -0
  51. package/dist/components/popover.d.ts.map +1 -0
  52. package/dist/components/popover.js +1 -1
  53. package/dist/components/popover.js.map +1 -1
  54. package/dist/components/price-tag.d.ts +31 -0
  55. package/dist/components/price-tag.d.ts.map +1 -0
  56. package/dist/components/price-tag.js.map +1 -1
  57. package/dist/components/radio-group.d.ts +15 -0
  58. package/dist/components/radio-group.d.ts.map +1 -0
  59. package/dist/components/radio-group.js +1 -1
  60. package/dist/components/radio-group.js.map +1 -1
  61. package/dist/components/scroll-area.d.ts +33 -0
  62. package/dist/components/scroll-area.d.ts.map +1 -0
  63. package/dist/components/scroll-area.js.map +1 -1
  64. package/dist/components/scroll-drum.d.ts +96 -0
  65. package/dist/components/scroll-drum.d.ts.map +1 -0
  66. package/dist/components/scroll-drum.js +63 -34
  67. package/dist/components/scroll-drum.js.map +2 -2
  68. package/dist/components/select.d.ts +49 -0
  69. package/dist/components/select.d.ts.map +1 -0
  70. package/dist/components/select.js +1 -1
  71. package/dist/components/select.js.map +1 -1
  72. package/dist/components/separator.d.ts +35 -0
  73. package/dist/components/separator.d.ts.map +1 -0
  74. package/dist/components/skeleton.d.ts +44 -0
  75. package/dist/components/skeleton.d.ts.map +1 -0
  76. package/dist/components/slider.d.ts +21 -0
  77. package/dist/components/slider.d.ts.map +1 -0
  78. package/dist/components/slider.js +1 -1
  79. package/dist/components/slider.js.map +1 -1
  80. package/dist/components/stacking-card.d.ts +89 -0
  81. package/dist/components/stacking-card.d.ts.map +1 -0
  82. package/dist/components/stacking-card.js +3 -3
  83. package/dist/components/stacking-card.js.map +2 -2
  84. package/dist/components/tabs.d.ts +46 -0
  85. package/dist/components/tabs.d.ts.map +1 -0
  86. package/dist/components/tabs.js +2 -2
  87. package/dist/components/tabs.js.map +1 -1
  88. package/dist/components/textarea.d.ts +34 -0
  89. package/dist/components/textarea.d.ts.map +1 -0
  90. package/dist/components/toast.d.ts +67 -0
  91. package/dist/components/toast.d.ts.map +1 -0
  92. package/dist/components/toast.js +2 -2
  93. package/dist/components/toast.js.map +2 -2
  94. package/dist/components/toggle-button.d.ts +54 -0
  95. package/dist/components/toggle-button.d.ts.map +1 -0
  96. package/dist/components/toggle-button.js.map +1 -1
  97. package/dist/components/typography/heading.d.ts +20 -0
  98. package/dist/components/typography/heading.d.ts.map +1 -0
  99. package/dist/components/typography/heading.js.map +1 -1
  100. package/dist/components/typography/index.d.ts +5 -0
  101. package/dist/components/typography/index.d.ts.map +1 -0
  102. package/dist/components/typography/list.d.ts +23 -0
  103. package/dist/components/typography/list.d.ts.map +1 -0
  104. package/dist/components/typography/list.js +1 -1
  105. package/dist/components/typography/list.js.map +2 -2
  106. package/dist/components/typography/prose.d.ts +8 -0
  107. package/dist/components/typography/prose.d.ts.map +1 -0
  108. package/dist/components/typography/text.d.ts +13 -0
  109. package/dist/components/typography/text.d.ts.map +1 -0
  110. package/dist/icons/custom/boards-indicator.d.ts +6 -0
  111. package/dist/icons/custom/boards-indicator.d.ts.map +1 -0
  112. package/dist/icons/custom/boards-indicator.js +7 -2
  113. package/dist/icons/custom/boards-indicator.js.map +2 -2
  114. package/dist/icons/custom/download-history.d.ts +5 -0
  115. package/dist/icons/custom/download-history.d.ts.map +1 -0
  116. package/dist/icons/custom/download-history.js +3 -4
  117. package/dist/icons/custom/download-history.js.map +2 -2
  118. package/dist/icons/custom/exclamation-mark-in-octagon.d.ts +5 -0
  119. package/dist/icons/custom/exclamation-mark-in-octagon.d.ts.map +1 -0
  120. package/dist/icons/custom/exclamation-mark-in-octagon.js +6 -3
  121. package/dist/icons/custom/exclamation-mark-in-octagon.js.map +2 -2
  122. package/dist/icons/custom/horizontal-orientation.d.ts +5 -0
  123. package/dist/icons/custom/horizontal-orientation.d.ts.map +1 -0
  124. package/dist/icons/custom/horizontal-orientation.js +4 -3
  125. package/dist/icons/custom/horizontal-orientation.js.map +2 -2
  126. package/dist/icons/custom/lightning-bolt.d.ts +5 -0
  127. package/dist/icons/custom/lightning-bolt.d.ts.map +1 -0
  128. package/dist/icons/custom/lightning-bolt.js +24 -27
  129. package/dist/icons/custom/lightning-bolt.js.map +2 -2
  130. package/dist/icons/custom/music-file.d.ts +5 -0
  131. package/dist/icons/custom/music-file.d.ts.map +1 -0
  132. package/dist/icons/custom/music-file.js +17 -0
  133. package/dist/icons/custom/music-file.js.map +7 -0
  134. package/dist/icons/custom/pin.d.ts +5 -0
  135. package/dist/icons/custom/pin.d.ts.map +1 -0
  136. package/dist/icons/custom/pin.js +4 -1
  137. package/dist/icons/custom/pin.js.map +2 -2
  138. package/dist/icons/custom/premium-star.d.ts +11 -0
  139. package/dist/icons/custom/premium-star.d.ts.map +1 -0
  140. package/dist/icons/custom/premium-star.js +3 -1
  141. package/dist/icons/custom/premium-star.js.map +2 -2
  142. package/dist/icons/custom/social/discord.d.ts +5 -0
  143. package/dist/icons/custom/social/discord.d.ts.map +1 -0
  144. package/dist/icons/custom/social/discord.js +3 -4
  145. package/dist/icons/custom/social/discord.js.map +2 -2
  146. package/dist/icons/custom/social/index.d.ts +4 -0
  147. package/dist/icons/custom/social/index.d.ts.map +1 -0
  148. package/dist/icons/custom/social/tiktok.d.ts +5 -0
  149. package/dist/icons/custom/social/tiktok.d.ts.map +1 -0
  150. package/dist/icons/custom/social/tiktok.js +3 -4
  151. package/dist/icons/custom/social/tiktok.js.map +2 -2
  152. package/dist/icons/custom/social/twitter-x.d.ts +5 -0
  153. package/dist/icons/custom/social/twitter-x.d.ts.map +1 -0
  154. package/dist/icons/custom/social/twitter-x.js +19 -22
  155. package/dist/icons/custom/social/twitter-x.js.map +2 -2
  156. package/dist/icons/custom/square-aspect-ratio.d.ts +5 -0
  157. package/dist/icons/custom/square-aspect-ratio.d.ts.map +1 -0
  158. package/dist/icons/custom/square-aspect-ratio.js +4 -1
  159. package/dist/icons/custom/square-aspect-ratio.js.map +2 -2
  160. package/dist/icons/custom/tick-in-circle.d.ts +8 -0
  161. package/dist/icons/custom/tick-in-circle.d.ts.map +1 -0
  162. package/dist/icons/custom/tick-in-circle.js +25 -25
  163. package/dist/icons/custom/tick-in-circle.js.map +2 -2
  164. package/dist/icons/custom/tick-small.d.ts +5 -0
  165. package/dist/icons/custom/tick-small.d.ts.map +1 -0
  166. package/dist/icons/custom/tick-small.js +8 -10
  167. package/dist/icons/custom/tick-small.js.map +2 -2
  168. package/dist/icons/custom/tick.d.ts +5 -0
  169. package/dist/icons/custom/tick.d.ts.map +1 -0
  170. package/dist/icons/custom/tick.js +2 -2
  171. package/dist/icons/custom/tick.js.map +2 -2
  172. package/dist/icons/custom/types.d.ts +3 -0
  173. package/dist/icons/custom/types.d.ts.map +1 -0
  174. package/dist/icons/custom/types.js +1 -0
  175. package/dist/icons/custom/types.js.map +7 -0
  176. package/dist/icons/custom/ultra-wide-aspect-ratio.d.ts +5 -0
  177. package/dist/icons/custom/ultra-wide-aspect-ratio.d.ts.map +1 -0
  178. package/dist/icons/custom/ultra-wide-aspect-ratio.js +4 -1
  179. package/dist/icons/custom/ultra-wide-aspect-ratio.js.map +2 -2
  180. package/dist/icons/custom/uppbeat-credit.d.ts +5 -0
  181. package/dist/icons/custom/uppbeat-credit.d.ts.map +1 -0
  182. package/dist/icons/custom/uppbeat-credit.js +2 -4
  183. package/dist/icons/custom/uppbeat-credit.js.map +2 -2
  184. package/dist/icons/custom/vertical-orientation.d.ts +5 -0
  185. package/dist/icons/custom/vertical-orientation.d.ts.map +1 -0
  186. package/dist/icons/custom/vertical-orientation.js +4 -1
  187. package/dist/icons/custom/vertical-orientation.js.map +2 -2
  188. package/dist/icons/custom/view-credit-note.d.ts +5 -0
  189. package/dist/icons/custom/view-credit-note.d.ts.map +1 -0
  190. package/dist/icons/custom/view-credit-note.js +4 -1
  191. package/dist/icons/custom/view-credit-note.js.map +2 -2
  192. package/dist/icons/index.d.ts +28 -0
  193. package/dist/icons/index.d.ts.map +1 -0
  194. package/dist/icons/index.js +4 -5
  195. package/dist/icons/index.js.map +2 -2
  196. package/dist/index.d.ts +19 -0
  197. package/dist/index.d.ts.map +1 -0
  198. package/dist/lib/utils.d.ts +12 -0
  199. package/dist/lib/utils.d.ts.map +1 -0
  200. package/dist/lib/utils.js +7 -17
  201. package/dist/lib/utils.js.map +2 -2
  202. package/dist/styles/index.css +16 -3
  203. package/dist/styles/storybook.css +2 -2
  204. package/dist/test/setup.d.ts +2 -0
  205. package/dist/test/setup.d.ts.map +1 -0
  206. package/dist/theme/index.d.ts +142 -0
  207. package/dist/theme/index.d.ts.map +1 -0
  208. package/dist/theme/index.js +1 -1
  209. package/dist/theme/index.js.map +2 -2
  210. package/package.json +4 -9
  211. package/{tailwind.config.v4.css → tailwind.config.css} +35 -14
  212. package/dist/styles/index.v4.css +0 -49
  213. package/tailwind.config.ts +0 -313
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/components/scroll-area.tsx"],
4
- "sourcesContent": ["/**\n * @module ScrollArea\n *\n * Custom scrollable container with styled scrollbars and optional fade indicators.\n * Built on Radix UI ScrollArea primitive.\n *\n * @see {@link https://ui.shadcn.com/docs/components/scroll-area Shadcn ScrollArea}\n * @see {@link https://www.radix-ui.com/primitives/docs/components/scroll-area Radix ScrollArea}\n *\n * @example\n * // Basic scroll area\n * <ScrollArea className=\"h-72 w-48\">\n * <div className=\"p-4\">\n * {longContent}\n * </div>\n * </ScrollArea>\n *\n * @example\n * // With fade indicators\n * <ScrollArea className=\"h-48\" fadeAway=\"y\">\n * {content}\n * </ScrollArea>\n *\n * @example\n * // Hidden scrollbar\n * <ScrollArea className=\"h-48\" hideScrollBar>\n * {content}\n * </ScrollArea>\n */\nimport throttle from \"lodash/throttle\";\nimport { ScrollArea as ScrollAreaPrimitive } from \"radix-ui\";\nimport type { Ref } from \"react\";\nimport * as React from \"react\";\n\nimport { cn } from \"../lib/utils\";\n\n/** Styled scrollbar component for vertical or horizontal scrolling. */\nconst ScrollBar = ({\n className,\n orientation = \"vertical\",\n ref,\n ...props\n}: React.ComponentPropsWithoutRef<\n typeof ScrollAreaPrimitive.ScrollAreaScrollbar\n> & {\n ref?: Ref<React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>>;\n}) => (\n <ScrollAreaPrimitive.ScrollAreaScrollbar\n className={cn(\n \"flex touch-none select-none transition-colors\",\n orientation === \"vertical\" &&\n \"h-full w-2.5 border-0 border-l border-l-transparent border-solid p-[1px]\",\n orientation === \"horizontal\" &&\n \"h-2.5 flex-col border-0 border-t border-t-transparent border-solid p-[1px]\",\n className\n )}\n orientation={orientation}\n ref={ref}\n {...props}\n >\n <ScrollAreaPrimitive.ScrollAreaThumb className=\"relative flex-1 rounded-full bg-gray-200 dark:bg-gray-800\" />\n </ScrollAreaPrimitive.ScrollAreaScrollbar>\n);\n\n/**\n * Props for FadeAway gradient overlay.\n * @property direction - Edge to show fade: `\"top\"`, `\"bottom\"`, `\"right\"`, or `\"left\"`\n * @property show - Whether the fade is visible\n */\ninterface FadeAwayProps extends React.HTMLAttributes<HTMLDivElement> {\n direction: \"top\" | \"bottom\" | \"right\" | \"left\";\n show: boolean;\n}\n\nconst FadeAway = ({\n className,\n direction,\n show,\n ref,\n ...props\n}: FadeAwayProps & { ref?: Ref<HTMLDivElement> }) => (\n <div\n className={cn(\n \"pointer-events-none absolute z-[8] transition-opacity duration-200\",\n direction === \"top\" &&\n \"top-0 right-0 left-0 h-[var(--fade-away-top-height,32px)] bg-gradient-to-b\",\n direction === \"bottom\" &&\n \"right-0 bottom-0 left-0 h-[var(--fade-away-bottom-height,32px)] bg-gradient-to-t\",\n direction === \"right\" &&\n \"top-0 right-0 bottom-0 w-[var(--fade-away-right-width,32px)] bg-gradient-to-l\",\n direction === \"left\" &&\n \"top-0 bottom-0 left-0 w-[var(--fade-away-left-width,32px)] bg-gradient-to-r\",\n \"from-[var(--fade-away-from,rgb(255_255_255))] to-[var(--fade-away-to,rgb(255_255_255_/_0))] dark:from-[var(--fade-away-from-dark,rgb(21_25_25))] dark:to-[var(--fade-away-to-dark,rgb(21_25_25_/_0))]\",\n show ? \"opacity-100\" : \"opacity-0\",\n className\n )}\n ref={ref}\n {...props}\n />\n);\n\n/**\n * Props for ScrollArea component.\n * @property fadeAway - Show fade gradients: `true` (all edges), `\"top\"`, `\"bottom\"`, `\"y\"`, `\"left\"`, `\"right\"`, `\"x\"`\n * @property hideScrollBar - Hide the scrollbar while keeping functionality\n */\ninterface ScrollAreaProps\n extends React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> {\n fadeAway?: boolean | \"top\" | \"bottom\" | \"y\" | \"left\" | \"right\" | \"x\";\n hideScrollBar?: boolean;\n}\n\ninterface ScrollFadeParams {\n viewportRef: React.RefObject<HTMLDivElement | null>;\n enabled: boolean;\n}\n\nconst useScrollFade = ({ viewportRef, enabled }: ScrollFadeParams) => {\n const [showTopFade, setShowTopFade] = React.useState(false);\n const [showBottomFade, setShowBottomFade] = React.useState(true);\n const [showLeftFade, setShowLeftFade] = React.useState(false);\n const [showRightFade, setShowRightFade] = React.useState(true);\n\n const handleScroll = React.useCallback(() => {\n if (!enabled) {\n return;\n }\n\n if (viewportRef.current) {\n const {\n scrollTop,\n scrollLeft,\n scrollHeight,\n scrollWidth,\n clientHeight,\n clientWidth,\n } = viewportRef.current;\n\n setShowTopFade(scrollTop > 0);\n setShowBottomFade(scrollTop < scrollHeight - clientHeight - 1);\n setShowLeftFade(scrollLeft > 0);\n setShowRightFade(scrollLeft < scrollWidth - clientWidth - 1);\n }\n }, [viewportRef, enabled]);\n\n const throttledHandleScroll = React.useMemo(\n () => throttle(handleScroll, 50),\n [handleScroll]\n );\n\n React.useEffect(() => {\n if (!enabled) {\n return;\n }\n\n const viewport = viewportRef.current;\n\n if (!viewport) {\n return;\n }\n\n // Initial check\n throttledHandleScroll();\n\n // Handle scroll events\n viewport.addEventListener(\"scroll\", throttledHandleScroll, {\n passive: true,\n });\n\n // Handle resize events that might affect scrollability\n const resizeObserver = new ResizeObserver(throttledHandleScroll);\n\n resizeObserver.observe(viewport);\n\n return () => {\n viewport.removeEventListener(\"scroll\", throttledHandleScroll);\n resizeObserver.disconnect();\n throttledHandleScroll.cancel(); // Clean up throttled function\n };\n }, [throttledHandleScroll, viewportRef, enabled]);\n\n return { showTopFade, showBottomFade, showLeftFade, showRightFade };\n};\n\nconst ScrollArea = ({\n className,\n children,\n fadeAway = false,\n hideScrollBar,\n ref,\n ...props\n}: ScrollAreaProps & {\n ref?: Ref<React.ElementRef<typeof ScrollAreaPrimitive.Root>>;\n}) => {\n const viewportRef = React.useRef<HTMLDivElement>(null);\n const { showTopFade, showBottomFade, showLeftFade, showRightFade } =\n useScrollFade({\n viewportRef,\n enabled: !!fadeAway,\n });\n\n const shouldHideScrollBar =\n \"hideScrollBar\" in props || hideScrollBar === true;\n\n return (\n <ScrollAreaPrimitive.Root\n className={cn(\"relative overflow-hidden\", className)}\n ref={ref}\n {...props}\n >\n <ScrollAreaPrimitive.Viewport\n className=\"h-full w-full rounded-[inherit]\"\n ref={viewportRef}\n >\n {fadeAway &&\n (fadeAway === true || fadeAway === \"top\" || fadeAway === \"y\") && (\n <FadeAway direction=\"top\" show={showTopFade} />\n )}\n {fadeAway &&\n (fadeAway === true || fadeAway === \"left\" || fadeAway === \"x\") && (\n <FadeAway direction=\"left\" show={showLeftFade} />\n )}\n {children}\n {fadeAway &&\n (fadeAway === true || fadeAway === \"bottom\" || fadeAway === \"y\") && (\n <FadeAway direction=\"bottom\" show={showBottomFade} />\n )}\n {fadeAway &&\n (fadeAway === true || fadeAway === \"right\" || fadeAway === \"x\") && (\n <FadeAway direction=\"right\" show={showRightFade} />\n )}\n </ScrollAreaPrimitive.Viewport>\n <ScrollBar className={shouldHideScrollBar ? \"hidden opacity-0\" : \"\"} />\n <ScrollAreaPrimitive.Corner />\n </ScrollAreaPrimitive.Root>\n );\n};\n\nexport { FadeAway, ScrollArea, ScrollBar, type ScrollAreaProps };\n"],
4
+ "sourcesContent": ["/**\n * @module ScrollArea\n *\n * Custom scrollable container with styled scrollbars and optional fade indicators.\n * Built on Radix UI ScrollArea primitive.\n *\n * @see {@link https://ui.shadcn.com/docs/components/scroll-area Shadcn ScrollArea}\n * @see {@link https://www.radix-ui.com/primitives/docs/components/scroll-area Radix ScrollArea}\n *\n * @example\n * // Basic scroll area\n * <ScrollArea className=\"h-72 w-48\">\n * <div className=\"p-4\">\n * {longContent}\n * </div>\n * </ScrollArea>\n *\n * @example\n * // With fade indicators\n * <ScrollArea className=\"h-48\" fadeAway=\"y\">\n * {content}\n * </ScrollArea>\n *\n * @example\n * // Hidden scrollbar\n * <ScrollArea className=\"h-48\" hideScrollBar>\n * {content}\n * </ScrollArea>\n */\nimport throttle from \"lodash/throttle\";\nimport { ScrollArea as ScrollAreaPrimitive } from \"radix-ui\";\nimport type { Ref } from \"react\";\nimport * as React from \"react\";\n\nimport { cn } from \"../lib/utils\";\n\n/** Styled scrollbar component for vertical or horizontal scrolling. */\nconst ScrollBar = ({\n className,\n orientation = \"vertical\",\n ref,\n ...props\n}: React.ComponentPropsWithoutRef<\n typeof ScrollAreaPrimitive.ScrollAreaScrollbar\n> & {\n ref?: Ref<React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>>;\n}) => (\n <ScrollAreaPrimitive.ScrollAreaScrollbar\n className={cn(\n \"flex touch-none select-none transition-colors\",\n orientation === \"vertical\" &&\n \"h-full w-2.5 border-0 border-l border-l-transparent border-solid p-[1px]\",\n orientation === \"horizontal\" &&\n \"h-2.5 flex-col border-0 border-t border-t-transparent border-solid p-[1px]\",\n className\n )}\n orientation={orientation}\n ref={ref}\n {...props}\n >\n <ScrollAreaPrimitive.ScrollAreaThumb className=\"relative flex-1 rounded-full bg-gray-200 dark:bg-gray-800\" />\n </ScrollAreaPrimitive.ScrollAreaScrollbar>\n);\n\n/**\n * Props for FadeAway gradient overlay.\n * @property direction - Edge to show fade: `\"top\"`, `\"bottom\"`, `\"right\"`, or `\"left\"`\n * @property show - Whether the fade is visible\n */\ninterface FadeAwayProps extends React.HTMLAttributes<HTMLDivElement> {\n direction: \"top\" | \"bottom\" | \"right\" | \"left\";\n show: boolean;\n}\n\nconst FadeAway = ({\n className,\n direction,\n show,\n ref,\n ...props\n}: FadeAwayProps & { ref?: Ref<HTMLDivElement> }) => (\n <div\n className={cn(\n \"pointer-events-none absolute z-[8] transition-opacity duration-200\",\n direction === \"top\" &&\n \"top-0 right-0 left-0 h-[var(--fade-away-top-height,32px)] bg-gradient-to-b\",\n direction === \"bottom\" &&\n \"right-0 bottom-0 left-0 h-[var(--fade-away-bottom-height,32px)] bg-gradient-to-t\",\n direction === \"right\" &&\n \"top-0 right-0 bottom-0 w-[var(--fade-away-right-width,32px)] bg-gradient-to-l\",\n direction === \"left\" &&\n \"top-0 bottom-0 left-0 w-[var(--fade-away-left-width,32px)] bg-gradient-to-r\",\n \"from-[var(--fade-away-from,rgb(255_255_255))] to-[var(--fade-away-to,rgb(255_255_255_/_0))] dark:from-[var(--fade-away-from-dark,rgb(21_25_25))] dark:to-[var(--fade-away-to-dark,rgb(21_25_25_/_0))]\",\n show ? \"opacity-100\" : \"opacity-0\",\n className\n )}\n ref={ref}\n {...props}\n />\n);\n\n/**\n * Props for ScrollArea component.\n * @property fadeAway - Show fade gradients: `true` (all edges), `\"top\"`, `\"bottom\"`, `\"y\"`, `\"left\"`, `\"right\"`, `\"x\"`\n * @property hideScrollBar - Hide the scrollbar while keeping functionality\n */\ninterface ScrollAreaProps\n extends React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> {\n fadeAway?: boolean | \"top\" | \"bottom\" | \"y\" | \"left\" | \"right\" | \"x\";\n hideScrollBar?: boolean;\n}\n\ninterface ScrollFadeParams {\n enabled: boolean;\n viewportRef: React.RefObject<HTMLDivElement | null>;\n}\n\nconst useScrollFade = ({ viewportRef, enabled }: ScrollFadeParams) => {\n const [showTopFade, setShowTopFade] = React.useState(false);\n const [showBottomFade, setShowBottomFade] = React.useState(true);\n const [showLeftFade, setShowLeftFade] = React.useState(false);\n const [showRightFade, setShowRightFade] = React.useState(true);\n\n const handleScroll = React.useCallback(() => {\n if (!enabled) {\n return;\n }\n\n if (viewportRef.current) {\n const {\n scrollTop,\n scrollLeft,\n scrollHeight,\n scrollWidth,\n clientHeight,\n clientWidth,\n } = viewportRef.current;\n\n setShowTopFade(scrollTop > 0);\n setShowBottomFade(scrollTop < scrollHeight - clientHeight - 1);\n setShowLeftFade(scrollLeft > 0);\n setShowRightFade(scrollLeft < scrollWidth - clientWidth - 1);\n }\n }, [viewportRef, enabled]);\n\n const throttledHandleScroll = React.useMemo(\n () => throttle(handleScroll, 50),\n [handleScroll]\n );\n\n React.useEffect(() => {\n if (!enabled) {\n return;\n }\n\n const viewport = viewportRef.current;\n\n if (!viewport) {\n return;\n }\n\n // Initial check\n throttledHandleScroll();\n\n // Handle scroll events\n viewport.addEventListener(\"scroll\", throttledHandleScroll, {\n passive: true,\n });\n\n // Handle resize events that might affect scrollability\n const resizeObserver = new ResizeObserver(throttledHandleScroll);\n\n resizeObserver.observe(viewport);\n\n return () => {\n viewport.removeEventListener(\"scroll\", throttledHandleScroll);\n resizeObserver.disconnect();\n throttledHandleScroll.cancel(); // Clean up throttled function\n };\n }, [throttledHandleScroll, viewportRef, enabled]);\n\n return { showTopFade, showBottomFade, showLeftFade, showRightFade };\n};\n\nconst ScrollArea = ({\n className,\n children,\n fadeAway = false,\n hideScrollBar,\n ref,\n ...props\n}: ScrollAreaProps & {\n ref?: Ref<React.ElementRef<typeof ScrollAreaPrimitive.Root>>;\n}) => {\n const viewportRef = React.useRef<HTMLDivElement>(null);\n const { showTopFade, showBottomFade, showLeftFade, showRightFade } =\n useScrollFade({\n viewportRef,\n enabled: !!fadeAway,\n });\n\n const shouldHideScrollBar =\n \"hideScrollBar\" in props || hideScrollBar === true;\n\n return (\n <ScrollAreaPrimitive.Root\n className={cn(\"relative overflow-hidden\", className)}\n ref={ref}\n {...props}\n >\n <ScrollAreaPrimitive.Viewport\n className=\"h-full w-full rounded-[inherit]\"\n ref={viewportRef}\n >\n {fadeAway &&\n (fadeAway === true || fadeAway === \"top\" || fadeAway === \"y\") && (\n <FadeAway direction=\"top\" show={showTopFade} />\n )}\n {fadeAway &&\n (fadeAway === true || fadeAway === \"left\" || fadeAway === \"x\") && (\n <FadeAway direction=\"left\" show={showLeftFade} />\n )}\n {children}\n {fadeAway &&\n (fadeAway === true || fadeAway === \"bottom\" || fadeAway === \"y\") && (\n <FadeAway direction=\"bottom\" show={showBottomFade} />\n )}\n {fadeAway &&\n (fadeAway === true || fadeAway === \"right\" || fadeAway === \"x\") && (\n <FadeAway direction=\"right\" show={showRightFade} />\n )}\n </ScrollAreaPrimitive.Viewport>\n <ScrollBar className={shouldHideScrollBar ? \"hidden opacity-0\" : \"\"} />\n <ScrollAreaPrimitive.Corner />\n </ScrollAreaPrimitive.Root>\n );\n};\n\nexport { FadeAway, ScrollArea, type ScrollAreaProps, ScrollBar };\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @module ScrollDrum
3
+ *
4
+ * iOS-style picker columns for selecting a value from a constrained list —
5
+ * durations, time, dates, BPM, etc. Supports wheel/trackpad scrolling, mouse
6
+ * and touch drag with momentum, click-to-center, full keyboard control, and
7
+ * optional infinite looping.
8
+ *
9
+ * @example
10
+ * // Single drum: count-in bars
11
+ * const [val, setVal] = useState("8");
12
+ * const items = Array.from({ length: 31 }, (_, i) => String(i));
13
+ *
14
+ * <ScrollDrumGroup>
15
+ * <ScrollDrumColumn label="bars">
16
+ * <ScrollDrum items={items} value={val} onChange={setVal} ariaLabel="Bars" />
17
+ * </ScrollDrumColumn>
18
+ * </ScrollDrumGroup>
19
+ *
20
+ * @example
21
+ * // 12-hour time picker with separator
22
+ * <ScrollDrumGroup separator=":">
23
+ * <ScrollDrumColumn label="hour">
24
+ * <ScrollDrum items={hours} value={h} onChange={setH} ariaLabel="Hour" />
25
+ * </ScrollDrumColumn>
26
+ * <ScrollDrumColumn label="min">
27
+ * <ScrollDrum items={minutes} value={m} onChange={setM} ariaLabel="Minute" />
28
+ * </ScrollDrumColumn>
29
+ * <ScrollDrumColumn label="">
30
+ * <ScrollDrum items={["AM", "PM"]} value={p} onChange={setP} loop={false} />
31
+ * </ScrollDrumColumn>
32
+ * </ScrollDrumGroup>
33
+ */
34
+ import { type ReactNode } from "react";
35
+ interface ScrollDrumProps {
36
+ /** Accessible name for the listbox. */
37
+ ariaLabel?: string;
38
+ /** Additional class names merged onto the drum element. */
39
+ className?: string;
40
+ /** Vertical pixel size of one row slot. Defaults to 52. */
41
+ itemHeight?: number;
42
+ /** Ordered list of selectable strings. Order is the visual order. */
43
+ items: string[];
44
+ /** When `true` (default), scrolling past the end wraps to the start. */
45
+ loop?: boolean;
46
+ /** Hard character cap per item. Long labels are truncated for layout. */
47
+ maxChars?: number;
48
+ /** Fires with `(newValue, newIndex)` when the user lands on a new row. */
49
+ onChange?: (value: string, index: number) => void;
50
+ /** Currently selected item. Must exist in `items`. */
51
+ value: string;
52
+ /** How many rows are visible at once (odd numbers center cleanly). */
53
+ visibleCount?: number;
54
+ }
55
+ /**
56
+ * A single scrollable column. Designed to be wrapped in a {@link ScrollDrumColumn}
57
+ * and grouped via {@link ScrollDrumGroup}.
58
+ */
59
+ declare const ScrollDrum: ({ items, value, onChange, itemHeight, visibleCount, loop: loopProp, maxChars, ariaLabel, className, }: ScrollDrumProps) => ReactNode;
60
+ interface ScrollDrumGroupProps {
61
+ /** One or more {@link ScrollDrumColumn} children. */
62
+ children: ReactNode;
63
+ /** Additional class names merged onto the group container. */
64
+ className?: string;
65
+ /** Glyph rendered between adjacent columns. Ignored when `showSeparators` is false. */
66
+ separator?: string;
67
+ /** When `false`, the gutter between columns is preserved but no glyph is rendered. */
68
+ showSeparators?: boolean;
69
+ }
70
+ /**
71
+ * Gray container that holds one or more {@link ScrollDrumColumn} children.
72
+ * Optionally renders a glyph between columns (e.g. `:` for time, `/` for date).
73
+ */
74
+ declare const ScrollDrumGroup: ({ children, separator, showSeparators, className, }: ScrollDrumGroupProps) => ReactNode;
75
+ interface ScrollDrumColumnProps {
76
+ /** Should be a single {@link ScrollDrum}. */
77
+ children: ReactNode;
78
+ /** Additional class names merged onto the column wrapper. */
79
+ className?: string;
80
+ /** Caption rendered below the drum slab. Pass `""` for an invisible spacer. */
81
+ label?: string;
82
+ /** Hard character cap that drives the auto-computed column width. */
83
+ maxChars?: number;
84
+ /** Explicit pixel width override. When omitted, scales with `maxChars`. */
85
+ width?: number;
86
+ }
87
+ /**
88
+ * Pairs a {@link ScrollDrum} with an optional caption underneath. When `width`
89
+ * is omitted the column auto-sizes via `Math.max(72, 24 + maxChars * 16)`, so
90
+ * a 4-char drum (e.g. `"2026"`) lands at 88px and a 2-char drum stays at the
91
+ * 72px floor.
92
+ */
93
+ declare const ScrollDrumColumn: ({ label, width, maxChars, children, className, }: ScrollDrumColumnProps) => ReactNode;
94
+ export type { ScrollDrumColumnProps, ScrollDrumGroupProps, ScrollDrumProps };
95
+ export { ScrollDrum, ScrollDrumColumn, ScrollDrumGroup };
96
+ //# sourceMappingURL=scroll-drum.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scroll-drum.d.ts","sourceRoot":"","sources":["../../src/components/scroll-drum.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,EAIL,KAAK,SAAS,EAOf,MAAM,OAAO,CAAC;AAkBf,UAAU,eAAe;IACvB,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,wEAAwE;IACxE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,QAAA,MAAM,UAAU,GAAI,uGAUjB,eAAe,KAAG,SA+UpB,CAAC;AAEF,UAAU,oBAAoB;IAC5B,qDAAqD;IACrD,QAAQ,EAAE,SAAS,CAAC;IACpB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sFAAsF;IACtF,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;GAGG;AACH,QAAA,MAAM,eAAe,GAAI,qDAKtB,oBAAoB,KAAG,SA6BzB,CAAC;AAEF,UAAU,qBAAqB;IAC7B,6CAA6C;IAC7C,QAAQ,EAAE,SAAS,CAAC;IACpB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,QAAA,MAAM,gBAAgB,GAAI,kDAMvB,qBAAqB,KAAG,SAiB1B,CAAC;AAEF,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,eAAe,EAAE,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAC"}
@@ -56,7 +56,9 @@ const ScrollDrum = ({
56
56
  }, [floatIndex]);
57
57
  const resolveIndex = useCallback(
58
58
  (f) => {
59
- if (loop) return pmod(Math.round(f), N);
59
+ if (loop) {
60
+ return pmod(Math.round(f), N);
61
+ }
60
62
  return clamp(Math.round(f), 0, N - 1);
61
63
  },
62
64
  [loop, N]
@@ -78,7 +80,9 @@ const ScrollDrum = ({
78
80
  setFloatIndex(final);
79
81
  const idx = resolveIndex(final);
80
82
  const next = items[idx];
81
- if (next !== void 0 && next !== value) onChange?.(next, idx);
83
+ if (next !== void 0 && next !== value) {
84
+ onChange?.(next, idx);
85
+ }
82
86
  }
83
87
  };
84
88
  animRef.current = requestAnimationFrame(step);
@@ -88,27 +92,37 @@ const ScrollDrum = ({
88
92
  const targetIdx = N > 0 ? Math.max(0, items.indexOf(value)) : 0;
89
93
  const lastSyncedTargetIdx = useRef(targetIdx);
90
94
  useEffect(() => {
91
- if (targetIdx === lastSyncedTargetIdx.current) return;
95
+ if (targetIdx === lastSyncedTargetIdx.current) {
96
+ return;
97
+ }
92
98
  lastSyncedTargetIdx.current = targetIdx;
93
- if (N === 0) return;
99
+ if (N === 0) {
100
+ return;
101
+ }
94
102
  const cur = floatIndexRef.current;
95
103
  let target = targetIdx;
96
104
  if (loop) {
97
105
  const cycles = Math.round((cur - targetIdx) / N);
98
106
  target = targetIdx + cycles * N;
99
107
  }
100
- if (Math.abs(target - cur) > 1e-3) animateTo(target);
108
+ if (Math.abs(target - cur) > 1e-3) {
109
+ animateTo(target);
110
+ }
101
111
  }, [targetIdx]);
102
112
  useEffect(() => {
103
113
  const el = containerRef.current;
104
- if (!el) return;
114
+ if (!el) {
115
+ return;
116
+ }
105
117
  const onWheel = (e) => {
106
118
  e.preventDefault();
107
119
  cancelAnimationFrame(animRef.current);
108
120
  const delta = e.deltaY / itemHeight;
109
121
  const raw = floatIndexRef.current + delta;
110
122
  setFloatIndex(loop ? raw : clamp(raw, 0, N - 1));
111
- if (wheelTimer.current) clearTimeout(wheelTimer.current);
123
+ if (wheelTimer.current) {
124
+ clearTimeout(wheelTimer.current);
125
+ }
112
126
  wheelTimer.current = setTimeout(() => {
113
127
  const cur = floatIndexRef.current;
114
128
  const target = loop ? Math.round(cur) : clamp(Math.round(cur), 0, N - 1);
@@ -118,7 +132,9 @@ const ScrollDrum = ({
118
132
  el.addEventListener("wheel", onWheel, { passive: false });
119
133
  return () => {
120
134
  el.removeEventListener("wheel", onWheel);
121
- if (wheelTimer.current) clearTimeout(wheelTimer.current);
135
+ if (wheelTimer.current) {
136
+ clearTimeout(wheelTimer.current);
137
+ }
122
138
  };
123
139
  }, [animateTo, N, itemHeight, loop]);
124
140
  useEffect(
@@ -128,7 +144,9 @@ const ScrollDrum = ({
128
144
  []
129
145
  );
130
146
  const onPointerDown = (e) => {
131
- if (e.pointerType === "mouse" && e.button !== 0) return;
147
+ if (e.pointerType === "mouse" && e.button !== 0) {
148
+ return;
149
+ }
132
150
  cancelAnimationFrame(animRef.current);
133
151
  containerRef.current?.setPointerCapture(e.pointerId);
134
152
  dragRef.current = {
@@ -143,9 +161,13 @@ const ScrollDrum = ({
143
161
  };
144
162
  const onPointerMove = (e) => {
145
163
  const d = dragRef.current;
146
- if (!d.active) return;
164
+ if (!d.active) {
165
+ return;
166
+ }
147
167
  const dy = e.clientY - d.startY;
148
- if (Math.abs(dy) > 3) d.moved = true;
168
+ if (Math.abs(dy) > 3) {
169
+ d.moved = true;
170
+ }
149
171
  const raw = d.startIndex - dy / itemHeight;
150
172
  setFloatIndex(loop ? raw : clamp(raw, 0, N - 1));
151
173
  const now = performance.now();
@@ -156,7 +178,9 @@ const ScrollDrum = ({
156
178
  };
157
179
  const onPointerUp = (e) => {
158
180
  const d = dragRef.current;
159
- if (!d.active) return;
181
+ if (!d.active) {
182
+ return;
183
+ }
160
184
  d.active = false;
161
185
  try {
162
186
  containerRef.current?.releasePointerCapture(e.pointerId);
@@ -169,19 +193,27 @@ const ScrollDrum = ({
169
193
  };
170
194
  const onKeyDown = (e) => {
171
195
  let target = Math.round(floatIndexRef.current);
172
- if (e.key === "ArrowUp") target -= 1;
173
- else if (e.key === "ArrowDown") target += 1;
174
- else if (e.key === "PageUp") target -= 5;
175
- else if (e.key === "PageDown") target += 5;
176
- else if (e.key === "Home") {
196
+ if (e.key === "ArrowUp") {
197
+ target -= 1;
198
+ } else if (e.key === "ArrowDown") {
199
+ target += 1;
200
+ } else if (e.key === "PageUp") {
201
+ target -= 5;
202
+ } else if (e.key === "PageDown") {
203
+ target += 5;
204
+ } else if (e.key === "Home") {
177
205
  const cur = Math.round(floatIndexRef.current);
178
206
  target = loop ? cur - pmod(cur, N) : 0;
179
207
  } else if (e.key === "End") {
180
208
  const cur = Math.round(floatIndexRef.current);
181
209
  target = loop ? cur - pmod(cur, N) + N - 1 : N - 1;
182
- } else return;
210
+ } else {
211
+ return;
212
+ }
183
213
  e.preventDefault();
184
- if (!loop) target = clamp(target, 0, N - 1);
214
+ if (!loop) {
215
+ target = clamp(target, 0, N - 1);
216
+ }
185
217
  animateTo(target);
186
218
  };
187
219
  const halfVisible = Math.floor(visibleCount / 2);
@@ -190,10 +222,14 @@ const ScrollDrum = ({
190
222
  const visibleItems = [];
191
223
  for (let off = -windowRadius; off <= windowRadius; off++) {
192
224
  const rawIdx = centerInt + off;
193
- if (!loop && (rawIdx < 0 || rawIdx >= N)) continue;
225
+ if (!loop && (rawIdx < 0 || rawIdx >= N)) {
226
+ continue;
227
+ }
194
228
  const itemIdx = pmod(rawIdx, N);
195
229
  const label = items[itemIdx];
196
- if (label === void 0) continue;
230
+ if (label === void 0) {
231
+ continue;
232
+ }
197
233
  visibleItems.push({
198
234
  key: String(rawIdx),
199
235
  itemIdx,
@@ -223,7 +259,7 @@ const ScrollDrum = ({
223
259
  "aria-label": ariaLabel,
224
260
  "aria-orientation": "vertical",
225
261
  className: cn(
226
- "relative h-full w-full cursor-grab touch-none select-none overflow-hidden bg-white outline-none active:cursor-grabbing focus-visible:[box-shadow:inset_0_0_0_2px_var(--brand-primary)] dark:bg-gray-900",
262
+ "relative h-full w-full cursor-grab touch-none select-none overflow-hidden bg-white outline-hidden active:cursor-grabbing focus-visible:[box-shadow:inset_0_0_0_2px_var(--brand-primary)] dark:bg-gray-900",
227
263
  className
228
264
  ),
229
265
  onKeyDown,
@@ -269,7 +305,9 @@ const ScrollDrum = ({
269
305
  ),
270
306
  id: isSelected ? optionId(itemIdx) : void 0,
271
307
  onClick: () => {
272
- if (dragRef.current.moved) return;
308
+ if (dragRef.current.moved) {
309
+ return;
310
+ }
273
311
  animateTo(offsetSlot);
274
312
  },
275
313
  role: "option",
@@ -329,20 +367,11 @@ const ScrollDrumColumn = ({
329
367
  return /* @__PURE__ */ jsxs(
330
368
  "div",
331
369
  {
332
- className: cn(
333
- "flex flex-none flex-col gap-2 p-0",
334
- className
335
- ),
370
+ className: cn("flex flex-none flex-col gap-2 p-0", className),
336
371
  style: { width: computed },
337
372
  children: [
338
373
  /* @__PURE__ */ jsx("div", { className: "flex h-[120px] flex-none items-center justify-center bg-white dark:bg-gray-900", children }),
339
- label !== void 0 && /* @__PURE__ */ jsx(
340
- "div",
341
- {
342
- className: "h-6 text-center font-sans font-semibold text-base text-gray-600 leading-6 [font-feature-settings:'ss03'] dark:text-gray-300",
343
- children: label || "\xA0"
344
- }
345
- )
374
+ label !== void 0 && /* @__PURE__ */ jsx("div", { className: "h-6 text-center font-sans font-semibold text-base text-gray-600 leading-6 [font-feature-settings:'ss03'] dark:text-gray-300", children: label || "\xA0" })
346
375
  ]
347
376
  }
348
377
  );
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/components/scroll-drum.tsx"],
4
- "sourcesContent": ["/**\n * @module ScrollDrum\n *\n * iOS-style picker columns for selecting a value from a constrained list \u2014\n * durations, time, dates, BPM, etc. Supports wheel/trackpad scrolling, mouse\n * and touch drag with momentum, click-to-center, full keyboard control, and\n * optional infinite looping.\n *\n * @example\n * // Single drum: count-in bars\n * const [val, setVal] = useState(\"8\");\n * const items = Array.from({ length: 31 }, (_, i) => String(i));\n *\n * <ScrollDrumGroup>\n * <ScrollDrumColumn label=\"bars\">\n * <ScrollDrum items={items} value={val} onChange={setVal} ariaLabel=\"Bars\" />\n * </ScrollDrumColumn>\n * </ScrollDrumGroup>\n *\n * @example\n * // 12-hour time picker with separator\n * <ScrollDrumGroup separator=\":\">\n * <ScrollDrumColumn label=\"hour\">\n * <ScrollDrum items={hours} value={h} onChange={setH} ariaLabel=\"Hour\" />\n * </ScrollDrumColumn>\n * <ScrollDrumColumn label=\"min\">\n * <ScrollDrum items={minutes} value={m} onChange={setM} ariaLabel=\"Minute\" />\n * </ScrollDrumColumn>\n * <ScrollDrumColumn label=\"\">\n * <ScrollDrum items={[\"AM\", \"PM\"]} value={p} onChange={setP} loop={false} />\n * </ScrollDrumColumn>\n * </ScrollDrumGroup>\n */\nimport {\n Children,\n Fragment,\n type PointerEvent as ReactPointerEvent,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\";\n\nimport { cn } from \"../lib/utils\";\n\nconst DEFAULT_ITEM_HEIGHT = 52;\nconst DEFAULT_VISIBLE = 3;\nconst DEFAULT_MAX_CHARS = 4;\n\nconst SNAP_DURATION_MS = 280;\nconst WHEEL_SETTLE_MS = 110;\nconst COAST_FACTOR = 220 * 0.55;\n\nconst clamp = (n: number, lo: number, hi: number): number =>\n Math.max(lo, Math.min(hi, n));\n\n// Positive modulo (handles negatives) \u2014 required for stable wrap math.\nconst pmod = (n: number, m: number): number => ((n % m) + m) % m;\n\ninterface ScrollDrumProps {\n /** Ordered list of selectable strings. Order is the visual order. */\n items: string[];\n /** Currently selected item. Must exist in `items`. */\n value: string;\n /** Fires with `(newValue, newIndex)` when the user lands on a new row. */\n onChange?: (value: string, index: number) => void;\n /** Vertical pixel size of one row slot. Defaults to 52. */\n itemHeight?: number;\n /** How many rows are visible at once (odd numbers center cleanly). */\n visibleCount?: number;\n /** When `true` (default), scrolling past the end wraps to the start. */\n loop?: boolean;\n /** Hard character cap per item. Long labels are truncated for layout. */\n maxChars?: number;\n /** Accessible name for the listbox. */\n ariaLabel?: string;\n /** Additional class names merged onto the drum element. */\n className?: string;\n}\n\n/**\n * A single scrollable column. Designed to be wrapped in a {@link ScrollDrumColumn}\n * and grouped via {@link ScrollDrumGroup}.\n */\nconst ScrollDrum = ({\n items,\n value,\n onChange,\n itemHeight = DEFAULT_ITEM_HEIGHT,\n visibleCount = DEFAULT_VISIBLE,\n loop: loopProp = true,\n maxChars = DEFAULT_MAX_CHARS,\n ariaLabel,\n className,\n}: ScrollDrumProps): ReactNode => {\n const uid = useId();\n const optionId = (idx: number): string => `${uid}-opt-${idx}`;\n const containerRef = useRef<HTMLDivElement | null>(null);\n const animRef = useRef<number>(0);\n const wheelTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const dragRef = useRef({\n active: false,\n startY: 0,\n startIndex: 0,\n lastY: 0,\n lastT: 0,\n vy: 0,\n moved: false,\n });\n\n const N = items.length;\n // Looping past zero items would call pmod(n, 0) and yield NaN, breaking the\n // render math. Force non-looping internally when there's nothing to loop\n // through; the render bails on N === 0.\n const loop = loopProp && N > 1;\n\n const truncate = useCallback(\n (label: string): string =>\n label.length > maxChars ? label.slice(0, maxChars) : label,\n [maxChars]\n );\n\n const initialIndex = Math.max(0, items.indexOf(value));\n const [floatIndex, setFloatIndex] = useState<number>(initialIndex);\n const floatIndexRef = useRef<number>(floatIndex);\n useEffect(() => {\n floatIndexRef.current = floatIndex;\n }, [floatIndex]);\n\n const resolveIndex = useCallback(\n (f: number): number => {\n if (loop) return pmod(Math.round(f), N);\n return clamp(Math.round(f), 0, N - 1);\n },\n [loop, N]\n );\n\n const animateTo = useCallback(\n (targetFloat: number): void => {\n cancelAnimationFrame(animRef.current);\n const start = performance.now();\n const from = floatIndexRef.current;\n const ease = (t: number): number => 1 - (1 - t) ** 3;\n const step = (): void => {\n const t = clamp((performance.now() - start) / SNAP_DURATION_MS, 0, 1);\n const v = from + (targetFloat - from) * ease(t);\n setFloatIndex(v);\n if (t < 1) {\n animRef.current = requestAnimationFrame(step);\n } else {\n const final = loop\n ? pmod(targetFloat, N)\n : clamp(targetFloat, 0, N - 1);\n setFloatIndex(final);\n const idx = resolveIndex(final);\n const next = items[idx];\n if (next !== undefined && next !== value) onChange?.(next, idx);\n }\n };\n animRef.current = requestAnimationFrame(step);\n },\n [items, loop, N, onChange, resolveIndex, value]\n );\n\n // External value -> internal float, animated on the shortest path. Tracked\n // by value's resolved index rather than the items reference, so an unstable\n // `items` array (fresh ref every render) doesn't cancel an in-flight\n // animation, but a real reorder (where `value` lands at a different index)\n // still resyncs the drum.\n const targetIdx = N > 0 ? Math.max(0, items.indexOf(value)) : 0;\n const lastSyncedTargetIdx = useRef<number>(targetIdx);\n useEffect(() => {\n if (targetIdx === lastSyncedTargetIdx.current) return;\n lastSyncedTargetIdx.current = targetIdx;\n if (N === 0) return;\n const cur = floatIndexRef.current;\n let target = targetIdx;\n if (loop) {\n const cycles = Math.round((cur - targetIdx) / N);\n target = targetIdx + cycles * N;\n }\n if (Math.abs(target - cur) > 0.001) animateTo(target);\n // animateTo intentionally omitted to avoid retriggering on every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [targetIdx]);\n\n // Wheel: native non-passive listener so we can preventDefault.\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const onWheel = (e: WheelEvent): void => {\n e.preventDefault();\n cancelAnimationFrame(animRef.current);\n const delta = e.deltaY / itemHeight;\n const raw = floatIndexRef.current + delta;\n setFloatIndex(loop ? raw : clamp(raw, 0, N - 1));\n if (wheelTimer.current) clearTimeout(wheelTimer.current);\n wheelTimer.current = setTimeout(() => {\n const cur = floatIndexRef.current;\n const target = loop\n ? Math.round(cur)\n : clamp(Math.round(cur), 0, N - 1);\n animateTo(target);\n }, WHEEL_SETTLE_MS);\n };\n el.addEventListener(\"wheel\", onWheel, { passive: false });\n return () => {\n el.removeEventListener(\"wheel\", onWheel);\n if (wheelTimer.current) clearTimeout(wheelTimer.current);\n };\n }, [animateTo, N, itemHeight, loop]);\n\n useEffect(\n () => () => {\n cancelAnimationFrame(animRef.current);\n },\n []\n );\n\n const onPointerDown = (e: ReactPointerEvent<HTMLDivElement>): void => {\n if (e.pointerType === \"mouse\" && e.button !== 0) return;\n cancelAnimationFrame(animRef.current);\n containerRef.current?.setPointerCapture(e.pointerId);\n dragRef.current = {\n active: true,\n startY: e.clientY,\n startIndex: floatIndexRef.current,\n lastY: e.clientY,\n lastT: performance.now(),\n vy: 0,\n moved: false,\n };\n };\n\n const onPointerMove = (e: ReactPointerEvent<HTMLDivElement>): void => {\n const d = dragRef.current;\n if (!d.active) return;\n const dy = e.clientY - d.startY;\n if (Math.abs(dy) > 3) d.moved = true;\n const raw = d.startIndex - dy / itemHeight;\n setFloatIndex(loop ? raw : clamp(raw, 0, N - 1));\n const now = performance.now();\n const dt = Math.max(1, now - d.lastT);\n d.vy = (e.clientY - d.lastY) / dt;\n d.lastY = e.clientY;\n d.lastT = now;\n };\n\n const onPointerUp = (e: ReactPointerEvent<HTMLDivElement>): void => {\n const d = dragRef.current;\n if (!d.active) return;\n d.active = false;\n try {\n containerRef.current?.releasePointerCapture(e.pointerId);\n } catch {\n /* pointer may already be released */\n }\n const coastPx = d.vy * COAST_FACTOR;\n const projected = floatIndexRef.current - coastPx / itemHeight;\n const target = loop\n ? Math.round(projected)\n : clamp(Math.round(projected), 0, N - 1);\n animateTo(target);\n };\n\n const onKeyDown = (e: ReactKeyboardEvent<HTMLDivElement>): void => {\n let target = Math.round(floatIndexRef.current);\n if (e.key === \"ArrowUp\") target -= 1;\n else if (e.key === \"ArrowDown\") target += 1;\n else if (e.key === \"PageUp\") target -= 5;\n else if (e.key === \"PageDown\") target += 5;\n else if (e.key === \"Home\") {\n const cur = Math.round(floatIndexRef.current);\n target = loop ? cur - pmod(cur, N) : 0;\n } else if (e.key === \"End\") {\n const cur = Math.round(floatIndexRef.current);\n target = loop ? cur - pmod(cur, N) + N - 1 : N - 1;\n } else return;\n e.preventDefault();\n if (!loop) target = clamp(target, 0, N - 1);\n animateTo(target);\n };\n\n const halfVisible = Math.floor(visibleCount / 2);\n const windowRadius = halfVisible + 2;\n const centerInt = Math.round(floatIndex);\n\n const visibleItems: Array<{\n key: string;\n itemIdx: number;\n offsetSlot: number;\n label: string;\n }> = [];\n for (let off = -windowRadius; off <= windowRadius; off++) {\n const rawIdx = centerInt + off;\n if (!loop && (rawIdx < 0 || rawIdx >= N)) continue;\n const itemIdx = pmod(rawIdx, N);\n const label = items[itemIdx];\n if (label === undefined) continue;\n visibleItems.push({\n key: String(rawIdx),\n itemIdx,\n offsetSlot: rawIdx,\n label,\n });\n }\n\n const rowOffset = (slot: number): number => (slot - floatIndex) * itemHeight;\n const selectedIndex = N > 0 ? resolveIndex(floatIndex) : 0;\n\n if (N === 0) {\n return (\n <div\n aria-label={ariaLabel}\n className={cn(\n \"relative h-full w-full overflow-hidden bg-white dark:bg-gray-900\",\n className\n )}\n role=\"listbox\"\n />\n );\n }\n\n return (\n <div\n aria-activedescendant={optionId(selectedIndex)}\n aria-label={ariaLabel}\n aria-orientation=\"vertical\"\n className={cn(\n \"relative h-full w-full cursor-grab touch-none select-none overflow-hidden bg-white outline-none active:cursor-grabbing focus-visible:[box-shadow:inset_0_0_0_2px_var(--brand-primary)] dark:bg-gray-900\",\n className\n )}\n onKeyDown={onKeyDown}\n onPointerCancel={onPointerUp}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n ref={containerRef}\n role=\"listbox\"\n style={{ \"--sd-item-h\": `${itemHeight}px` } as React.CSSProperties}\n tabIndex={0}\n >\n <div\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-x-0 top-[-1px] z-[2] h-9 bg-gradient-to-b from-white from-[12%] to-transparent dark:from-gray-900\"\n />\n <div\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-x-0 bottom-[-1px] z-[2] h-9 bg-gradient-to-t from-white from-[12%] to-transparent dark:from-gray-900\"\n />\n <div\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-x-2 top-1/2 z-0 h-[41px] -translate-y-1/2 rounded-sm border border-brand-primary bg-brand-secondary\"\n />\n\n <div className=\"pointer-events-none absolute inset-0\">\n {visibleItems.map(({ key, itemIdx, offsetSlot, label }) => {\n const isSelected =\n itemIdx === selectedIndex &&\n Math.abs(offsetSlot - floatIndex) <= 0.5;\n return (\n <button\n aria-selected={isSelected}\n className={cn(\n \"pointer-events-auto absolute top-1/2 left-1/2 z-[1] m-0 box-border flex h-[var(--sd-item-h,52px)] w-full cursor-pointer items-center justify-center overflow-hidden border-0 bg-transparent px-1.5 py-0 transition-colors duration-150\",\n isSelected\n ? \"text-brand-primary-hover dark:text-white\"\n : \"text-gray-600 dark:text-gray-300\"\n )}\n id={isSelected ? optionId(itemIdx) : undefined}\n key={key}\n onClick={() => {\n if (dragRef.current.moved) return;\n animateTo(offsetSlot);\n }}\n role=\"option\"\n style={{\n transform: `translate3d(-50%, calc(-50% + ${rowOffset(offsetSlot)}px), 0)`,\n }}\n tabIndex={-1}\n type=\"button\"\n >\n <span className=\"inline-block whitespace-nowrap text-center font-sans font-semibold text-[30px] leading-9 tracking-[-1px] [font-feature-settings:'ss03']\">\n {truncate(label)}\n </span>\n </button>\n );\n })}\n </div>\n </div>\n );\n};\n\ninterface ScrollDrumGroupProps {\n /** One or more {@link ScrollDrumColumn} children. */\n children: ReactNode;\n /** Glyph rendered between adjacent columns. Ignored when `showSeparators` is false. */\n separator?: string;\n /** When `false`, the gutter between columns is preserved but no glyph is rendered. */\n showSeparators?: boolean;\n /** Additional class names merged onto the group container. */\n className?: string;\n}\n\n/**\n * Gray container that holds one or more {@link ScrollDrumColumn} children.\n * Optionally renders a glyph between columns (e.g. `:` for time, `/` for date).\n */\nconst ScrollDrumGroup = ({\n children,\n separator = \":\",\n showSeparators = true,\n className,\n}: ScrollDrumGroupProps): ReactNode => {\n const cols = Children.toArray(children);\n return (\n <div\n className={cn(\n \"inline-flex flex-row items-center justify-center rounded-lg bg-gray-100 px-8 pt-6 pb-4 dark:bg-gray-800\",\n className\n )}\n >\n {cols.map((col, idx) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: columns are static and not reordered\n <Fragment key={idx}>\n {col}\n {idx < cols.length - 1 && (\n <div\n aria-hidden=\"true\"\n className=\"flex h-[120px] w-8 flex-none items-center justify-center self-start\"\n >\n {showSeparators && (\n <span className=\"text-center font-sans font-semibold text-[36px] text-gray-600 leading-10 tracking-[-1.5px] [font-feature-settings:'ss03'] dark:text-gray-400\">\n {separator}\n </span>\n )}\n </div>\n )}\n </Fragment>\n ))}\n </div>\n );\n};\n\ninterface ScrollDrumColumnProps {\n /** Caption rendered below the drum slab. Pass `\"\"` for an invisible spacer. */\n label?: string;\n /** Explicit pixel width override. When omitted, scales with `maxChars`. */\n width?: number;\n /** Hard character cap that drives the auto-computed column width. */\n maxChars?: number;\n /** Should be a single {@link ScrollDrum}. */\n children: ReactNode;\n /** Additional class names merged onto the column wrapper. */\n className?: string;\n}\n\n/**\n * Pairs a {@link ScrollDrum} with an optional caption underneath. When `width`\n * is omitted the column auto-sizes via `Math.max(72, 24 + maxChars * 16)`, so\n * a 4-char drum (e.g. `\"2026\"`) lands at 88px and a 2-char drum stays at the\n * 72px floor.\n */\nconst ScrollDrumColumn = ({\n label,\n width,\n maxChars = DEFAULT_MAX_CHARS,\n children,\n className,\n}: ScrollDrumColumnProps): ReactNode => {\n const computed = width ?? Math.max(72, 24 + maxChars * 16);\n return (\n <div\n className={cn(\n \"flex flex-none flex-col gap-2 p-0\",\n className\n )}\n style={{ width: computed }}\n >\n <div className=\"flex h-[120px] flex-none items-center justify-center bg-white dark:bg-gray-900\">\n {children}\n </div>\n {label !== undefined && (\n <div\n className=\"h-6 text-center font-sans font-semibold text-base text-gray-600 leading-6 [font-feature-settings:'ss03'] dark:text-gray-300\"\n >\n {label || \"\u00A0\"}\n </div>\n )}\n </div>\n );\n};\n\nexport { ScrollDrum, ScrollDrumGroup, ScrollDrumColumn };\nexport type { ScrollDrumProps, ScrollDrumGroupProps, ScrollDrumColumnProps };\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * @module ScrollDrum\n *\n * iOS-style picker columns for selecting a value from a constrained list \u2014\n * durations, time, dates, BPM, etc. Supports wheel/trackpad scrolling, mouse\n * and touch drag with momentum, click-to-center, full keyboard control, and\n * optional infinite looping.\n *\n * @example\n * // Single drum: count-in bars\n * const [val, setVal] = useState(\"8\");\n * const items = Array.from({ length: 31 }, (_, i) => String(i));\n *\n * <ScrollDrumGroup>\n * <ScrollDrumColumn label=\"bars\">\n * <ScrollDrum items={items} value={val} onChange={setVal} ariaLabel=\"Bars\" />\n * </ScrollDrumColumn>\n * </ScrollDrumGroup>\n *\n * @example\n * // 12-hour time picker with separator\n * <ScrollDrumGroup separator=\":\">\n * <ScrollDrumColumn label=\"hour\">\n * <ScrollDrum items={hours} value={h} onChange={setH} ariaLabel=\"Hour\" />\n * </ScrollDrumColumn>\n * <ScrollDrumColumn label=\"min\">\n * <ScrollDrum items={minutes} value={m} onChange={setM} ariaLabel=\"Minute\" />\n * </ScrollDrumColumn>\n * <ScrollDrumColumn label=\"\">\n * <ScrollDrum items={[\"AM\", \"PM\"]} value={p} onChange={setP} loop={false} />\n * </ScrollDrumColumn>\n * </ScrollDrumGroup>\n */\nimport {\n Children,\n Fragment,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n type PointerEvent as ReactPointerEvent,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\";\n\nimport { cn } from \"../lib/utils\";\n\nconst DEFAULT_ITEM_HEIGHT = 52;\nconst DEFAULT_VISIBLE = 3;\nconst DEFAULT_MAX_CHARS = 4;\n\nconst SNAP_DURATION_MS = 280;\nconst WHEEL_SETTLE_MS = 110;\nconst COAST_FACTOR = 220 * 0.55;\n\nconst clamp = (n: number, lo: number, hi: number): number =>\n Math.max(lo, Math.min(hi, n));\n\n// Positive modulo (handles negatives) \u2014 required for stable wrap math.\nconst pmod = (n: number, m: number): number => ((n % m) + m) % m;\n\ninterface ScrollDrumProps {\n /** Accessible name for the listbox. */\n ariaLabel?: string;\n /** Additional class names merged onto the drum element. */\n className?: string;\n /** Vertical pixel size of one row slot. Defaults to 52. */\n itemHeight?: number;\n /** Ordered list of selectable strings. Order is the visual order. */\n items: string[];\n /** When `true` (default), scrolling past the end wraps to the start. */\n loop?: boolean;\n /** Hard character cap per item. Long labels are truncated for layout. */\n maxChars?: number;\n /** Fires with `(newValue, newIndex)` when the user lands on a new row. */\n onChange?: (value: string, index: number) => void;\n /** Currently selected item. Must exist in `items`. */\n value: string;\n /** How many rows are visible at once (odd numbers center cleanly). */\n visibleCount?: number;\n}\n\n/**\n * A single scrollable column. Designed to be wrapped in a {@link ScrollDrumColumn}\n * and grouped via {@link ScrollDrumGroup}.\n */\nconst ScrollDrum = ({\n items,\n value,\n onChange,\n itemHeight = DEFAULT_ITEM_HEIGHT,\n visibleCount = DEFAULT_VISIBLE,\n loop: loopProp = true,\n maxChars = DEFAULT_MAX_CHARS,\n ariaLabel,\n className,\n}: ScrollDrumProps): ReactNode => {\n const uid = useId();\n const optionId = (idx: number): string => `${uid}-opt-${idx}`;\n const containerRef = useRef<HTMLDivElement | null>(null);\n const animRef = useRef<number>(0);\n const wheelTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const dragRef = useRef({\n active: false,\n startY: 0,\n startIndex: 0,\n lastY: 0,\n lastT: 0,\n vy: 0,\n moved: false,\n });\n\n const N = items.length;\n // Looping past zero items would call pmod(n, 0) and yield NaN, breaking the\n // render math. Force non-looping internally when there's nothing to loop\n // through; the render bails on N === 0.\n const loop = loopProp && N > 1;\n\n const truncate = useCallback(\n (label: string): string =>\n label.length > maxChars ? label.slice(0, maxChars) : label,\n [maxChars]\n );\n\n const initialIndex = Math.max(0, items.indexOf(value));\n const [floatIndex, setFloatIndex] = useState<number>(initialIndex);\n const floatIndexRef = useRef<number>(floatIndex);\n useEffect(() => {\n floatIndexRef.current = floatIndex;\n }, [floatIndex]);\n\n const resolveIndex = useCallback(\n (f: number): number => {\n if (loop) {\n return pmod(Math.round(f), N);\n }\n return clamp(Math.round(f), 0, N - 1);\n },\n [loop, N]\n );\n\n const animateTo = useCallback(\n (targetFloat: number): void => {\n cancelAnimationFrame(animRef.current);\n const start = performance.now();\n const from = floatIndexRef.current;\n const ease = (t: number): number => 1 - (1 - t) ** 3;\n const step = (): void => {\n const t = clamp((performance.now() - start) / SNAP_DURATION_MS, 0, 1);\n const v = from + (targetFloat - from) * ease(t);\n setFloatIndex(v);\n if (t < 1) {\n animRef.current = requestAnimationFrame(step);\n } else {\n const final = loop\n ? pmod(targetFloat, N)\n : clamp(targetFloat, 0, N - 1);\n setFloatIndex(final);\n const idx = resolveIndex(final);\n const next = items[idx];\n if (next !== undefined && next !== value) {\n onChange?.(next, idx);\n }\n }\n };\n animRef.current = requestAnimationFrame(step);\n },\n [items, loop, N, onChange, resolveIndex, value]\n );\n\n // External value -> internal float, animated on the shortest path. Tracked\n // by value's resolved index rather than the items reference, so an unstable\n // `items` array (fresh ref every render) doesn't cancel an in-flight\n // animation, but a real reorder (where `value` lands at a different index)\n // still resyncs the drum.\n const targetIdx = N > 0 ? Math.max(0, items.indexOf(value)) : 0;\n const lastSyncedTargetIdx = useRef<number>(targetIdx);\n useEffect(() => {\n if (targetIdx === lastSyncedTargetIdx.current) {\n return;\n }\n lastSyncedTargetIdx.current = targetIdx;\n if (N === 0) {\n return;\n }\n const cur = floatIndexRef.current;\n let target = targetIdx;\n if (loop) {\n const cycles = Math.round((cur - targetIdx) / N);\n target = targetIdx + cycles * N;\n }\n if (Math.abs(target - cur) > 0.001) {\n animateTo(target);\n }\n // animateTo intentionally omitted to avoid retriggering on every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [targetIdx]);\n\n // Wheel: native non-passive listener so we can preventDefault.\n useEffect(() => {\n const el = containerRef.current;\n if (!el) {\n return;\n }\n const onWheel = (e: WheelEvent): void => {\n e.preventDefault();\n cancelAnimationFrame(animRef.current);\n const delta = e.deltaY / itemHeight;\n const raw = floatIndexRef.current + delta;\n setFloatIndex(loop ? raw : clamp(raw, 0, N - 1));\n if (wheelTimer.current) {\n clearTimeout(wheelTimer.current);\n }\n wheelTimer.current = setTimeout(() => {\n const cur = floatIndexRef.current;\n const target = loop\n ? Math.round(cur)\n : clamp(Math.round(cur), 0, N - 1);\n animateTo(target);\n }, WHEEL_SETTLE_MS);\n };\n el.addEventListener(\"wheel\", onWheel, { passive: false });\n return () => {\n el.removeEventListener(\"wheel\", onWheel);\n if (wheelTimer.current) {\n clearTimeout(wheelTimer.current);\n }\n };\n }, [animateTo, N, itemHeight, loop]);\n\n useEffect(\n () => () => {\n cancelAnimationFrame(animRef.current);\n },\n []\n );\n\n const onPointerDown = (e: ReactPointerEvent<HTMLDivElement>): void => {\n if (e.pointerType === \"mouse\" && e.button !== 0) {\n return;\n }\n cancelAnimationFrame(animRef.current);\n containerRef.current?.setPointerCapture(e.pointerId);\n dragRef.current = {\n active: true,\n startY: e.clientY,\n startIndex: floatIndexRef.current,\n lastY: e.clientY,\n lastT: performance.now(),\n vy: 0,\n moved: false,\n };\n };\n\n const onPointerMove = (e: ReactPointerEvent<HTMLDivElement>): void => {\n const d = dragRef.current;\n if (!d.active) {\n return;\n }\n const dy = e.clientY - d.startY;\n if (Math.abs(dy) > 3) {\n d.moved = true;\n }\n const raw = d.startIndex - dy / itemHeight;\n setFloatIndex(loop ? raw : clamp(raw, 0, N - 1));\n const now = performance.now();\n const dt = Math.max(1, now - d.lastT);\n d.vy = (e.clientY - d.lastY) / dt;\n d.lastY = e.clientY;\n d.lastT = now;\n };\n\n const onPointerUp = (e: ReactPointerEvent<HTMLDivElement>): void => {\n const d = dragRef.current;\n if (!d.active) {\n return;\n }\n d.active = false;\n try {\n containerRef.current?.releasePointerCapture(e.pointerId);\n } catch {\n /* pointer may already be released */\n }\n const coastPx = d.vy * COAST_FACTOR;\n const projected = floatIndexRef.current - coastPx / itemHeight;\n const target = loop\n ? Math.round(projected)\n : clamp(Math.round(projected), 0, N - 1);\n animateTo(target);\n };\n\n const onKeyDown = (e: ReactKeyboardEvent<HTMLDivElement>): void => {\n let target = Math.round(floatIndexRef.current);\n if (e.key === \"ArrowUp\") {\n target -= 1;\n } else if (e.key === \"ArrowDown\") {\n target += 1;\n } else if (e.key === \"PageUp\") {\n target -= 5;\n } else if (e.key === \"PageDown\") {\n target += 5;\n } else if (e.key === \"Home\") {\n const cur = Math.round(floatIndexRef.current);\n target = loop ? cur - pmod(cur, N) : 0;\n } else if (e.key === \"End\") {\n const cur = Math.round(floatIndexRef.current);\n target = loop ? cur - pmod(cur, N) + N - 1 : N - 1;\n } else {\n return;\n }\n e.preventDefault();\n if (!loop) {\n target = clamp(target, 0, N - 1);\n }\n animateTo(target);\n };\n\n const halfVisible = Math.floor(visibleCount / 2);\n const windowRadius = halfVisible + 2;\n const centerInt = Math.round(floatIndex);\n\n const visibleItems: Array<{\n key: string;\n itemIdx: number;\n offsetSlot: number;\n label: string;\n }> = [];\n for (let off = -windowRadius; off <= windowRadius; off++) {\n const rawIdx = centerInt + off;\n if (!loop && (rawIdx < 0 || rawIdx >= N)) {\n continue;\n }\n const itemIdx = pmod(rawIdx, N);\n const label = items[itemIdx];\n if (label === undefined) {\n continue;\n }\n visibleItems.push({\n key: String(rawIdx),\n itemIdx,\n offsetSlot: rawIdx,\n label,\n });\n }\n\n const rowOffset = (slot: number): number => (slot - floatIndex) * itemHeight;\n const selectedIndex = N > 0 ? resolveIndex(floatIndex) : 0;\n\n if (N === 0) {\n return (\n <div\n aria-label={ariaLabel}\n className={cn(\n \"relative h-full w-full overflow-hidden bg-white dark:bg-gray-900\",\n className\n )}\n role=\"listbox\"\n />\n );\n }\n\n return (\n <div\n aria-activedescendant={optionId(selectedIndex)}\n aria-label={ariaLabel}\n aria-orientation=\"vertical\"\n className={cn(\n \"relative h-full w-full cursor-grab touch-none select-none overflow-hidden bg-white outline-hidden active:cursor-grabbing focus-visible:[box-shadow:inset_0_0_0_2px_var(--brand-primary)] dark:bg-gray-900\",\n className\n )}\n onKeyDown={onKeyDown}\n onPointerCancel={onPointerUp}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n ref={containerRef}\n role=\"listbox\"\n style={{ \"--sd-item-h\": `${itemHeight}px` } as React.CSSProperties}\n tabIndex={0}\n >\n <div\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-x-0 top-[-1px] z-[2] h-9 bg-gradient-to-b from-white from-[12%] to-transparent dark:from-gray-900\"\n />\n <div\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-x-0 bottom-[-1px] z-[2] h-9 bg-gradient-to-t from-white from-[12%] to-transparent dark:from-gray-900\"\n />\n <div\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-x-2 top-1/2 z-0 h-[41px] -translate-y-1/2 rounded-sm border border-brand-primary bg-brand-secondary\"\n />\n\n <div className=\"pointer-events-none absolute inset-0\">\n {visibleItems.map(({ key, itemIdx, offsetSlot, label }) => {\n const isSelected =\n itemIdx === selectedIndex &&\n Math.abs(offsetSlot - floatIndex) <= 0.5;\n return (\n <button\n aria-selected={isSelected}\n className={cn(\n \"pointer-events-auto absolute top-1/2 left-1/2 z-[1] m-0 box-border flex h-[var(--sd-item-h,52px)] w-full cursor-pointer items-center justify-center overflow-hidden border-0 bg-transparent px-1.5 py-0 transition-colors duration-150\",\n isSelected\n ? \"text-brand-primary-hover dark:text-white\"\n : \"text-gray-600 dark:text-gray-300\"\n )}\n id={isSelected ? optionId(itemIdx) : undefined}\n key={key}\n onClick={() => {\n if (dragRef.current.moved) {\n return;\n }\n animateTo(offsetSlot);\n }}\n role=\"option\"\n style={{\n transform: `translate3d(-50%, calc(-50% + ${rowOffset(offsetSlot)}px), 0)`,\n }}\n tabIndex={-1}\n type=\"button\"\n >\n <span className=\"inline-block whitespace-nowrap text-center font-sans font-semibold text-[30px] leading-9 tracking-[-1px] [font-feature-settings:'ss03']\">\n {truncate(label)}\n </span>\n </button>\n );\n })}\n </div>\n </div>\n );\n};\n\ninterface ScrollDrumGroupProps {\n /** One or more {@link ScrollDrumColumn} children. */\n children: ReactNode;\n /** Additional class names merged onto the group container. */\n className?: string;\n /** Glyph rendered between adjacent columns. Ignored when `showSeparators` is false. */\n separator?: string;\n /** When `false`, the gutter between columns is preserved but no glyph is rendered. */\n showSeparators?: boolean;\n}\n\n/**\n * Gray container that holds one or more {@link ScrollDrumColumn} children.\n * Optionally renders a glyph between columns (e.g. `:` for time, `/` for date).\n */\nconst ScrollDrumGroup = ({\n children,\n separator = \":\",\n showSeparators = true,\n className,\n}: ScrollDrumGroupProps): ReactNode => {\n const cols = Children.toArray(children);\n return (\n <div\n className={cn(\n \"inline-flex flex-row items-center justify-center rounded-lg bg-gray-100 px-8 pt-6 pb-4 dark:bg-gray-800\",\n className\n )}\n >\n {cols.map((col, idx) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: columns are static and not reordered\n <Fragment key={idx}>\n {col}\n {idx < cols.length - 1 && (\n <div\n aria-hidden=\"true\"\n className=\"flex h-[120px] w-8 flex-none items-center justify-center self-start\"\n >\n {showSeparators && (\n <span className=\"text-center font-sans font-semibold text-[36px] text-gray-600 leading-10 tracking-[-1.5px] [font-feature-settings:'ss03'] dark:text-gray-400\">\n {separator}\n </span>\n )}\n </div>\n )}\n </Fragment>\n ))}\n </div>\n );\n};\n\ninterface ScrollDrumColumnProps {\n /** Should be a single {@link ScrollDrum}. */\n children: ReactNode;\n /** Additional class names merged onto the column wrapper. */\n className?: string;\n /** Caption rendered below the drum slab. Pass `\"\"` for an invisible spacer. */\n label?: string;\n /** Hard character cap that drives the auto-computed column width. */\n maxChars?: number;\n /** Explicit pixel width override. When omitted, scales with `maxChars`. */\n width?: number;\n}\n\n/**\n * Pairs a {@link ScrollDrum} with an optional caption underneath. When `width`\n * is omitted the column auto-sizes via `Math.max(72, 24 + maxChars * 16)`, so\n * a 4-char drum (e.g. `\"2026\"`) lands at 88px and a 2-char drum stays at the\n * 72px floor.\n */\nconst ScrollDrumColumn = ({\n label,\n width,\n maxChars = DEFAULT_MAX_CHARS,\n children,\n className,\n}: ScrollDrumColumnProps): ReactNode => {\n const computed = width ?? Math.max(72, 24 + maxChars * 16);\n return (\n <div\n className={cn(\"flex flex-none flex-col gap-2 p-0\", className)}\n style={{ width: computed }}\n >\n <div className=\"flex h-[120px] flex-none items-center justify-center bg-white dark:bg-gray-900\">\n {children}\n </div>\n {label !== undefined && (\n <div className=\"h-6 text-center font-sans font-semibold text-base text-gray-600 leading-6 [font-feature-settings:'ss03'] dark:text-gray-300\">\n {label || \"\u00A0\"}\n </div>\n )}\n </div>\n );\n};\n\nexport type { ScrollDrumColumnProps, ScrollDrumGroupProps, ScrollDrumProps };\nexport { ScrollDrum, ScrollDrumColumn, ScrollDrumGroup };\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,49 @@
1
+ import { Select as SelectPrimitive } from "radix-ui";
2
+ import type * as React from "react";
3
+ import type { Ref } from "react";
4
+ /** Root component that manages select state. */
5
+ declare const Select: React.FC<SelectPrimitive.SelectProps>;
6
+ /** Container for grouping related select items with a label. */
7
+ declare const SelectGroup: React.ForwardRefExoticComponent<SelectPrimitive.SelectGroupProps & React.RefAttributes<HTMLDivElement>>;
8
+ /** Displays the selected value or placeholder text. */
9
+ declare const SelectValue: React.ForwardRefExoticComponent<SelectPrimitive.SelectValueProps & React.RefAttributes<HTMLSpanElement>>;
10
+ /**
11
+ * Button that opens the select dropdown.
12
+ * @param icon - Custom dropdown icon (defaults to ChevronDown)
13
+ */
14
+ interface SelectTriggerProps extends Omit<React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>, "ref"> {
15
+ icon?: React.ReactNode;
16
+ ref?: Ref<React.ElementRef<typeof SelectPrimitive.Trigger>>;
17
+ }
18
+ declare const SelectTrigger: ({ className, children, icon, ref, ...props }: SelectTriggerProps) => import("react/jsx-runtime").JSX.Element;
19
+ interface SelectScrollUpButtonProps extends Omit<React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>, "ref"> {
20
+ ref?: Ref<React.ElementRef<typeof SelectPrimitive.ScrollUpButton>>;
21
+ }
22
+ declare const SelectScrollUpButton: ({ className, ref, ...props }: SelectScrollUpButtonProps) => import("react/jsx-runtime").JSX.Element;
23
+ interface SelectScrollDownButtonProps extends Omit<React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>, "ref"> {
24
+ ref?: Ref<React.ElementRef<typeof SelectPrimitive.ScrollDownButton>>;
25
+ }
26
+ declare const SelectScrollDownButton: ({ className, ref, ...props }: SelectScrollDownButtonProps) => import("react/jsx-runtime").JSX.Element;
27
+ interface SelectContentProps extends Omit<React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>, "ref"> {
28
+ ref?: Ref<React.ElementRef<typeof SelectPrimitive.Content>>;
29
+ }
30
+ declare const SelectContent: ({ className, children, position, ref, ...props }: SelectContentProps) => import("react/jsx-runtime").JSX.Element;
31
+ interface SelectContentPopperProps extends Omit<React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>, "ref"> {
32
+ ref?: Ref<React.ElementRef<typeof SelectPrimitive.Content>>;
33
+ }
34
+ declare const SelectContentPopper: ({ className, children, position, ref, ...props }: SelectContentPopperProps) => import("react/jsx-runtime").JSX.Element;
35
+ interface SelectLabelProps extends Omit<React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>, "ref"> {
36
+ ref?: Ref<React.ElementRef<typeof SelectPrimitive.Label>>;
37
+ }
38
+ declare const SelectLabel: ({ className, ref, ...props }: SelectLabelProps) => import("react/jsx-runtime").JSX.Element;
39
+ interface SelectItemProps extends Omit<React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>, "ref"> {
40
+ hideCheck?: boolean;
41
+ ref?: Ref<React.ElementRef<typeof SelectPrimitive.Item>>;
42
+ }
43
+ declare const SelectItem: ({ className, children, hideCheck, ref, ...props }: SelectItemProps) => import("react/jsx-runtime").JSX.Element;
44
+ interface SelectSeparatorProps extends Omit<React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>, "ref"> {
45
+ ref?: Ref<React.ElementRef<typeof SelectPrimitive.Separator>>;
46
+ }
47
+ declare const SelectSeparator: ({ className, ref, ...props }: SelectSeparatorProps) => import("react/jsx-runtime").JSX.Element;
48
+ export { Select, SelectContent, SelectContentPopper, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, };
49
+ //# sourceMappingURL=select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../src/components/select.tsx"],"names":[],"mappings":"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"}
@@ -143,7 +143,7 @@ const SelectItem = ({
143
143
  SelectPrimitive.Item,
144
144
  {
145
145
  className: cn(
146
- "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-base outline-none focus:bg-gray-50 focus:text-gray-950 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-gray-900 dark:focus:text-white",
146
+ "relative flex w-full cursor-default select-none items-center rounded-xs py-1.5 pr-2 pl-8 text-base outline-hidden focus:bg-gray-50 focus:text-gray-950 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-gray-900 dark:focus:text-white",
147
147
  className
148
148
  ),
149
149
  ref,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/components/select.tsx"],
4
- "sourcesContent": ["/**\n * @module Select\n *\n * Dropdown select menu for single value selection. Built on Radix UI Select primitive.\n * Includes keyboard navigation, typeahead, and customizable styling.\n *\n * @see {@link https://ui.shadcn.com/docs/components/select Shadcn Select}\n * @see {@link https://www.radix-ui.com/primitives/docs/components/select Radix Select}\n *\n * @example\n * // Basic select\n * <Select>\n * <SelectTrigger className=\"w-48\">\n * <SelectValue placeholder=\"Select option\" />\n * </SelectTrigger>\n * <SelectContent>\n * <SelectItem value=\"a\">Option A</SelectItem>\n * <SelectItem value=\"b\">Option B</SelectItem>\n * </SelectContent>\n * </Select>\n *\n * @example\n * // Grouped options\n * <SelectContent>\n * <SelectGroup>\n * <SelectLabel>Fruits</SelectLabel>\n * <SelectItem value=\"apple\">Apple</SelectItem>\n * <SelectItem value=\"banana\">Banana</SelectItem>\n * </SelectGroup>\n * <SelectSeparator />\n * <SelectGroup>\n * <SelectLabel>Vegetables</SelectLabel>\n * <SelectItem value=\"carrot\">Carrot</SelectItem>\n * </SelectGroup>\n * </SelectContent>\n */\nimport { Check, ChevronDown, ChevronUp } from \"lucide-react\";\nimport { Select as SelectPrimitive } from \"radix-ui\";\nimport type * as React from \"react\";\nimport type { Ref } from \"react\";\n\nimport { cn } from \"../lib/utils\";\n\n/** Root component that manages select state. */\nconst Select = SelectPrimitive.Root;\n\n/** Container for grouping related select items with a label. */\nconst SelectGroup = SelectPrimitive.Group;\n\n/** Displays the selected value or placeholder text. */\nconst SelectValue = SelectPrimitive.Value;\n\n/**\n * Button that opens the select dropdown.\n * @param icon - Custom dropdown icon (defaults to ChevronDown)\n */\ninterface SelectTriggerProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>,\n \"ref\"\n > {\n icon?: React.ReactNode;\n ref?: Ref<React.ElementRef<typeof SelectPrimitive.Trigger>>;\n}\n\nconst SelectTrigger = ({\n className,\n children,\n icon,\n ref,\n ...props\n}: SelectTriggerProps) => (\n <SelectPrimitive.Trigger\n className={cn(\n \"group flex h-10 w-full min-w-44 items-center justify-between gap-2 rounded-lg border border-gray-200 border-solid bg-white px-4 py-2 text-base transition-colors focus-visible:border-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--focus-ring)] focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1\",\n \"dark:border-gray-800 dark:bg-gray-950 dark:text-white dark:focus-visible:border-gray-600 dark:focus-visible:ring-[var(--focus-ring)] dark:focus-visible:ring-offset-gray-950\",\n className\n )}\n ref={ref}\n {...props}\n >\n {children}\n <SelectPrimitive.Icon asChild>\n {icon ?? <ChevronDown className=\"h-5 w-5\" />}\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n);\n\ninterface SelectScrollUpButtonProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>,\n \"ref\"\n > {\n ref?: Ref<React.ElementRef<typeof SelectPrimitive.ScrollUpButton>>;\n}\n\nconst SelectScrollUpButton = ({\n className,\n ref,\n ...props\n}: SelectScrollUpButtonProps) => (\n <SelectPrimitive.ScrollUpButton\n className={cn(\n \"flex cursor-default items-center justify-center py-1\",\n className\n )}\n ref={ref}\n {...props}\n >\n <ChevronUp className=\"h-5 w-5\" />\n </SelectPrimitive.ScrollUpButton>\n);\n\ninterface SelectScrollDownButtonProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>,\n \"ref\"\n > {\n ref?: Ref<React.ElementRef<typeof SelectPrimitive.ScrollDownButton>>;\n}\n\nconst SelectScrollDownButton = ({\n className,\n ref,\n ...props\n}: SelectScrollDownButtonProps) => (\n <SelectPrimitive.ScrollDownButton\n className={cn(\n \"flex cursor-default items-center justify-center py-1\",\n className\n )}\n ref={ref}\n {...props}\n >\n <ChevronDown className=\"h-4 w-4\" />\n </SelectPrimitive.ScrollDownButton>\n);\n\ninterface SelectContentProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>,\n \"ref\"\n > {\n ref?: Ref<React.ElementRef<typeof SelectPrimitive.Content>>;\n}\n\nconst SelectContent = ({\n className,\n children,\n position,\n ref,\n ...props\n}: SelectContentProps) => (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n className={cn(\n \"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-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 relative z-50 max-h-96 min-w-[8rem] rounded-lg border border-gray-200 border-solid bg-white text-gray-950 shadow-[0px_2px_4px_0px_rgba(0,_0,_0,_0.15)] data-[state=closed]:animate-out data-[state=open]:animate-in dark:border-gray-800 dark:bg-gray-950 dark:text-white\",\n position === \"popper\" &&\n \"data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=bottom]:translate-y-1 data-[side=top]:-translate-y-1\",\n className\n )}\n position={position}\n ref={ref}\n {...props}\n >\n <SelectScrollUpButton />\n <SelectPrimitive.Viewport\n className={cn(\n \"p-1\",\n position === \"popper\" &&\n \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]\"\n )}\n >\n {children}\n </SelectPrimitive.Viewport>\n <SelectScrollDownButton />\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n);\n\ninterface SelectContentPopperProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>,\n \"ref\"\n > {\n ref?: Ref<React.ElementRef<typeof SelectPrimitive.Content>>;\n}\n\nconst SelectContentPopper = ({\n className,\n children,\n position = \"popper\",\n ref,\n ...props\n}: SelectContentPopperProps) => (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n className={cn(\n \"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-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 relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-lg border border-gray-200 border-solid bg-white text-gray-950 shadow-100 data-[state=closed]:animate-out data-[state=open]:animate-in dark:border-gray-800 dark:bg-gray-950 dark:text-white\",\n position === \"popper\" &&\n \"data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=bottom]:translate-y-1 data-[side=top]:-translate-y-1\",\n className\n )}\n position={position}\n ref={ref}\n {...props}\n >\n <SelectScrollUpButton />\n <SelectPrimitive.Viewport\n className={cn(\n \"p-1\",\n position === \"popper\" &&\n \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]\"\n )}\n >\n {children}\n </SelectPrimitive.Viewport>\n <SelectScrollDownButton />\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n);\n\ninterface SelectLabelProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>,\n \"ref\"\n > {\n ref?: Ref<React.ElementRef<typeof SelectPrimitive.Label>>;\n}\n\nconst SelectLabel = ({ className, ref, ...props }: SelectLabelProps) => (\n <SelectPrimitive.Label\n className={cn(\"py-1.5 pr-2 pl-8 font-semibold text-base\", className)}\n ref={ref}\n {...props}\n />\n);\n\ninterface SelectItemProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>,\n \"ref\"\n > {\n hideCheck?: boolean;\n ref?: Ref<React.ElementRef<typeof SelectPrimitive.Item>>;\n}\n\nconst SelectItem = ({\n className,\n children,\n hideCheck,\n ref,\n ...props\n}: SelectItemProps) => (\n <SelectPrimitive.Item\n className={cn(\n \"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-base outline-none focus:bg-gray-50 focus:text-gray-950 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-gray-900 dark:focus:text-white\",\n className\n )}\n ref={ref}\n {...props}\n >\n {hideCheck ? null : (\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <SelectPrimitive.ItemIndicator>\n <Check className=\"h-4 w-4\" />\n </SelectPrimitive.ItemIndicator>\n </span>\n )}\n\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n);\n\ninterface SelectSeparatorProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>,\n \"ref\"\n > {\n ref?: Ref<React.ElementRef<typeof SelectPrimitive.Separator>>;\n}\n\nconst SelectSeparator = ({\n className,\n ref,\n ...props\n}: SelectSeparatorProps) => (\n <SelectPrimitive.Separator\n className={cn(\"-mx-1 my-1 h-px bg-gray-150 dark:bg-gray-800\", className)}\n ref={ref}\n {...props}\n />\n);\n\nexport {\n Select,\n SelectGroup,\n SelectValue,\n SelectTrigger,\n SelectContent,\n SelectContentPopper,\n SelectLabel,\n SelectItem,\n SelectSeparator,\n SelectScrollUpButton,\n SelectScrollDownButton,\n};\n"],
4
+ "sourcesContent": ["/**\n * @module Select\n *\n * Dropdown select menu for single value selection. Built on Radix UI Select primitive.\n * Includes keyboard navigation, typeahead, and customizable styling.\n *\n * @see {@link https://ui.shadcn.com/docs/components/select Shadcn Select}\n * @see {@link https://www.radix-ui.com/primitives/docs/components/select Radix Select}\n *\n * @example\n * // Basic select\n * <Select>\n * <SelectTrigger className=\"w-48\">\n * <SelectValue placeholder=\"Select option\" />\n * </SelectTrigger>\n * <SelectContent>\n * <SelectItem value=\"a\">Option A</SelectItem>\n * <SelectItem value=\"b\">Option B</SelectItem>\n * </SelectContent>\n * </Select>\n *\n * @example\n * // Grouped options\n * <SelectContent>\n * <SelectGroup>\n * <SelectLabel>Fruits</SelectLabel>\n * <SelectItem value=\"apple\">Apple</SelectItem>\n * <SelectItem value=\"banana\">Banana</SelectItem>\n * </SelectGroup>\n * <SelectSeparator />\n * <SelectGroup>\n * <SelectLabel>Vegetables</SelectLabel>\n * <SelectItem value=\"carrot\">Carrot</SelectItem>\n * </SelectGroup>\n * </SelectContent>\n */\nimport { Check, ChevronDown, ChevronUp } from \"lucide-react\";\nimport { Select as SelectPrimitive } from \"radix-ui\";\nimport type * as React from \"react\";\nimport type { Ref } from \"react\";\n\nimport { cn } from \"../lib/utils\";\n\n/** Root component that manages select state. */\nconst Select = SelectPrimitive.Root;\n\n/** Container for grouping related select items with a label. */\nconst SelectGroup = SelectPrimitive.Group;\n\n/** Displays the selected value or placeholder text. */\nconst SelectValue = SelectPrimitive.Value;\n\n/**\n * Button that opens the select dropdown.\n * @param icon - Custom dropdown icon (defaults to ChevronDown)\n */\ninterface SelectTriggerProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>,\n \"ref\"\n > {\n icon?: React.ReactNode;\n ref?: Ref<React.ElementRef<typeof SelectPrimitive.Trigger>>;\n}\n\nconst SelectTrigger = ({\n className,\n children,\n icon,\n ref,\n ...props\n}: SelectTriggerProps) => (\n <SelectPrimitive.Trigger\n className={cn(\n \"group flex h-10 w-full min-w-44 items-center justify-between gap-2 rounded-lg border border-gray-200 border-solid bg-white px-4 py-2 text-base transition-colors focus-visible:border-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--focus-ring)] focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1\",\n \"dark:border-gray-800 dark:bg-gray-950 dark:text-white dark:focus-visible:border-gray-600 dark:focus-visible:ring-[var(--focus-ring)] dark:focus-visible:ring-offset-gray-950\",\n className\n )}\n ref={ref}\n {...props}\n >\n {children}\n <SelectPrimitive.Icon asChild>\n {icon ?? <ChevronDown className=\"h-5 w-5\" />}\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n);\n\ninterface SelectScrollUpButtonProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>,\n \"ref\"\n > {\n ref?: Ref<React.ElementRef<typeof SelectPrimitive.ScrollUpButton>>;\n}\n\nconst SelectScrollUpButton = ({\n className,\n ref,\n ...props\n}: SelectScrollUpButtonProps) => (\n <SelectPrimitive.ScrollUpButton\n className={cn(\n \"flex cursor-default items-center justify-center py-1\",\n className\n )}\n ref={ref}\n {...props}\n >\n <ChevronUp className=\"h-5 w-5\" />\n </SelectPrimitive.ScrollUpButton>\n);\n\ninterface SelectScrollDownButtonProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>,\n \"ref\"\n > {\n ref?: Ref<React.ElementRef<typeof SelectPrimitive.ScrollDownButton>>;\n}\n\nconst SelectScrollDownButton = ({\n className,\n ref,\n ...props\n}: SelectScrollDownButtonProps) => (\n <SelectPrimitive.ScrollDownButton\n className={cn(\n \"flex cursor-default items-center justify-center py-1\",\n className\n )}\n ref={ref}\n {...props}\n >\n <ChevronDown className=\"h-4 w-4\" />\n </SelectPrimitive.ScrollDownButton>\n);\n\ninterface SelectContentProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>,\n \"ref\"\n > {\n ref?: Ref<React.ElementRef<typeof SelectPrimitive.Content>>;\n}\n\nconst SelectContent = ({\n className,\n children,\n position,\n ref,\n ...props\n}: SelectContentProps) => (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n className={cn(\n \"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-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 relative z-50 max-h-96 min-w-[8rem] rounded-lg border border-gray-200 border-solid bg-white text-gray-950 shadow-[0px_2px_4px_0px_rgba(0,_0,_0,_0.15)] data-[state=closed]:animate-out data-[state=open]:animate-in dark:border-gray-800 dark:bg-gray-950 dark:text-white\",\n position === \"popper\" &&\n \"data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=bottom]:translate-y-1 data-[side=top]:-translate-y-1\",\n className\n )}\n position={position}\n ref={ref}\n {...props}\n >\n <SelectScrollUpButton />\n <SelectPrimitive.Viewport\n className={cn(\n \"p-1\",\n position === \"popper\" &&\n \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]\"\n )}\n >\n {children}\n </SelectPrimitive.Viewport>\n <SelectScrollDownButton />\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n);\n\ninterface SelectContentPopperProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>,\n \"ref\"\n > {\n ref?: Ref<React.ElementRef<typeof SelectPrimitive.Content>>;\n}\n\nconst SelectContentPopper = ({\n className,\n children,\n position = \"popper\",\n ref,\n ...props\n}: SelectContentPopperProps) => (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n className={cn(\n \"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-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 relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-lg border border-gray-200 border-solid bg-white text-gray-950 shadow-100 data-[state=closed]:animate-out data-[state=open]:animate-in dark:border-gray-800 dark:bg-gray-950 dark:text-white\",\n position === \"popper\" &&\n \"data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=bottom]:translate-y-1 data-[side=top]:-translate-y-1\",\n className\n )}\n position={position}\n ref={ref}\n {...props}\n >\n <SelectScrollUpButton />\n <SelectPrimitive.Viewport\n className={cn(\n \"p-1\",\n position === \"popper\" &&\n \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]\"\n )}\n >\n {children}\n </SelectPrimitive.Viewport>\n <SelectScrollDownButton />\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n);\n\ninterface SelectLabelProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>,\n \"ref\"\n > {\n ref?: Ref<React.ElementRef<typeof SelectPrimitive.Label>>;\n}\n\nconst SelectLabel = ({ className, ref, ...props }: SelectLabelProps) => (\n <SelectPrimitive.Label\n className={cn(\"py-1.5 pr-2 pl-8 font-semibold text-base\", className)}\n ref={ref}\n {...props}\n />\n);\n\ninterface SelectItemProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>,\n \"ref\"\n > {\n hideCheck?: boolean;\n ref?: Ref<React.ElementRef<typeof SelectPrimitive.Item>>;\n}\n\nconst SelectItem = ({\n className,\n children,\n hideCheck,\n ref,\n ...props\n}: SelectItemProps) => (\n <SelectPrimitive.Item\n className={cn(\n \"relative flex w-full cursor-default select-none items-center rounded-xs py-1.5 pr-2 pl-8 text-base outline-hidden focus:bg-gray-50 focus:text-gray-950 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-gray-900 dark:focus:text-white\",\n className\n )}\n ref={ref}\n {...props}\n >\n {hideCheck ? null : (\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <SelectPrimitive.ItemIndicator>\n <Check className=\"h-4 w-4\" />\n </SelectPrimitive.ItemIndicator>\n </span>\n )}\n\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n);\n\ninterface SelectSeparatorProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>,\n \"ref\"\n > {\n ref?: Ref<React.ElementRef<typeof SelectPrimitive.Separator>>;\n}\n\nconst SelectSeparator = ({\n className,\n ref,\n ...props\n}: SelectSeparatorProps) => (\n <SelectPrimitive.Separator\n className={cn(\"-mx-1 my-1 h-px bg-gray-150 dark:bg-gray-800\", className)}\n ref={ref}\n {...props}\n />\n);\n\nexport {\n Select,\n SelectContent,\n SelectContentPopper,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectScrollDownButton,\n SelectScrollUpButton,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n};\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }