@snowcone-app/ui 0.1.42 → 0.2.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/CHANGELOG.md +33 -0
- package/README.md +18 -4
- package/package.json +9 -5
- package/src/components/CanvasIsolationBoundary.tsx +202 -0
- package/src/components/LoadingOverlayPrism.tsx +251 -0
- package/src/composed/AddToCart.tsx +229 -0
- package/src/composed/ArtAlignment.tsx +703 -0
- package/src/composed/ArtSelector.tsx +290 -0
- package/src/composed/ArtworkCustomizer.tsx +212 -0
- package/src/composed/CanvasEditor.tsx +79 -0
- package/src/composed/ColorPicker.tsx +111 -0
- package/src/composed/CurrentSelectionDisplay.tsx +86 -0
- package/src/composed/HeroProductImage.tsx +1071 -0
- package/src/composed/Lightbox.index.ts +2 -0
- package/src/composed/Lightbox.tsx +230 -0
- package/src/composed/PlacementClipShapeSelector.tsx +88 -0
- package/src/composed/PlacementTabs.tsx +179 -0
- package/src/composed/ProductCard.tsx +298 -0
- package/src/composed/ProductGallery.tsx +54 -0
- package/src/composed/ProductImage.tsx +129 -0
- package/src/composed/ProductList.tsx +147 -0
- package/src/composed/ProductOptions.tsx +305 -0
- package/src/composed/RealtimeMockup.tsx +121 -0
- package/src/composed/TileCount.tsx +348 -0
- package/src/composed/carousels/HeroCarousel.tsx +240 -0
- package/src/composed/carousels/MobileProductCarousel.tsx +1002 -0
- package/src/composed/carousels/index.ts +11 -0
- package/src/composed/carousels/types.ts +58 -0
- package/src/composed/grids/MasonryGrid.tsx +238 -0
- package/src/composed/grids/index.ts +9 -0
- package/src/composed/search/CurrentRefinements.tsx +80 -0
- package/src/composed/search/Filters.tsx +49 -0
- package/src/composed/search/FiltersButton.tsx +57 -0
- package/src/composed/search/FiltersDrawer.tsx +375 -0
- package/src/composed/search/ProductGrid.tsx +118 -0
- package/src/composed/search/ProductHit.tsx +56 -0
- package/src/composed/search/SearchBox.tsx +109 -0
- package/src/composed/search/SearchProvider.tsx +136 -0
- package/src/composed/search/facetConfig.ts +16 -0
- package/src/composed/search/index.ts +22 -0
- package/src/composed/search/meilisearchAdapter.ts +20 -0
- package/src/composed/search/types.ts +22 -0
- package/src/composed/zoom/EnhancedImageViewer.tsx +505 -0
- package/src/composed/zoom/ResponsiveZoom.tsx +134 -0
- package/src/composed/zoom/ZoomOverlay.tsx +194 -0
- package/src/composed/zoom/index.ts +12 -0
- package/src/composed/zoom/types.ts +12 -0
- package/src/design-system/ColorPalette.tsx +126 -0
- package/src/design-system/ColorSwatch.tsx +49 -0
- package/src/design-system/DesignSystemPage.tsx +130 -0
- package/src/design-system/ThemeSwitcher.tsx +181 -0
- package/src/design-system/TypographyScale.tsx +106 -0
- package/src/design-system/index.ts +5 -0
- package/src/ecommerce/stories/HeroProductImage.stories.tsx +66 -0
- package/src/ecommerce/stories/PDPHeroGallery.stories.tsx +105 -0
- package/src/ecommerce/stories/PDPInfoPanel.stories.tsx +472 -0
- package/src/ecommerce/stories/PDPLayout.stories.tsx +365 -0
- package/src/hooks/useBrand.ts +41 -0
- package/src/hooks/useCanvasContext.ts +127 -0
- package/src/hooks/useDeviceDetection.ts +64 -0
- package/src/hooks/useFocusTrap.ts +70 -0
- package/src/hooks/useImagePreloader.ts +268 -0
- package/src/hooks/useImageTransition.ts +608 -0
- package/src/hooks/usePlacementsProcessor.ts +74 -0
- package/src/hooks/useProductGallery.ts +193 -0
- package/src/hooks/useProductPage.ts +467 -0
- package/src/hooks/useRenderGuard.ts +96 -0
- package/src/hooks/useScrollDirection.ts +196 -0
- package/src/hooks/viewport/index.ts +25 -0
- package/src/hooks/viewport/useContainerWidth.ts +59 -0
- package/src/hooks/viewport/useMediaQuery.ts +52 -0
- package/src/hooks/viewport/useResponsiveImageCap.ts +149 -0
- package/src/hooks/viewport/useViewportDimensions.ts +135 -0
- package/src/hooks/viewport/useWideMonitorMode.ts +150 -0
- package/src/hooks/visibility/index.ts +15 -0
- package/src/hooks/visibility/observerPool.ts +150 -0
- package/src/index.ts +240 -0
- package/src/layouts/hero-zoom/HeroShrinkLayout.tsx +209 -0
- package/src/layouts/hero-zoom/HeroZoomLayout.tsx +351 -0
- package/src/layouts/hero-zoom/index.ts +30 -0
- package/src/layouts/hero-zoom/stories/HeroZoomLayout.stories.tsx +350 -0
- package/src/layouts/hero-zoom/types.ts +113 -0
- package/src/layouts/hero-zoom/useHeroZoomScales.ts +156 -0
- package/src/layouts/index.ts +9 -0
- package/src/layouts/pdp/EdgeBlurBox.tsx +210 -0
- package/src/layouts/pdp/ImageBlurExtension.tsx +215 -0
- package/src/layouts/pdp/ImageEdgeBlur.tsx +215 -0
- package/src/layouts/pdp/PDPLayout.tsx +246 -0
- package/src/layouts/pdp/SimpleImageBlur.tsx +140 -0
- package/src/layouts/pdp/index.ts +40 -0
- package/src/lib/env.ts +15 -0
- package/src/lib/locale.ts +167 -0
- package/src/lib/router.tsx +46 -0
- package/src/lib/utils.ts +6 -0
- package/src/lightbox/README.md +77 -0
- package/src/next/index.tsx +26 -0
- package/src/patterns/MockupPriorityProvider.tsx +1014 -0
- package/src/patterns/Product.tsx +850 -0
- package/src/patterns/ProductPageProvider.tsx +224 -0
- package/src/patterns/RealtimeProvider.tsx +1162 -0
- package/src/patterns/ShopProvider.tsx +603 -0
- package/src/personalization/PersonalizationBridge.tsx +235 -0
- package/src/personalization/PersonalizationContext.ts +29 -0
- package/src/personalization/PersonalizationInputs.tsx +110 -0
- package/src/personalization/PersonalizationProvider.tsx +407 -0
- package/src/personalization/canvas-stub.d.ts +22 -0
- package/src/personalization/index.ts +43 -0
- package/src/personalization/types.ts +48 -0
- package/src/personalization/usePersonalization.ts +32 -0
- package/src/personalization/usePersonalizationShimmer.ts +159 -0
- package/src/personalization/utils.ts +59 -0
- package/src/primitives/BrandLogo.tsx +65 -0
- package/src/primitives/BrandName.tsx +51 -0
- package/src/primitives/Button.tsx +123 -0
- package/src/primitives/ColorSwatch.tsx +221 -0
- package/src/primitives/DragHintAnimation.tsx +190 -0
- package/src/primitives/EdgeSwipeGuards.tsx +60 -0
- package/src/primitives/FloatingActionGroup.tsx +176 -0
- package/src/primitives/ProductPrice.tsx +171 -0
- package/src/primitives/ProgressiveBlur.tsx +295 -0
- package/src/primitives/ThemeToggle.tsx +125 -0
- package/src/primitives/__tests__/story-coverage.test.ts +98 -0
- package/src/primitives/accordion.tsx +280 -0
- package/src/primitives/badge.tsx +137 -0
- package/src/primitives/card.tsx +61 -0
- package/src/primitives/checkbox.tsx +56 -0
- package/src/primitives/collapsible.tsx +51 -0
- package/src/primitives/drawer.tsx +828 -0
- package/src/primitives/dropdown-menu.tsx +197 -0
- package/src/primitives/fieldset.tsx +73 -0
- package/src/primitives/index.ts +138 -0
- package/src/primitives/input.tsx +91 -0
- package/src/primitives/kbd.tsx +130 -0
- package/src/primitives/label.tsx +20 -0
- package/src/primitives/link.tsx +182 -0
- package/src/primitives/popover.tsx +80 -0
- package/src/primitives/radio-group.tsx +79 -0
- package/src/primitives/scroll-fade.tsx +159 -0
- package/src/primitives/select.tsx +170 -0
- package/src/primitives/separator.tsx +25 -0
- package/src/primitives/slider.tsx +221 -0
- package/src/primitives/spinner.tsx +72 -0
- package/src/primitives/stories/Accordion.stories.tsx +121 -0
- package/src/primitives/stories/Badge.stories.tsx +221 -0
- package/src/primitives/stories/Button.stories.tsx +185 -0
- package/src/primitives/stories/Card.stories.tsx +171 -0
- package/src/primitives/stories/Checkbox.stories.tsx +214 -0
- package/src/primitives/stories/Collapsible.stories.tsx +230 -0
- package/src/primitives/stories/Drawer.stories.tsx +378 -0
- package/src/primitives/stories/DropdownMenu.stories.tsx +182 -0
- package/src/primitives/stories/Fieldset.stories.tsx +212 -0
- package/src/primitives/stories/Input.stories.tsx +172 -0
- package/src/primitives/stories/Kbd.stories.tsx +183 -0
- package/src/primitives/stories/Label.stories.tsx +98 -0
- package/src/primitives/stories/Link.stories.tsx +260 -0
- package/src/primitives/stories/Popover.stories.tsx +178 -0
- package/src/primitives/stories/RadioGroup.stories.tsx +205 -0
- package/src/primitives/stories/Select.stories.tsx +222 -0
- package/src/primitives/stories/Separator.stories.tsx +134 -0
- package/src/primitives/stories/Slider.stories.tsx +203 -0
- package/src/primitives/stories/Spinner.stories.tsx +142 -0
- package/src/primitives/stories/Surface.stories.tsx +257 -0
- package/src/primitives/stories/Switch.stories.tsx +131 -0
- package/src/primitives/stories/Tabs.stories.tsx +275 -0
- package/src/primitives/stories/TextField.stories.tsx +139 -0
- package/src/primitives/stories/Textarea.stories.tsx +148 -0
- package/src/primitives/stories/Tooltip.stories.tsx +119 -0
- package/src/primitives/surface.tsx +86 -0
- package/src/primitives/switch.tsx +35 -0
- package/src/primitives/tabs.tsx +206 -0
- package/src/primitives/text-field.tsx +84 -0
- package/src/primitives/textarea.tsx +50 -0
- package/src/primitives/tooltip.tsx +58 -0
- package/src/services/CanvasExportService.ts +518 -0
- package/src/styles/base.css +380 -0
- package/src/styles/defaults.css +280 -0
- package/src/styles/globals.css +1242 -0
- package/src/styles/index.css +17 -0
- package/src/styles/ne-themes.css +4740 -0
- package/src/styles/tailwind.css +11 -0
- package/src/styles/tokens.css +117 -0
- package/src/styles/utilities.css +188 -0
- package/src/themes/apply-theme.ts +449 -0
- package/src/themes/getThemeStyles.ts +454 -0
- package/src/themes/index.ts +48 -0
- package/src/themes/oklch-theme.ts +283 -0
- package/src/themes/presets.ts +989 -0
- package/src/themes/types.ts +386 -0
- package/src/themes/useTheme.tsx +450 -0
- package/src/utils/dev-warnings.ts +161 -0
- package/src/utils/devWarnings.ts +153 -0
- package/dist/styles.css +0 -1
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
5
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
6
|
+
import { cn } from '../lib/utils';
|
|
7
|
+
|
|
8
|
+
const linkVariants = cva(
|
|
9
|
+
[
|
|
10
|
+
'inline-flex items-center gap-1 font-medium transition-colors',
|
|
11
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus focus-visible:ring-offset-2',
|
|
12
|
+
'disabled:pointer-events-none disabled:opacity-50',
|
|
13
|
+
'aria-disabled:pointer-events-none aria-disabled:opacity-50',
|
|
14
|
+
],
|
|
15
|
+
{
|
|
16
|
+
variants: {
|
|
17
|
+
color: {
|
|
18
|
+
default: 'text-foreground hover:text-foreground/80',
|
|
19
|
+
primary: 'text-primary hover:text-primary/80',
|
|
20
|
+
secondary: 'text-secondary hover:text-secondary/80',
|
|
21
|
+
success: 'text-success hover:text-success/80',
|
|
22
|
+
warning: 'text-warning hover:text-warning/80',
|
|
23
|
+
danger: 'text-danger hover:text-danger/80',
|
|
24
|
+
},
|
|
25
|
+
size: {
|
|
26
|
+
sm: 'text-sm',
|
|
27
|
+
md: 'text-base',
|
|
28
|
+
lg: 'text-lg',
|
|
29
|
+
},
|
|
30
|
+
underline: {
|
|
31
|
+
none: '',
|
|
32
|
+
hover: [
|
|
33
|
+
'relative',
|
|
34
|
+
'after:absolute after:bottom-0 after:left-0 after:h-[1px] after:w-full',
|
|
35
|
+
'after:origin-bottom-right after:scale-x-0',
|
|
36
|
+
'after:bg-current after:transition-transform after:duration-200',
|
|
37
|
+
'hover:after:origin-bottom-left hover:after:scale-x-100',
|
|
38
|
+
],
|
|
39
|
+
always: [
|
|
40
|
+
'relative',
|
|
41
|
+
'after:absolute after:bottom-0 after:left-0 after:h-[1px] after:w-full',
|
|
42
|
+
'after:bg-current after:opacity-50',
|
|
43
|
+
'hover:after:opacity-100',
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
underlineOffset: {
|
|
47
|
+
1: 'after:bottom-0',
|
|
48
|
+
2: 'after:-bottom-0.5',
|
|
49
|
+
3: 'after:-bottom-1',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
defaultVariants: {
|
|
53
|
+
color: 'primary',
|
|
54
|
+
size: 'md',
|
|
55
|
+
underline: 'hover',
|
|
56
|
+
underlineOffset: 1,
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
export interface LinkProps
|
|
62
|
+
extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'color'>,
|
|
63
|
+
VariantProps<typeof linkVariants> {
|
|
64
|
+
/**
|
|
65
|
+
* If true, the link will be rendered as its child element (using Radix Slot).
|
|
66
|
+
* Useful for integrating with routing libraries like Next.js Link.
|
|
67
|
+
*/
|
|
68
|
+
asChild?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* If true, adds an external link icon.
|
|
71
|
+
*/
|
|
72
|
+
isExternal?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* If true, shows a block-style link (full width with padding).
|
|
75
|
+
*/
|
|
76
|
+
isBlock?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Link - A styled anchor component for navigation.
|
|
81
|
+
*
|
|
82
|
+
* Supports animated underlines, external link indicators, and integration
|
|
83
|
+
* with routing libraries via the asChild prop.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```tsx
|
|
87
|
+
* <Link href="/about">About</Link>
|
|
88
|
+
* <Link href="https://example.com" isExternal>External Link</Link>
|
|
89
|
+
* <Link asChild underline="hover">
|
|
90
|
+
* <NextLink href="/dashboard">Dashboard</NextLink>
|
|
91
|
+
* </Link>
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
|
|
95
|
+
(
|
|
96
|
+
{
|
|
97
|
+
className,
|
|
98
|
+
color,
|
|
99
|
+
size,
|
|
100
|
+
underline,
|
|
101
|
+
underlineOffset,
|
|
102
|
+
asChild = false,
|
|
103
|
+
isExternal = false,
|
|
104
|
+
isBlock = false,
|
|
105
|
+
children,
|
|
106
|
+
target,
|
|
107
|
+
rel,
|
|
108
|
+
...props
|
|
109
|
+
},
|
|
110
|
+
ref
|
|
111
|
+
) => {
|
|
112
|
+
const Comp = asChild ? Slot : 'a';
|
|
113
|
+
|
|
114
|
+
// Handle external links
|
|
115
|
+
const externalProps = isExternal
|
|
116
|
+
? {
|
|
117
|
+
target: target ?? '_blank',
|
|
118
|
+
rel: rel ?? 'noopener noreferrer',
|
|
119
|
+
}
|
|
120
|
+
: { target, rel };
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<Comp
|
|
124
|
+
ref={ref}
|
|
125
|
+
className={cn(
|
|
126
|
+
linkVariants({ color, size, underline, underlineOffset }),
|
|
127
|
+
isBlock && 'w-full justify-start rounded-md px-3 py-2 hover:bg-muted',
|
|
128
|
+
className
|
|
129
|
+
)}
|
|
130
|
+
{...externalProps}
|
|
131
|
+
{...props}
|
|
132
|
+
>
|
|
133
|
+
{children}
|
|
134
|
+
{isExternal && <LinkIcon />}
|
|
135
|
+
</Comp>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
Link.displayName = 'Link';
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* LinkIcon - External link indicator icon.
|
|
143
|
+
*
|
|
144
|
+
* Used automatically when isExternal is true, or can be used manually.
|
|
145
|
+
*/
|
|
146
|
+
interface LinkIconProps extends React.SVGAttributes<SVGElement> {
|
|
147
|
+
/**
|
|
148
|
+
* If true, renders as child element using Slot.
|
|
149
|
+
*/
|
|
150
|
+
asChild?: boolean;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const LinkIcon = React.forwardRef<SVGSVGElement, LinkIconProps>(
|
|
154
|
+
({ className, asChild = false, children, ...props }, ref) => {
|
|
155
|
+
if (asChild && children) {
|
|
156
|
+
return <Slot className={cn('size-3.5 shrink-0', className)}>{children}</Slot>;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<svg
|
|
161
|
+
ref={ref}
|
|
162
|
+
className={cn('size-3.5 shrink-0', className)}
|
|
163
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
164
|
+
viewBox="0 0 24 24"
|
|
165
|
+
fill="none"
|
|
166
|
+
stroke="currentColor"
|
|
167
|
+
strokeWidth="2"
|
|
168
|
+
strokeLinecap="round"
|
|
169
|
+
strokeLinejoin="round"
|
|
170
|
+
aria-hidden="true"
|
|
171
|
+
{...props}
|
|
172
|
+
>
|
|
173
|
+
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
|
174
|
+
<polyline points="15 3 21 3 21 9" />
|
|
175
|
+
<line x1="10" y1="14" x2="21" y2="3" />
|
|
176
|
+
</svg>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
LinkIcon.displayName = 'LinkIcon';
|
|
181
|
+
|
|
182
|
+
export { Link, LinkIcon, linkVariants };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
|
5
|
+
import { cn } from '../lib/utils';
|
|
6
|
+
import { SurfaceContext } from './surface';
|
|
7
|
+
|
|
8
|
+
const PopoverRoot = PopoverPrimitive.Root;
|
|
9
|
+
|
|
10
|
+
const PopoverTrigger = PopoverPrimitive.Trigger;
|
|
11
|
+
|
|
12
|
+
const PopoverAnchor = PopoverPrimitive.Anchor;
|
|
13
|
+
|
|
14
|
+
interface PopoverContentProps extends React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> {
|
|
15
|
+
/** Preserve text selection when popover opens (useful for canvas/editor contexts) */
|
|
16
|
+
preserveSelection?: boolean;
|
|
17
|
+
/** Use maximum z-index to appear above fullscreen modals (for canvas/editor contexts) */
|
|
18
|
+
maxZIndex?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const PopoverContent = React.forwardRef<
|
|
22
|
+
React.ElementRef<typeof PopoverPrimitive.Content>,
|
|
23
|
+
PopoverContentProps
|
|
24
|
+
>(({ className, align = 'center', sideOffset = 4, preserveSelection, maxZIndex, children, style, ...props }, ref) => (
|
|
25
|
+
<PopoverPrimitive.Portal>
|
|
26
|
+
<SurfaceContext.Provider value={{ variant: 'default' }}>
|
|
27
|
+
<PopoverPrimitive.Content
|
|
28
|
+
ref={ref}
|
|
29
|
+
align={align}
|
|
30
|
+
sideOffset={sideOffset}
|
|
31
|
+
data-preserve-selection={preserveSelection ? '' : undefined}
|
|
32
|
+
className={cn(
|
|
33
|
+
'z-dropdown w-72 overflow-hidden rounded-tooltip border-none bg-popover p-4 text-popover-foreground shadow-soft outline-none',
|
|
34
|
+
'data-[state=open]:animate-in data-[state=closed]:animate-out',
|
|
35
|
+
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
|
36
|
+
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
|
37
|
+
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',
|
|
38
|
+
'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
style={maxZIndex ? { ...style, zIndex: 2147483647 } : style}
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
{children}
|
|
45
|
+
</PopoverPrimitive.Content>
|
|
46
|
+
</SurfaceContext.Provider>
|
|
47
|
+
</PopoverPrimitive.Portal>
|
|
48
|
+
));
|
|
49
|
+
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
|
50
|
+
|
|
51
|
+
interface PopoverProps {
|
|
52
|
+
children: React.ReactNode;
|
|
53
|
+
open?: boolean;
|
|
54
|
+
onOpenChange?: (open: boolean) => void;
|
|
55
|
+
defaultOpen?: boolean;
|
|
56
|
+
modal?: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const PopoverBase = ({ children, open, onOpenChange, ...props }: PopoverProps) => (
|
|
60
|
+
<PopoverRoot open={open} onOpenChange={onOpenChange} {...props}>
|
|
61
|
+
{children}
|
|
62
|
+
</PopoverRoot>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Compound component with subcomponents
|
|
66
|
+
const Popover = Object.assign(PopoverBase, {
|
|
67
|
+
Trigger: PopoverTrigger,
|
|
68
|
+
Content: PopoverContent,
|
|
69
|
+
Anchor: PopoverAnchor,
|
|
70
|
+
// HeroUI uses Dialog for the content wrapper - map to Content
|
|
71
|
+
Dialog: React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
72
|
+
({ children, className, ...props }, ref) => (
|
|
73
|
+
<div ref={ref} className={className} {...props}>
|
|
74
|
+
{children}
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor, PopoverRoot };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
|
|
5
|
+
import { cn } from '../lib/utils';
|
|
6
|
+
import { useSurface } from './surface';
|
|
7
|
+
|
|
8
|
+
const RadioGroup = React.forwardRef<
|
|
9
|
+
React.ElementRef<typeof RadioGroupPrimitive.Root>,
|
|
10
|
+
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
|
|
11
|
+
>(({ className, ...props }, ref) => (
|
|
12
|
+
<RadioGroupPrimitive.Root className={cn('grid gap-2', className)} {...props} ref={ref} />
|
|
13
|
+
));
|
|
14
|
+
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
|
|
15
|
+
|
|
16
|
+
interface RadioGroupItemProps extends React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item> {
|
|
17
|
+
/** Visual variant - auto-detected from Surface context, or can be set explicitly */
|
|
18
|
+
variant?: 'default' | 'filled';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const RadioGroupItem = React.forwardRef<
|
|
22
|
+
React.ElementRef<typeof RadioGroupPrimitive.Item>,
|
|
23
|
+
RadioGroupItemProps
|
|
24
|
+
>(({ className, variant, ...props }, ref) => {
|
|
25
|
+
// Auto-detect variant from Surface context
|
|
26
|
+
const surface = useSurface();
|
|
27
|
+
const effectiveVariant = variant ?? (surface.variant === 'default' ? 'filled' : 'default');
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<RadioGroupPrimitive.Item
|
|
31
|
+
ref={ref}
|
|
32
|
+
className={cn(
|
|
33
|
+
'aspect-square size-4 rounded-full border-none transition-colors',
|
|
34
|
+
'focus-visible:outline-none',
|
|
35
|
+
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
36
|
+
// Variant styles - semantic: field on page vs field on surface
|
|
37
|
+
effectiveVariant === 'default' && 'bg-field shadow-soft dark:shadow-none',
|
|
38
|
+
effectiveVariant === 'filled' && 'bg-[var(--color-field-on-surface)]',
|
|
39
|
+
'data-[state=checked]:bg-primary data-[state=checked]:shadow-none',
|
|
40
|
+
className
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
|
|
45
|
+
<div className="size-1.5 rounded-full bg-primary-foreground" />
|
|
46
|
+
</RadioGroupPrimitive.Indicator>
|
|
47
|
+
</RadioGroupPrimitive.Item>
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
|
|
51
|
+
|
|
52
|
+
// Radio component - wraps RadioGroupItem with label pattern
|
|
53
|
+
interface RadioProps extends React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item> {
|
|
54
|
+
children?: React.ReactNode;
|
|
55
|
+
/** Visual variant - auto-detected from Surface context, or can be set explicitly */
|
|
56
|
+
variant?: 'default' | 'filled';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const Radio = React.forwardRef<React.ElementRef<typeof RadioGroupPrimitive.Item>, RadioProps>(
|
|
60
|
+
({ children, className, variant, ...props }, ref) => {
|
|
61
|
+
if (children) {
|
|
62
|
+
return (
|
|
63
|
+
<div className="flex items-center space-x-2">
|
|
64
|
+
<RadioGroupItem ref={ref} variant={variant} {...props} />
|
|
65
|
+
<label
|
|
66
|
+
htmlFor={props.id}
|
|
67
|
+
className="text-sm leading-none text-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-70 font-label"
|
|
68
|
+
>
|
|
69
|
+
{children}
|
|
70
|
+
</label>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return <RadioGroupItem ref={ref} className={className} variant={variant} {...props} />;
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
Radio.displayName = 'Radio';
|
|
78
|
+
|
|
79
|
+
export { RadioGroup, RadioGroupItem, Radio };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ScrollFade Component
|
|
8
|
+
*
|
|
9
|
+
* A container that shows a gradient fade at the bottom when content is scrollable,
|
|
10
|
+
* indicating there's more content below. The gradient fades out when the user
|
|
11
|
+
* scrolls to the bottom.
|
|
12
|
+
*
|
|
13
|
+
* @example Basic usage
|
|
14
|
+
* <ScrollFade className="h-[400px]">
|
|
15
|
+
* <div className="space-y-4">
|
|
16
|
+
* {items.map(item => <Card key={item.id}>{item.content}</Card>)}
|
|
17
|
+
* </div>
|
|
18
|
+
* </ScrollFade>
|
|
19
|
+
*
|
|
20
|
+
* @example With custom fade color
|
|
21
|
+
* <ScrollFade fadeColor="rgb(0, 0, 0)" className="h-full bg-black">
|
|
22
|
+
* <DarkModeContent />
|
|
23
|
+
* </ScrollFade>
|
|
24
|
+
*
|
|
25
|
+
* @example Disabled fade
|
|
26
|
+
* <ScrollFade showFade={false}>
|
|
27
|
+
* <Content />
|
|
28
|
+
* </ScrollFade>
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
interface ScrollFadeProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
32
|
+
/** Whether to show the gradient fade. Default: true */
|
|
33
|
+
showFade?: boolean;
|
|
34
|
+
/** Height of the gradient fade in pixels or CSS value. Default: 80 */
|
|
35
|
+
fadeHeight?: number | string;
|
|
36
|
+
/** Color for the gradient fade. Should match the container background. Default: uses CSS variable */
|
|
37
|
+
fadeColor?: string;
|
|
38
|
+
/** Additional class for the scroll container */
|
|
39
|
+
scrollClassName?: string;
|
|
40
|
+
/** Threshold in pixels from bottom to consider "at bottom". Default: 20 */
|
|
41
|
+
bottomThreshold?: number;
|
|
42
|
+
/** Callback when scroll position changes */
|
|
43
|
+
onScrollChange?: (info: { isAtBottom: boolean; isAtTop: boolean; scrollTop: number }) => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const ScrollFade = React.forwardRef<HTMLDivElement, ScrollFadeProps>(
|
|
47
|
+
(
|
|
48
|
+
{
|
|
49
|
+
className,
|
|
50
|
+
children,
|
|
51
|
+
showFade = true,
|
|
52
|
+
fadeHeight = 80,
|
|
53
|
+
fadeColor,
|
|
54
|
+
scrollClassName,
|
|
55
|
+
bottomThreshold = 20,
|
|
56
|
+
onScrollChange,
|
|
57
|
+
...props
|
|
58
|
+
},
|
|
59
|
+
ref
|
|
60
|
+
) => {
|
|
61
|
+
const [isAtBottom, setIsAtBottom] = React.useState(false);
|
|
62
|
+
const [isAtTop, setIsAtTop] = React.useState(true);
|
|
63
|
+
// Default to true - will be corrected after layout check
|
|
64
|
+
const [hasOverflow, setHasOverflow] = React.useState(true);
|
|
65
|
+
const scrollRef = React.useRef<HTMLDivElement>(null);
|
|
66
|
+
|
|
67
|
+
// Check if content overflows on mount and resize
|
|
68
|
+
React.useEffect(() => {
|
|
69
|
+
const checkOverflow = () => {
|
|
70
|
+
if (scrollRef.current) {
|
|
71
|
+
const hasScroll = scrollRef.current.scrollHeight > scrollRef.current.clientHeight;
|
|
72
|
+
setHasOverflow(hasScroll);
|
|
73
|
+
// If no overflow, we're at bottom
|
|
74
|
+
if (!hasScroll) {
|
|
75
|
+
setIsAtBottom(true);
|
|
76
|
+
} else {
|
|
77
|
+
// Check initial scroll position
|
|
78
|
+
const atBottom = scrollRef.current.scrollHeight - scrollRef.current.scrollTop - scrollRef.current.clientHeight < bottomThreshold;
|
|
79
|
+
setIsAtBottom(atBottom);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Initial check after a short delay to ensure layout is complete
|
|
85
|
+
checkOverflow();
|
|
86
|
+
const timeoutId = setTimeout(checkOverflow, 100);
|
|
87
|
+
|
|
88
|
+
// Re-check on resize
|
|
89
|
+
const resizeObserver = new ResizeObserver(checkOverflow);
|
|
90
|
+
if (scrollRef.current) {
|
|
91
|
+
resizeObserver.observe(scrollRef.current);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return () => {
|
|
95
|
+
clearTimeout(timeoutId);
|
|
96
|
+
resizeObserver.disconnect();
|
|
97
|
+
};
|
|
98
|
+
}, [children, bottomThreshold]);
|
|
99
|
+
|
|
100
|
+
const handleScroll = React.useCallback(
|
|
101
|
+
(e: React.UIEvent<HTMLDivElement>) => {
|
|
102
|
+
const target = e.target as HTMLDivElement;
|
|
103
|
+
const scrollTop = target.scrollTop;
|
|
104
|
+
const atBottom =
|
|
105
|
+
target.scrollHeight - scrollTop - target.clientHeight < bottomThreshold;
|
|
106
|
+
const atTop = scrollTop < bottomThreshold;
|
|
107
|
+
|
|
108
|
+
setIsAtBottom(atBottom);
|
|
109
|
+
setIsAtTop(atTop);
|
|
110
|
+
onScrollChange?.({ isAtBottom: atBottom, isAtTop: atTop, scrollTop });
|
|
111
|
+
},
|
|
112
|
+
[bottomThreshold, onScrollChange]
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const fadeHeightValue = typeof fadeHeight === "number" ? `${fadeHeight}px` : fadeHeight;
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div
|
|
119
|
+
ref={ref}
|
|
120
|
+
className={cn("relative overflow-hidden min-h-0", className)}
|
|
121
|
+
{...props}
|
|
122
|
+
>
|
|
123
|
+
<div
|
|
124
|
+
ref={scrollRef}
|
|
125
|
+
className={cn("absolute inset-0 overflow-y-auto", scrollClassName)}
|
|
126
|
+
style={{
|
|
127
|
+
WebkitOverflowScrolling: "touch",
|
|
128
|
+
overscrollBehavior: "contain",
|
|
129
|
+
}}
|
|
130
|
+
onScroll={handleScroll}
|
|
131
|
+
>
|
|
132
|
+
{children}
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
{/* Gradient fade - hides when scrolled to bottom or no overflow */}
|
|
136
|
+
{showFade && (
|
|
137
|
+
<div
|
|
138
|
+
className={cn(
|
|
139
|
+
"absolute bottom-0 left-0 right-0 pointer-events-none transition-opacity duration-300 z-10",
|
|
140
|
+
isAtBottom || !hasOverflow ? "opacity-0" : "opacity-100",
|
|
141
|
+
!fadeColor && "bg-gradient-to-t from-background to-transparent"
|
|
142
|
+
)}
|
|
143
|
+
style={{
|
|
144
|
+
height: fadeHeightValue,
|
|
145
|
+
...(fadeColor && {
|
|
146
|
+
background: `linear-gradient(to top, ${fadeColor}, transparent)`,
|
|
147
|
+
}),
|
|
148
|
+
}}
|
|
149
|
+
aria-hidden="true"
|
|
150
|
+
/>
|
|
151
|
+
)}
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
ScrollFade.displayName = "ScrollFade";
|
|
157
|
+
|
|
158
|
+
export { ScrollFade };
|
|
159
|
+
export type { ScrollFadeProps };
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import * as SelectPrimitive from '@radix-ui/react-select';
|
|
5
|
+
import { Icon } from '@iconify/react';
|
|
6
|
+
import { cn } from '../lib/utils';
|
|
7
|
+
import { useSurface } from './surface';
|
|
8
|
+
|
|
9
|
+
const Select = SelectPrimitive.Root;
|
|
10
|
+
|
|
11
|
+
const SelectGroup = SelectPrimitive.Group;
|
|
12
|
+
|
|
13
|
+
const SelectValue = SelectPrimitive.Value;
|
|
14
|
+
|
|
15
|
+
interface SelectTriggerProps extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> {
|
|
16
|
+
/** Visual variant - auto-detected from Surface context, or can be set explicitly */
|
|
17
|
+
variant?: 'default' | 'filled';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const SelectTrigger = React.forwardRef<React.ElementRef<typeof SelectPrimitive.Trigger>, SelectTriggerProps>(
|
|
21
|
+
({ className, children, variant, ...props }, ref) => {
|
|
22
|
+
// Auto-detect variant from Surface context
|
|
23
|
+
const surface = useSurface();
|
|
24
|
+
const effectiveVariant = variant ?? (surface.variant === 'default' ? 'filled' : 'default');
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<SelectPrimitive.Trigger
|
|
28
|
+
ref={ref}
|
|
29
|
+
className={cn(
|
|
30
|
+
'flex h-9 w-full items-center justify-between gap-2 rounded-input px-2.5 py-1.5 text-sm transition-colors',
|
|
31
|
+
'text-foreground border-none',
|
|
32
|
+
'focus:ring-focus focus:ring-2 focus:outline-none',
|
|
33
|
+
'disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
|
|
34
|
+
// Placeholder styling (Radix adds data-placeholder attribute to trigger)
|
|
35
|
+
'data-[placeholder]:text-foreground/50',
|
|
36
|
+
// Variant styles - semantic: field on page vs field on surface
|
|
37
|
+
effectiveVariant === 'default' && 'bg-field shadow-soft dark:shadow-none',
|
|
38
|
+
effectiveVariant === 'filled' && 'bg-[var(--color-field-on-surface)]',
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
<SelectPrimitive.Icon asChild>
|
|
45
|
+
<Icon
|
|
46
|
+
icon="gravity-ui:chevron-down"
|
|
47
|
+
className="text-foreground/40 size-4 transition-transform duration-200 ease-out [[data-state=open]>&]:rotate-180"
|
|
48
|
+
/>
|
|
49
|
+
</SelectPrimitive.Icon>
|
|
50
|
+
</SelectPrimitive.Trigger>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
|
55
|
+
|
|
56
|
+
const SelectScrollUpButton = React.forwardRef<
|
|
57
|
+
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
|
58
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
|
59
|
+
>(({ className, ...props }, ref) => (
|
|
60
|
+
<SelectPrimitive.ScrollUpButton
|
|
61
|
+
ref={ref}
|
|
62
|
+
className={cn('flex cursor-default items-center justify-center py-1', className)}
|
|
63
|
+
{...props}
|
|
64
|
+
>
|
|
65
|
+
<Icon icon="gravity-ui:chevron-up" className="size-4" />
|
|
66
|
+
</SelectPrimitive.ScrollUpButton>
|
|
67
|
+
));
|
|
68
|
+
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
|
|
69
|
+
|
|
70
|
+
const SelectScrollDownButton = React.forwardRef<
|
|
71
|
+
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
|
72
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
|
73
|
+
>(({ className, ...props }, ref) => (
|
|
74
|
+
<SelectPrimitive.ScrollDownButton
|
|
75
|
+
ref={ref}
|
|
76
|
+
className={cn('flex cursor-default items-center justify-center py-1', className)}
|
|
77
|
+
{...props}
|
|
78
|
+
>
|
|
79
|
+
<Icon icon="gravity-ui:chevron-down" className="size-4" />
|
|
80
|
+
</SelectPrimitive.ScrollDownButton>
|
|
81
|
+
));
|
|
82
|
+
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
|
|
83
|
+
|
|
84
|
+
const SelectContent = React.forwardRef<
|
|
85
|
+
React.ElementRef<typeof SelectPrimitive.Content>,
|
|
86
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
|
87
|
+
>(({ className, children, position = 'popper', ...props }, ref) => (
|
|
88
|
+
<SelectPrimitive.Portal>
|
|
89
|
+
<SelectPrimitive.Content
|
|
90
|
+
ref={ref}
|
|
91
|
+
className={cn(
|
|
92
|
+
'z-dropdown bg-popover text-popover-foreground shadow-soft relative min-w-[8rem] overflow-hidden rounded-tooltip border-none p-1',
|
|
93
|
+
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
|
94
|
+
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
|
95
|
+
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',
|
|
96
|
+
'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
97
|
+
position === 'popper' &&
|
|
98
|
+
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
|
99
|
+
className
|
|
100
|
+
)}
|
|
101
|
+
position={position}
|
|
102
|
+
{...props}
|
|
103
|
+
>
|
|
104
|
+
<SelectScrollUpButton />
|
|
105
|
+
<SelectPrimitive.Viewport
|
|
106
|
+
className={cn(position === 'popper' && 'w-full min-w-[var(--radix-select-trigger-width)]')}
|
|
107
|
+
>
|
|
108
|
+
{children}
|
|
109
|
+
</SelectPrimitive.Viewport>
|
|
110
|
+
<SelectScrollDownButton />
|
|
111
|
+
</SelectPrimitive.Content>
|
|
112
|
+
</SelectPrimitive.Portal>
|
|
113
|
+
));
|
|
114
|
+
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
|
115
|
+
|
|
116
|
+
const SelectLabel = React.forwardRef<
|
|
117
|
+
React.ElementRef<typeof SelectPrimitive.Label>,
|
|
118
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
|
119
|
+
>(({ className, ...props }, ref) => (
|
|
120
|
+
<SelectPrimitive.Label
|
|
121
|
+
ref={ref}
|
|
122
|
+
className={cn('text-muted-foreground px-2 py-1.5 text-xs font-label', className)}
|
|
123
|
+
{...props}
|
|
124
|
+
/>
|
|
125
|
+
));
|
|
126
|
+
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
|
127
|
+
|
|
128
|
+
const SelectItem = React.forwardRef<
|
|
129
|
+
React.ElementRef<typeof SelectPrimitive.Item>,
|
|
130
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
|
131
|
+
>(({ className, children, ...props }, ref) => (
|
|
132
|
+
<SelectPrimitive.Item
|
|
133
|
+
ref={ref}
|
|
134
|
+
className={cn(
|
|
135
|
+
'relative flex w-full cursor-default items-center rounded-full py-1 pr-8 pl-2 text-sm text-foreground outline-none select-none font-body',
|
|
136
|
+
'hover:bg-default/50 focus:bg-default/50 dark:hover:bg-white/5 dark:focus:bg-white/5 data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
137
|
+
className
|
|
138
|
+
)}
|
|
139
|
+
{...props}
|
|
140
|
+
>
|
|
141
|
+
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
142
|
+
<SelectPrimitive.ItemIndicator>
|
|
143
|
+
<Icon icon="gravity-ui:check" className="size-4" />
|
|
144
|
+
</SelectPrimitive.ItemIndicator>
|
|
145
|
+
</span>
|
|
146
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
147
|
+
</SelectPrimitive.Item>
|
|
148
|
+
));
|
|
149
|
+
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
|
150
|
+
|
|
151
|
+
const SelectSeparator = React.forwardRef<
|
|
152
|
+
React.ElementRef<typeof SelectPrimitive.Separator>,
|
|
153
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
|
154
|
+
>(({ className, ...props }, ref) => (
|
|
155
|
+
<SelectPrimitive.Separator ref={ref} className={cn('bg-divider mx-2 my-1 h-px', className)} {...props} />
|
|
156
|
+
));
|
|
157
|
+
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
|
158
|
+
|
|
159
|
+
export {
|
|
160
|
+
Select,
|
|
161
|
+
SelectGroup,
|
|
162
|
+
SelectValue,
|
|
163
|
+
SelectTrigger,
|
|
164
|
+
SelectContent,
|
|
165
|
+
SelectLabel,
|
|
166
|
+
SelectItem,
|
|
167
|
+
SelectSeparator,
|
|
168
|
+
SelectScrollUpButton,
|
|
169
|
+
SelectScrollDownButton,
|
|
170
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
|
5
|
+
import { cn } from '../lib/utils';
|
|
6
|
+
|
|
7
|
+
const Separator = React.forwardRef<
|
|
8
|
+
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
|
9
|
+
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
|
10
|
+
>(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (
|
|
11
|
+
<SeparatorPrimitive.Root
|
|
12
|
+
ref={ref}
|
|
13
|
+
decorative={decorative}
|
|
14
|
+
orientation={orientation}
|
|
15
|
+
className={cn(
|
|
16
|
+
'shrink-0 bg-divider',
|
|
17
|
+
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
));
|
|
23
|
+
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
|
24
|
+
|
|
25
|
+
export { Separator };
|