@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.
- package/README.md +5 -44
- package/dist/components/accordion.d.ts +71 -0
- package/dist/components/accordion.d.ts.map +1 -0
- package/dist/components/accordion.js +2 -2
- package/dist/components/accordion.js.map +1 -1
- package/dist/components/badge.d.ts +62 -0
- package/dist/components/badge.d.ts.map +1 -0
- package/dist/components/badge.js +1 -1
- package/dist/components/badge.js.map +1 -1
- package/dist/components/breadcrumb.d.ts +42 -0
- package/dist/components/breadcrumb.d.ts.map +1 -0
- package/dist/components/button.d.ts +117 -0
- package/dist/components/button.d.ts.map +1 -0
- package/dist/components/button.js +3 -3
- package/dist/components/button.js.map +1 -1
- package/dist/components/card.d.ts +56 -0
- package/dist/components/card.d.ts.map +1 -0
- package/dist/components/card.js.map +1 -1
- package/dist/components/carousel-dots.d.ts +17 -0
- package/dist/components/carousel-dots.d.ts.map +1 -0
- package/dist/components/carousel-dots.js +1 -1
- package/dist/components/carousel-dots.js.map +1 -1
- package/dist/components/carousel.d.ts +99 -0
- package/dist/components/carousel.d.ts.map +1 -0
- package/dist/components/carousel.js +2 -2
- package/dist/components/carousel.js.map +1 -1
- package/dist/components/checkbox.d.ts +34 -0
- package/dist/components/checkbox.d.ts.map +1 -0
- package/dist/components/checkbox.js +1 -1
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/context-menu.d.ts +126 -0
- package/dist/components/context-menu.d.ts.map +1 -0
- package/dist/components/context-menu.js +6 -6
- package/dist/components/context-menu.js.map +1 -1
- package/dist/components/dialog.d.ts +85 -0
- package/dist/components/dialog.d.ts.map +1 -0
- package/dist/components/dialog.js +1 -1
- package/dist/components/dialog.js.map +1 -1
- package/dist/components/drawer.d.ts +90 -0
- package/dist/components/drawer.d.ts.map +1 -0
- package/dist/components/drawer.js.map +1 -1
- package/dist/components/index.d.ts +36 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/input.d.ts +69 -0
- package/dist/components/input.d.ts.map +1 -0
- package/dist/components/input.js +61 -57
- package/dist/components/input.js.map +2 -2
- package/dist/components/label.d.ts +36 -0
- package/dist/components/label.d.ts.map +1 -0
- package/dist/components/popover.d.ts +61 -0
- package/dist/components/popover.d.ts.map +1 -0
- package/dist/components/popover.js +1 -1
- package/dist/components/popover.js.map +1 -1
- package/dist/components/price-tag.d.ts +31 -0
- package/dist/components/price-tag.d.ts.map +1 -0
- package/dist/components/price-tag.js.map +1 -1
- package/dist/components/radio-group.d.ts +15 -0
- package/dist/components/radio-group.d.ts.map +1 -0
- package/dist/components/radio-group.js +1 -1
- package/dist/components/radio-group.js.map +1 -1
- package/dist/components/scroll-area.d.ts +33 -0
- package/dist/components/scroll-area.d.ts.map +1 -0
- package/dist/components/scroll-area.js.map +1 -1
- package/dist/components/scroll-drum.d.ts +96 -0
- package/dist/components/scroll-drum.d.ts.map +1 -0
- package/dist/components/scroll-drum.js +63 -34
- package/dist/components/scroll-drum.js.map +2 -2
- package/dist/components/select.d.ts +49 -0
- package/dist/components/select.d.ts.map +1 -0
- package/dist/components/select.js +1 -1
- package/dist/components/select.js.map +1 -1
- package/dist/components/separator.d.ts +35 -0
- package/dist/components/separator.d.ts.map +1 -0
- package/dist/components/skeleton.d.ts +44 -0
- package/dist/components/skeleton.d.ts.map +1 -0
- package/dist/components/slider.d.ts +21 -0
- package/dist/components/slider.d.ts.map +1 -0
- package/dist/components/slider.js +1 -1
- package/dist/components/slider.js.map +1 -1
- package/dist/components/stacking-card.d.ts +89 -0
- package/dist/components/stacking-card.d.ts.map +1 -0
- package/dist/components/stacking-card.js +3 -3
- package/dist/components/stacking-card.js.map +2 -2
- package/dist/components/tabs.d.ts +46 -0
- package/dist/components/tabs.d.ts.map +1 -0
- package/dist/components/tabs.js +2 -2
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/textarea.d.ts +34 -0
- package/dist/components/textarea.d.ts.map +1 -0
- package/dist/components/toast.d.ts +67 -0
- package/dist/components/toast.d.ts.map +1 -0
- package/dist/components/toast.js +2 -2
- package/dist/components/toast.js.map +2 -2
- package/dist/components/toggle-button.d.ts +54 -0
- package/dist/components/toggle-button.d.ts.map +1 -0
- package/dist/components/toggle-button.js.map +1 -1
- package/dist/components/typography/heading.d.ts +20 -0
- package/dist/components/typography/heading.d.ts.map +1 -0
- package/dist/components/typography/heading.js.map +1 -1
- package/dist/components/typography/index.d.ts +5 -0
- package/dist/components/typography/index.d.ts.map +1 -0
- package/dist/components/typography/list.d.ts +23 -0
- package/dist/components/typography/list.d.ts.map +1 -0
- package/dist/components/typography/list.js +1 -1
- package/dist/components/typography/list.js.map +2 -2
- package/dist/components/typography/prose.d.ts +8 -0
- package/dist/components/typography/prose.d.ts.map +1 -0
- package/dist/components/typography/text.d.ts +13 -0
- package/dist/components/typography/text.d.ts.map +1 -0
- package/dist/icons/custom/boards-indicator.d.ts +6 -0
- package/dist/icons/custom/boards-indicator.d.ts.map +1 -0
- package/dist/icons/custom/boards-indicator.js +7 -2
- package/dist/icons/custom/boards-indicator.js.map +2 -2
- package/dist/icons/custom/download-history.d.ts +5 -0
- package/dist/icons/custom/download-history.d.ts.map +1 -0
- package/dist/icons/custom/download-history.js +3 -4
- package/dist/icons/custom/download-history.js.map +2 -2
- package/dist/icons/custom/exclamation-mark-in-octagon.d.ts +5 -0
- package/dist/icons/custom/exclamation-mark-in-octagon.d.ts.map +1 -0
- package/dist/icons/custom/exclamation-mark-in-octagon.js +6 -3
- package/dist/icons/custom/exclamation-mark-in-octagon.js.map +2 -2
- package/dist/icons/custom/horizontal-orientation.d.ts +5 -0
- package/dist/icons/custom/horizontal-orientation.d.ts.map +1 -0
- package/dist/icons/custom/horizontal-orientation.js +4 -3
- package/dist/icons/custom/horizontal-orientation.js.map +2 -2
- package/dist/icons/custom/lightning-bolt.d.ts +5 -0
- package/dist/icons/custom/lightning-bolt.d.ts.map +1 -0
- package/dist/icons/custom/lightning-bolt.js +24 -27
- package/dist/icons/custom/lightning-bolt.js.map +2 -2
- package/dist/icons/custom/music-file.d.ts +5 -0
- package/dist/icons/custom/music-file.d.ts.map +1 -0
- package/dist/icons/custom/music-file.js +17 -0
- package/dist/icons/custom/music-file.js.map +7 -0
- package/dist/icons/custom/pin.d.ts +5 -0
- package/dist/icons/custom/pin.d.ts.map +1 -0
- package/dist/icons/custom/pin.js +4 -1
- package/dist/icons/custom/pin.js.map +2 -2
- package/dist/icons/custom/premium-star.d.ts +11 -0
- package/dist/icons/custom/premium-star.d.ts.map +1 -0
- package/dist/icons/custom/premium-star.js +3 -1
- package/dist/icons/custom/premium-star.js.map +2 -2
- package/dist/icons/custom/social/discord.d.ts +5 -0
- package/dist/icons/custom/social/discord.d.ts.map +1 -0
- package/dist/icons/custom/social/discord.js +3 -4
- package/dist/icons/custom/social/discord.js.map +2 -2
- package/dist/icons/custom/social/index.d.ts +4 -0
- package/dist/icons/custom/social/index.d.ts.map +1 -0
- package/dist/icons/custom/social/tiktok.d.ts +5 -0
- package/dist/icons/custom/social/tiktok.d.ts.map +1 -0
- package/dist/icons/custom/social/tiktok.js +3 -4
- package/dist/icons/custom/social/tiktok.js.map +2 -2
- package/dist/icons/custom/social/twitter-x.d.ts +5 -0
- package/dist/icons/custom/social/twitter-x.d.ts.map +1 -0
- package/dist/icons/custom/social/twitter-x.js +19 -22
- package/dist/icons/custom/social/twitter-x.js.map +2 -2
- package/dist/icons/custom/square-aspect-ratio.d.ts +5 -0
- package/dist/icons/custom/square-aspect-ratio.d.ts.map +1 -0
- package/dist/icons/custom/square-aspect-ratio.js +4 -1
- package/dist/icons/custom/square-aspect-ratio.js.map +2 -2
- package/dist/icons/custom/tick-in-circle.d.ts +8 -0
- package/dist/icons/custom/tick-in-circle.d.ts.map +1 -0
- package/dist/icons/custom/tick-in-circle.js +25 -25
- package/dist/icons/custom/tick-in-circle.js.map +2 -2
- package/dist/icons/custom/tick-small.d.ts +5 -0
- package/dist/icons/custom/tick-small.d.ts.map +1 -0
- package/dist/icons/custom/tick-small.js +8 -10
- package/dist/icons/custom/tick-small.js.map +2 -2
- package/dist/icons/custom/tick.d.ts +5 -0
- package/dist/icons/custom/tick.d.ts.map +1 -0
- package/dist/icons/custom/tick.js +2 -2
- package/dist/icons/custom/tick.js.map +2 -2
- package/dist/icons/custom/types.d.ts +3 -0
- package/dist/icons/custom/types.d.ts.map +1 -0
- package/dist/icons/custom/types.js +1 -0
- package/dist/icons/custom/types.js.map +7 -0
- package/dist/icons/custom/ultra-wide-aspect-ratio.d.ts +5 -0
- package/dist/icons/custom/ultra-wide-aspect-ratio.d.ts.map +1 -0
- package/dist/icons/custom/ultra-wide-aspect-ratio.js +4 -1
- package/dist/icons/custom/ultra-wide-aspect-ratio.js.map +2 -2
- package/dist/icons/custom/uppbeat-credit.d.ts +5 -0
- package/dist/icons/custom/uppbeat-credit.d.ts.map +1 -0
- package/dist/icons/custom/uppbeat-credit.js +2 -4
- package/dist/icons/custom/uppbeat-credit.js.map +2 -2
- package/dist/icons/custom/vertical-orientation.d.ts +5 -0
- package/dist/icons/custom/vertical-orientation.d.ts.map +1 -0
- package/dist/icons/custom/vertical-orientation.js +4 -1
- package/dist/icons/custom/vertical-orientation.js.map +2 -2
- package/dist/icons/custom/view-credit-note.d.ts +5 -0
- package/dist/icons/custom/view-credit-note.d.ts.map +1 -0
- package/dist/icons/custom/view-credit-note.js +4 -1
- package/dist/icons/custom/view-credit-note.js.map +2 -2
- package/dist/icons/index.d.ts +28 -0
- package/dist/icons/index.d.ts.map +1 -0
- package/dist/icons/index.js +4 -5
- package/dist/icons/index.js.map +2 -2
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/lib/utils.d.ts +12 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +7 -17
- package/dist/lib/utils.js.map +2 -2
- package/dist/styles/index.css +16 -3
- package/dist/styles/storybook.css +2 -2
- package/dist/test/setup.d.ts +2 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/dist/theme/index.d.ts +142 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +1 -1
- package/dist/theme/index.js.map +2 -2
- package/package.json +4 -9
- package/{tailwind.config.v4.css → tailwind.config.css} +35 -14
- package/dist/styles/index.v4.css +0 -49
- 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
|
|
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": "AA4DI,cAsJE,YAtJF;AA/BJ,OAAO,cAAc;AACrB,SAAS,cAAc,2BAA2B;AAElD,YAAY,WAAW;AAEvB,SAAS,UAAU;AAGnB,MAAM,YAAY,CAAC;AAAA,EACjB;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA,GAAG;AACL,MAKE;AAAA,EAAC,oBAAoB;AAAA,EAApB;AAAA,IACC,WAAW;AAAA,MACT;AAAA,MACA,gBAAgB,cACd;AAAA,MACF,gBAAgB,gBACd;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,IACC,GAAG;AAAA,IAEJ,8BAAC,oBAAoB,iBAApB,EAAoC,WAAU,6DAA4D;AAAA;AAC7G;AAaF,MAAM,WAAW,CAAC;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MACE;AAAA,EAAC;AAAA;AAAA,IACC,WAAW;AAAA,MACT;AAAA,MACA,cAAc,SACZ;AAAA,MACF,cAAc,YACZ;AAAA,MACF,cAAc,WACZ;AAAA,MACF,cAAc,UACZ;AAAA,MACF;AAAA,MACA,OAAO,gBAAgB;AAAA,MACvB;AAAA,IACF;AAAA,IACA;AAAA,IACC,GAAG;AAAA;AACN;AAmBF,MAAM,gBAAgB,CAAC,EAAE,aAAa,QAAQ,MAAwB;AACpE,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,KAAK;AAC1D,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAS,IAAI;AAC/D,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAS,KAAK;AAC5D,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAS,IAAI;AAE7D,QAAM,eAAe,MAAM,YAAY,MAAM;AAC3C,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AAEA,QAAI,YAAY,SAAS;AACvB,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,IAAI,YAAY;AAEhB,qBAAe,YAAY,CAAC;AAC5B,wBAAkB,YAAY,eAAe,eAAe,CAAC;AAC7D,sBAAgB,aAAa,CAAC;AAC9B,uBAAiB,aAAa,cAAc,cAAc,CAAC;AAAA,IAC7D;AAAA,EACF,GAAG,CAAC,aAAa,OAAO,CAAC;AAEzB,QAAM,wBAAwB,MAAM;AAAA,IAClC,MAAM,SAAS,cAAc,EAAE;AAAA,IAC/B,CAAC,YAAY;AAAA,EACf;AAEA,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AAEA,UAAM,WAAW,YAAY;AAE7B,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AAGA,0BAAsB;AAGtB,aAAS,iBAAiB,UAAU,uBAAuB;AAAA,MACzD,SAAS;AAAA,IACX,CAAC;AAGD,UAAM,iBAAiB,IAAI,eAAe,qBAAqB;AAE/D,mBAAe,QAAQ,QAAQ;AAE/B,WAAO,MAAM;AACX,eAAS,oBAAoB,UAAU,qBAAqB;AAC5D,qBAAe,WAAW;AAC1B,4BAAsB,OAAO;AAAA,IAC/B;AAAA,EACF,GAAG,CAAC,uBAAuB,aAAa,OAAO,CAAC;AAEhD,SAAO,EAAE,aAAa,gBAAgB,cAAc,cAAc;AACpE;AAEA,MAAM,aAAa,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAEM;AACJ,QAAM,cAAc,MAAM,OAAuB,IAAI;AACrD,QAAM,EAAE,aAAa,gBAAgB,cAAc,cAAc,IAC/D,cAAc;AAAA,IACZ;AAAA,IACA,SAAS,CAAC,CAAC;AAAA,EACb,CAAC;AAEH,QAAM,sBACJ,mBAAmB,SAAS,kBAAkB;AAEhD,SACE;AAAA,IAAC,oBAAoB;AAAA,IAApB;AAAA,MACC,WAAW,GAAG,4BAA4B,SAAS;AAAA,MACnD;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA;AAAA,UAAC,oBAAoB;AAAA,UAApB;AAAA,YACC,WAAU;AAAA,YACV,KAAK;AAAA,YAEJ;AAAA,2BACE,aAAa,QAAQ,aAAa,SAAS,aAAa,QACvD,oBAAC,YAAS,WAAU,OAAM,MAAM,aAAa;AAAA,cAEhD,aACE,aAAa,QAAQ,aAAa,UAAU,aAAa,QACxD,oBAAC,YAAS,WAAU,QAAO,MAAM,cAAc;AAAA,cAElD;AAAA,cACA,aACE,aAAa,QAAQ,aAAa,YAAY,aAAa,QAC1D,oBAAC,YAAS,WAAU,UAAS,MAAM,gBAAgB;AAAA,cAEtD,aACE,aAAa,QAAQ,aAAa,WAAW,aAAa,QACzD,oBAAC,YAAS,WAAU,SAAQ,MAAM,eAAe;AAAA;AAAA;AAAA,QAEvD;AAAA,QACA,oBAAC,aAAU,WAAW,sBAAsB,qBAAqB,IAAI;AAAA,QACrE,oBAAC,oBAAoB,QAApB,EAA2B;AAAA;AAAA;AAAA,EAC9B;AAEJ;",
|
|
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)
|
|
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)
|
|
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)
|
|
95
|
+
if (targetIdx === lastSyncedTargetIdx.current) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
92
98
|
lastSyncedTargetIdx.current = targetIdx;
|
|
93
|
-
if (N === 0)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
164
|
+
if (!d.active) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
147
167
|
const dy = e.clientY - d.startY;
|
|
148
|
-
if (Math.abs(dy) > 3)
|
|
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)
|
|
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")
|
|
173
|
-
|
|
174
|
-
else if (e.key === "
|
|
175
|
-
|
|
176
|
-
else if (e.key === "
|
|
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
|
|
210
|
+
} else {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
183
213
|
e.preventDefault();
|
|
184
|
-
if (!loop)
|
|
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))
|
|
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)
|
|
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-
|
|
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)
|
|
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": "
|
|
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-
|
|
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-
|
|
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": "AAwEE,SAWa,KAXb;AApCF,SAAS,OAAO,aAAa,iBAAiB;AAC9C,SAAS,UAAU,uBAAuB;AAI1C,SAAS,UAAU;AAGnB,MAAM,SAAS,gBAAgB;AAG/B,MAAM,cAAc,gBAAgB;AAGpC,MAAM,cAAc,gBAAgB;AAepC,MAAM,gBAAgB,CAAC;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MACE;AAAA,EAAC,gBAAgB;AAAA,EAAhB;AAAA,IACC,WAAW;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,IACC,GAAG;AAAA,IAEH;AAAA;AAAA,MACD,oBAAC,gBAAgB,MAAhB,EAAqB,SAAO,MAC1B,kBAAQ,oBAAC,eAAY,WAAU,WAAU,GAC5C;AAAA;AAAA;AACF;AAWF,MAAM,uBAAuB,CAAC;AAAA,EAC5B;AAAA,EACA;AAAA,EACA,GAAG;AACL,MACE;AAAA,EAAC,gBAAgB;AAAA,EAAhB;AAAA,IACC,WAAW;AAAA,MACT;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,IACC,GAAG;AAAA,IAEJ,8BAAC,aAAU,WAAU,WAAU;AAAA;AACjC;AAWF,MAAM,yBAAyB,CAAC;AAAA,EAC9B;AAAA,EACA;AAAA,EACA,GAAG;AACL,MACE;AAAA,EAAC,gBAAgB;AAAA,EAAhB;AAAA,IACC,WAAW;AAAA,MACT;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,IACC,GAAG;AAAA,IAEJ,8BAAC,eAAY,WAAU,WAAU;AAAA;AACnC;AAWF,MAAM,gBAAgB,CAAC;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MACE,oBAAC,gBAAgB,QAAhB,EACC;AAAA,EAAC,gBAAgB;AAAA,EAAhB;AAAA,IACC,WAAW;AAAA,MACT;AAAA,MACA,aAAa,YACX;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,IACC,GAAG;AAAA,IAEJ;AAAA,0BAAC,wBAAqB;AAAA,MACtB;AAAA,QAAC,gBAAgB;AAAA,QAAhB;AAAA,UACC,WAAW;AAAA,YACT;AAAA,YACA,aAAa,YACX;AAAA,UACJ;AAAA,UAEC;AAAA;AAAA,MACH;AAAA,MACA,oBAAC,0BAAuB;AAAA;AAAA;AAC1B,GACF;AAWF,MAAM,sBAAsB,CAAC;AAAA,EAC3B;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA,GAAG;AACL,MACE,oBAAC,gBAAgB,QAAhB,EACC;AAAA,EAAC,gBAAgB;AAAA,EAAhB;AAAA,IACC,WAAW;AAAA,MACT;AAAA,MACA,aAAa,YACX;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,IACC,GAAG;AAAA,IAEJ;AAAA,0BAAC,wBAAqB;AAAA,MACtB;AAAA,QAAC,gBAAgB;AAAA,QAAhB;AAAA,UACC,WAAW;AAAA,YACT;AAAA,YACA,aAAa,YACX;AAAA,UACJ;AAAA,UAEC;AAAA;AAAA,MACH;AAAA,MACA,oBAAC,0BAAuB;AAAA;AAAA;AAC1B,GACF;AAWF,MAAM,cAAc,CAAC,EAAE,WAAW,KAAK,GAAG,MAAM,MAC9C;AAAA,EAAC,gBAAgB;AAAA,EAAhB;AAAA,IACC,WAAW,GAAG,4CAA4C,SAAS;AAAA,IACnE;AAAA,IACC,GAAG;AAAA;AACN;AAYF,MAAM,aAAa,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MACE;AAAA,EAAC,gBAAgB;AAAA,EAAhB;AAAA,IACC,WAAW;AAAA,MACT;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,IACC,GAAG;AAAA,IAEH;AAAA,kBAAY,OACX,oBAAC,UAAK,WAAU,gEACd,8BAAC,gBAAgB,eAAhB,EACC,8BAAC,SAAM,WAAU,WAAU,GAC7B,GACF;AAAA,MAGF,oBAAC,gBAAgB,UAAhB,EAA0B,UAAS;AAAA;AAAA;AACtC;AAWF,MAAM,kBAAkB,CAAC;AAAA,EACvB;AAAA,EACA;AAAA,EACA,GAAG;AACL,MACE;AAAA,EAAC,gBAAgB;AAAA,EAAhB;AAAA,IACC,WAAW,GAAG,gDAAgD,SAAS;AAAA,IACvE;AAAA,IACC,GAAG;AAAA;AACN;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|