@moontra/moonui-pro 2.0.22 → 2.1.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/dist/index.mjs +215 -214
- package/package.json +4 -2
- package/src/__tests__/use-intersection-observer.test.tsx +216 -0
- package/src/__tests__/use-local-storage.test.tsx +174 -0
- package/src/__tests__/use-pro-access.test.tsx +183 -0
- package/src/components/advanced-chart/advanced-chart.test.tsx +281 -0
- package/src/components/advanced-chart/index.tsx +412 -0
- package/src/components/advanced-forms/index.tsx +431 -0
- package/src/components/animated-button/index.tsx +202 -0
- package/src/components/calendar/event-dialog.tsx +372 -0
- package/src/components/calendar/index.tsx +557 -0
- package/src/components/color-picker/index.tsx +434 -0
- package/src/components/dashboard/index.tsx +334 -0
- package/src/components/data-table/data-table.test.tsx +187 -0
- package/src/components/data-table/index.tsx +368 -0
- package/src/components/draggable-list/index.tsx +100 -0
- package/src/components/enhanced/button.tsx +360 -0
- package/src/components/enhanced/card.tsx +272 -0
- package/src/components/enhanced/dialog.tsx +248 -0
- package/src/components/enhanced/index.ts +3 -0
- package/src/components/error-boundary/index.tsx +111 -0
- package/src/components/file-upload/file-upload.test.tsx +242 -0
- package/src/components/file-upload/index.tsx +362 -0
- package/src/components/floating-action-button/index.tsx +209 -0
- package/src/components/github-stars/index.tsx +414 -0
- package/src/components/health-check/index.tsx +441 -0
- package/src/components/hover-card-3d/index.tsx +170 -0
- package/src/components/index.ts +76 -0
- package/src/components/kanban/index.tsx +436 -0
- package/src/components/lazy-component/index.tsx +342 -0
- package/src/components/magnetic-button/index.tsx +170 -0
- package/src/components/memory-efficient-data/index.tsx +352 -0
- package/src/components/optimized-image/index.tsx +427 -0
- package/src/components/performance-debugger/index.tsx +591 -0
- package/src/components/performance-monitor/index.tsx +775 -0
- package/src/components/pinch-zoom/index.tsx +172 -0
- package/src/components/rich-text-editor/index-old-backup.tsx +443 -0
- package/src/components/rich-text-editor/index.tsx +1537 -0
- package/src/components/rich-text-editor/slash-commands-extension.ts +220 -0
- package/src/components/rich-text-editor/slash-commands.css +35 -0
- package/src/components/rich-text-editor/table-styles.css +65 -0
- package/src/components/spotlight-card/index.tsx +194 -0
- package/src/components/swipeable-card/index.tsx +100 -0
- package/src/components/timeline/index.tsx +333 -0
- package/src/components/ui/animated-button.tsx +185 -0
- package/src/components/ui/avatar.tsx +135 -0
- package/src/components/ui/badge.tsx +225 -0
- package/src/components/ui/button.tsx +221 -0
- package/src/components/ui/card.tsx +141 -0
- package/src/components/ui/checkbox.tsx +256 -0
- package/src/components/ui/color-picker.tsx +95 -0
- package/src/components/ui/dialog.tsx +332 -0
- package/src/components/ui/dropdown-menu.tsx +200 -0
- package/src/components/ui/hover-card-3d.tsx +103 -0
- package/src/components/ui/index.ts +33 -0
- package/src/components/ui/input.tsx +219 -0
- package/src/components/ui/label.tsx +26 -0
- package/src/components/ui/magnetic-button.tsx +129 -0
- package/src/components/ui/popover.tsx +183 -0
- package/src/components/ui/select.tsx +273 -0
- package/src/components/ui/separator.tsx +140 -0
- package/src/components/ui/slider.tsx +351 -0
- package/src/components/ui/spotlight-card.tsx +119 -0
- package/src/components/ui/switch.tsx +83 -0
- package/src/components/ui/tabs.tsx +195 -0
- package/src/components/ui/textarea.tsx +25 -0
- package/src/components/ui/toast.tsx +313 -0
- package/src/components/ui/tooltip.tsx +152 -0
- package/src/components/virtual-list/index.tsx +369 -0
- package/src/hooks/use-chart.ts +205 -0
- package/src/hooks/use-data-table.ts +182 -0
- package/src/hooks/use-docs-pro-access.ts +13 -0
- package/src/hooks/use-license-check.ts +65 -0
- package/src/hooks/use-subscription.ts +19 -0
- package/src/index.ts +14 -0
- package/src/lib/micro-interactions.ts +255 -0
- package/src/lib/utils.ts +6 -0
- package/src/patterns/login-form/index.tsx +276 -0
- package/src/patterns/login-form/types.ts +67 -0
- package/src/setupTests.ts +41 -0
- package/src/styles/design-system.css +365 -0
- package/src/styles/index.css +4 -0
- package/src/styles/tailwind.css +6 -0
- package/src/styles/tokens.css +453 -0
- package/src/types/moonui.d.ts +22 -0
- package/src/use-intersection-observer.tsx +154 -0
- package/src/use-local-storage.tsx +71 -0
- package/src/use-paddle.ts +138 -0
- package/src/use-performance-optimizer.ts +379 -0
- package/src/use-pro-access.ts +141 -0
- package/src/use-scroll-animation.ts +221 -0
- package/src/use-subscription.ts +37 -0
- package/src/use-toast.ts +32 -0
- package/src/utils/chart-helpers.ts +257 -0
- package/src/utils/cn.ts +69 -0
- package/src/utils/data-processing.ts +151 -0
- package/src/utils/license-guard.tsx +177 -0
- package/src/utils/license-validator.tsx +183 -0
- package/src/utils/package-guard.ts +60 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { motion, useMotionValue, useTransform, useSpring } from "framer-motion";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
7
|
+
|
|
8
|
+
const magneticButtonVariants = cva(
|
|
9
|
+
"relative inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
variant: {
|
|
13
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
14
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
15
|
+
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
16
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
17
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
18
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
19
|
+
},
|
|
20
|
+
size: {
|
|
21
|
+
default: "h-10 px-4 py-2",
|
|
22
|
+
sm: "h-9 rounded-md px-3",
|
|
23
|
+
lg: "h-11 rounded-md px-8",
|
|
24
|
+
icon: "h-10 w-10",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
defaultVariants: {
|
|
28
|
+
variant: "default",
|
|
29
|
+
size: "default",
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export interface MagneticButtonProps
|
|
35
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
36
|
+
VariantProps<typeof magneticButtonVariants> {
|
|
37
|
+
magnetStrength?: number;
|
|
38
|
+
magnetDistance?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const MagneticButton = React.forwardRef<HTMLButtonElement, MagneticButtonProps>(
|
|
42
|
+
(
|
|
43
|
+
{
|
|
44
|
+
className,
|
|
45
|
+
variant,
|
|
46
|
+
size,
|
|
47
|
+
magnetStrength = 0.25,
|
|
48
|
+
magnetDistance = 100,
|
|
49
|
+
children,
|
|
50
|
+
onMouseMove,
|
|
51
|
+
onMouseLeave,
|
|
52
|
+
...props
|
|
53
|
+
},
|
|
54
|
+
ref
|
|
55
|
+
) => {
|
|
56
|
+
const buttonRef = React.useRef<HTMLDivElement>(null);
|
|
57
|
+
const mouseX = useMotionValue(0);
|
|
58
|
+
const mouseY = useMotionValue(0);
|
|
59
|
+
|
|
60
|
+
const springConfig = { damping: 15, stiffness: 150 };
|
|
61
|
+
const x = useSpring(useTransform(mouseX, (value) => value * magnetStrength), springConfig);
|
|
62
|
+
const y = useSpring(useTransform(mouseY, (value) => value * magnetStrength), springConfig);
|
|
63
|
+
|
|
64
|
+
const handleMouseMove = React.useCallback(
|
|
65
|
+
(e: React.MouseEvent<HTMLButtonElement>) => {
|
|
66
|
+
if (!buttonRef.current) return;
|
|
67
|
+
|
|
68
|
+
const rect = buttonRef.current.getBoundingClientRect();
|
|
69
|
+
const centerX = rect.left + rect.width / 2;
|
|
70
|
+
const centerY = rect.top + rect.height / 2;
|
|
71
|
+
|
|
72
|
+
const distanceX = e.clientX - centerX;
|
|
73
|
+
const distanceY = e.clientY - centerY;
|
|
74
|
+
|
|
75
|
+
const distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
|
|
76
|
+
|
|
77
|
+
if (distance < magnetDistance) {
|
|
78
|
+
mouseX.set(distanceX);
|
|
79
|
+
mouseY.set(distanceY);
|
|
80
|
+
} else {
|
|
81
|
+
mouseX.set(0);
|
|
82
|
+
mouseY.set(0);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
onMouseMove?.(e);
|
|
86
|
+
},
|
|
87
|
+
[magnetDistance, mouseX, mouseY, onMouseMove]
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const handleMouseLeave = React.useCallback(
|
|
91
|
+
(e: React.MouseEvent<HTMLButtonElement>) => {
|
|
92
|
+
mouseX.set(0);
|
|
93
|
+
mouseY.set(0);
|
|
94
|
+
onMouseLeave?.(e);
|
|
95
|
+
},
|
|
96
|
+
[mouseX, mouseY, onMouseLeave]
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<motion.div
|
|
101
|
+
ref={buttonRef}
|
|
102
|
+
style={{ x, y }}
|
|
103
|
+
className="inline-block"
|
|
104
|
+
>
|
|
105
|
+
<motion.button
|
|
106
|
+
ref={ref}
|
|
107
|
+
className={cn(magneticButtonVariants({ variant, size, className }))}
|
|
108
|
+
onMouseMove={handleMouseMove}
|
|
109
|
+
onMouseLeave={handleMouseLeave}
|
|
110
|
+
whileHover={{ scale: 1.05 }}
|
|
111
|
+
whileTap={{ scale: 0.95 }}
|
|
112
|
+
transition={{ type: "spring", stiffness: 400, damping: 17 }}
|
|
113
|
+
{...props}
|
|
114
|
+
>
|
|
115
|
+
<motion.span
|
|
116
|
+
style={{ x: useTransform(x, (value) => value * 0.2), y: useTransform(y, (value) => value * 0.2) }}
|
|
117
|
+
className="relative z-10"
|
|
118
|
+
>
|
|
119
|
+
{children}
|
|
120
|
+
</motion.span>
|
|
121
|
+
</motion.button>
|
|
122
|
+
</motion.div>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
MagneticButton.displayName = "MagneticButton";
|
|
128
|
+
|
|
129
|
+
export { MagneticButton, magneticButtonVariants };
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../lib/utils";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Popover Bileşeni
|
|
11
|
+
*
|
|
12
|
+
* Tıklanabilir bir öğeden açılan, tema sistemiyle tam entegre bir içerik bileşeni.
|
|
13
|
+
* Filtreler, ayarlar ve diğer içerikler için kullanışlı ve erişilebilir bir arayüz sağlar.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const popoverContentVariants = cva(
|
|
17
|
+
"z-50 w-72 rounded-md border border-border bg-background p-4 shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
18
|
+
{
|
|
19
|
+
variants: {
|
|
20
|
+
variant: {
|
|
21
|
+
default: "",
|
|
22
|
+
destructive: "border-destructive bg-destructive/5",
|
|
23
|
+
outline: "border-border bg-transparent",
|
|
24
|
+
subtle: "border-transparent bg-muted/50",
|
|
25
|
+
},
|
|
26
|
+
size: {
|
|
27
|
+
sm: "w-48 p-3",
|
|
28
|
+
default: "w-72 p-4",
|
|
29
|
+
lg: "w-96 p-5",
|
|
30
|
+
},
|
|
31
|
+
side: {
|
|
32
|
+
top: "data-[side=top]:slide-in-from-bottom-2",
|
|
33
|
+
right: "data-[side=right]:slide-in-from-left-2",
|
|
34
|
+
bottom: "data-[side=bottom]:slide-in-from-top-2",
|
|
35
|
+
left: "data-[side=left]:slide-in-from-right-2",
|
|
36
|
+
},
|
|
37
|
+
position: {
|
|
38
|
+
pointerEventsNone: "pointer-events-none",
|
|
39
|
+
default: "",
|
|
40
|
+
},
|
|
41
|
+
radius: {
|
|
42
|
+
none: "rounded-none",
|
|
43
|
+
sm: "rounded-sm",
|
|
44
|
+
default: "rounded-md",
|
|
45
|
+
lg: "rounded-lg",
|
|
46
|
+
full: "rounded-full",
|
|
47
|
+
},
|
|
48
|
+
shadow: {
|
|
49
|
+
none: "shadow-none",
|
|
50
|
+
sm: "shadow-sm",
|
|
51
|
+
default: "shadow-md",
|
|
52
|
+
md: "shadow-md",
|
|
53
|
+
lg: "shadow-lg",
|
|
54
|
+
xl: "shadow-xl",
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
defaultVariants: {
|
|
58
|
+
variant: "default",
|
|
59
|
+
size: "default",
|
|
60
|
+
radius: "default",
|
|
61
|
+
shadow: "default",
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const Popover = PopoverPrimitive.Root;
|
|
67
|
+
const PopoverTrigger = PopoverPrimitive.Trigger;
|
|
68
|
+
const PopoverAnchor = PopoverPrimitive.Anchor;
|
|
69
|
+
|
|
70
|
+
export interface PopoverContentProps
|
|
71
|
+
extends Omit<React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>, 'side'>,
|
|
72
|
+
VariantProps<typeof popoverContentVariants> {
|
|
73
|
+
/**
|
|
74
|
+
* Popover'ın arka plan bulanıklığı etkin mi
|
|
75
|
+
*/
|
|
76
|
+
backdrop?: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Popover'a tıklandığında kapanıp kapanmayacağı
|
|
79
|
+
*/
|
|
80
|
+
closeOnInteractOutside?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Popover açıkken arka planın karartılması
|
|
83
|
+
*/
|
|
84
|
+
overlayBackdrop?: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const PopoverContent = React.forwardRef<
|
|
88
|
+
React.ElementRef<typeof PopoverPrimitive.Content>,
|
|
89
|
+
PopoverContentProps
|
|
90
|
+
>(({
|
|
91
|
+
className,
|
|
92
|
+
variant,
|
|
93
|
+
size,
|
|
94
|
+
side,
|
|
95
|
+
position,
|
|
96
|
+
radius,
|
|
97
|
+
shadow,
|
|
98
|
+
backdrop = false,
|
|
99
|
+
closeOnInteractOutside = true,
|
|
100
|
+
overlayBackdrop = false,
|
|
101
|
+
sideOffset = 4,
|
|
102
|
+
...props
|
|
103
|
+
}, ref) => (
|
|
104
|
+
<>
|
|
105
|
+
{overlayBackdrop && (
|
|
106
|
+
<div className="fixed inset-0 z-40 bg-overlay opacity-30" />
|
|
107
|
+
)}
|
|
108
|
+
<PopoverPrimitive.Content
|
|
109
|
+
ref={ref}
|
|
110
|
+
sideOffset={sideOffset}
|
|
111
|
+
collisionPadding={8}
|
|
112
|
+
onInteractOutside={(e) => {
|
|
113
|
+
if (!closeOnInteractOutside) {
|
|
114
|
+
e.preventDefault();
|
|
115
|
+
}
|
|
116
|
+
}}
|
|
117
|
+
className={cn(
|
|
118
|
+
popoverContentVariants({
|
|
119
|
+
variant,
|
|
120
|
+
size,
|
|
121
|
+
side,
|
|
122
|
+
position,
|
|
123
|
+
radius,
|
|
124
|
+
shadow
|
|
125
|
+
}),
|
|
126
|
+
backdrop && "backdrop-blur-md bg-opacity-80",
|
|
127
|
+
className
|
|
128
|
+
)}
|
|
129
|
+
{...props}
|
|
130
|
+
/>
|
|
131
|
+
</>
|
|
132
|
+
));
|
|
133
|
+
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* PopoverClose bileşeni
|
|
137
|
+
*/
|
|
138
|
+
const PopoverClose = PopoverPrimitive.Close;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Popover için bölüm ayırıcı
|
|
142
|
+
*/
|
|
143
|
+
const PopoverSeparator = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
144
|
+
<div
|
|
145
|
+
className={cn("my-2 h-px bg-border", className)}
|
|
146
|
+
{...props}
|
|
147
|
+
/>
|
|
148
|
+
);
|
|
149
|
+
PopoverSeparator.displayName = "PopoverSeparator";
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Popover başlık bileşeni
|
|
153
|
+
*/
|
|
154
|
+
const PopoverHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
155
|
+
<div
|
|
156
|
+
className={cn("-mx-4 -mt-4 mb-3 px-4 pt-4 pb-3 border-b border-border", className)}
|
|
157
|
+
{...props}
|
|
158
|
+
/>
|
|
159
|
+
);
|
|
160
|
+
PopoverHeader.displayName = "PopoverHeader";
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Popover footer bileşeni
|
|
164
|
+
*/
|
|
165
|
+
const PopoverFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
166
|
+
<div
|
|
167
|
+
className={cn("-mx-4 -mb-4 mt-3 px-4 pt-3 pb-4 border-t border-border", className)}
|
|
168
|
+
{...props}
|
|
169
|
+
/>
|
|
170
|
+
);
|
|
171
|
+
PopoverFooter.displayName = "PopoverFooter";
|
|
172
|
+
|
|
173
|
+
export {
|
|
174
|
+
Popover,
|
|
175
|
+
PopoverTrigger,
|
|
176
|
+
PopoverContent,
|
|
177
|
+
PopoverAnchor,
|
|
178
|
+
PopoverClose,
|
|
179
|
+
PopoverSeparator,
|
|
180
|
+
PopoverHeader,
|
|
181
|
+
PopoverFooter,
|
|
182
|
+
popoverContentVariants,
|
|
183
|
+
};
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as SelectPrimitive from "@radix-ui/react-select"
|
|
5
|
+
import { Check, ChevronDown, ChevronUp, Loader2 } from "lucide-react"
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../lib/utils"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Premium Select Component
|
|
11
|
+
*
|
|
12
|
+
* Advanced dropdown/select component with variants, sizes and accessibility features.
|
|
13
|
+
* Compatible with dark and light mode, providing a modern user experience with smooth animations.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// Directly re-exporting the Root component
|
|
17
|
+
const Select = SelectPrimitive.Root
|
|
18
|
+
Select.displayName = "Select"
|
|
19
|
+
|
|
20
|
+
const SelectGroup = SelectPrimitive.Group
|
|
21
|
+
|
|
22
|
+
const SelectValue = SelectPrimitive.Value
|
|
23
|
+
|
|
24
|
+
type SelectTriggerVariant = "standard" | "outline" | "ghost" | "underline";
|
|
25
|
+
type SelectTriggerSize = "sm" | "md" | "lg";
|
|
26
|
+
|
|
27
|
+
interface SelectTriggerProps extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> {
|
|
28
|
+
/** Visual variant */
|
|
29
|
+
variant?: SelectTriggerVariant;
|
|
30
|
+
/** Size */
|
|
31
|
+
size?: SelectTriggerSize;
|
|
32
|
+
/** Error state */
|
|
33
|
+
error?: boolean | string;
|
|
34
|
+
/** Success state */
|
|
35
|
+
success?: boolean;
|
|
36
|
+
/** Loading state */
|
|
37
|
+
loading?: boolean;
|
|
38
|
+
/** Icon displayed on the left */
|
|
39
|
+
leftIcon?: React.ReactNode;
|
|
40
|
+
/** Icon displayed on the right (instead of default chevron) */
|
|
41
|
+
rightIcon?: React.ReactNode;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const SelectTrigger = React.forwardRef<
|
|
45
|
+
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
|
46
|
+
SelectTriggerProps
|
|
47
|
+
>(({ className, children, variant = "standard", size = "md", error, success, loading, leftIcon, rightIcon, ...props }, ref) => (
|
|
48
|
+
<SelectPrimitive.Trigger
|
|
49
|
+
ref={ref}
|
|
50
|
+
className={cn(
|
|
51
|
+
/* Base styles */
|
|
52
|
+
"flex w-full items-center justify-between gap-1 rounded-md transition-all duration-200",
|
|
53
|
+
"disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
|
54
|
+
"focus-visible:outline-none",
|
|
55
|
+
/* Error state */
|
|
56
|
+
error && "border-error focus-visible:ring-error/30 focus-visible:border-error",
|
|
57
|
+
/* Success state */
|
|
58
|
+
success && "border-success focus-visible:ring-success/30 focus-visible:border-success",
|
|
59
|
+
/* Size variants */
|
|
60
|
+
size === "sm" && "h-8 text-xs px-2",
|
|
61
|
+
size === "md" && "h-10 text-sm px-3",
|
|
62
|
+
size === "lg" && "h-12 text-base px-4",
|
|
63
|
+
/* Visual variants */
|
|
64
|
+
variant === "standard" && "border border-gray-300 dark:border-gray-700 bg-background dark:bg-gray-900 hover:border-gray-400 dark:hover:border-gray-600 focus-visible:ring-2 focus-visible:ring-primary/30 dark:focus-visible:ring-primary/50 focus-visible:border-primary dark:focus-visible:border-primary",
|
|
65
|
+
variant === "outline" && "border border-gray-300 dark:border-gray-700 bg-transparent hover:border-gray-400 dark:hover:border-gray-600 focus-visible:ring-2 focus-visible:ring-primary/30 dark:focus-visible:ring-primary/50 focus-visible:border-primary dark:focus-visible:border-primary",
|
|
66
|
+
variant === "ghost" && "border-none bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 focus-visible:ring-2 focus-visible:ring-primary/30 dark:focus-visible:ring-primary/50",
|
|
67
|
+
variant === "underline" && "border-t-0 border-l-0 border-r-0 border-b border-gray-300 dark:border-gray-600 rounded-none px-0 hover:border-gray-400 dark:hover:border-gray-500 focus-visible:ring-0 focus-visible:border-b-2 focus-visible:border-primary dark:focus-visible:border-primary",
|
|
68
|
+
className
|
|
69
|
+
)}
|
|
70
|
+
{...props}
|
|
71
|
+
disabled={props.disabled || loading}
|
|
72
|
+
data-error={error ? true : undefined}
|
|
73
|
+
data-success={success ? true : undefined}
|
|
74
|
+
data-loading={loading ? true : undefined}
|
|
75
|
+
>
|
|
76
|
+
<div className="flex items-center flex-1 gap-2">
|
|
77
|
+
{leftIcon && (
|
|
78
|
+
<span className="flex items-center justify-center text-gray-500 dark:text-gray-400">
|
|
79
|
+
{leftIcon}
|
|
80
|
+
</span>
|
|
81
|
+
)}
|
|
82
|
+
{loading ? (
|
|
83
|
+
<div className="flex items-center gap-2">
|
|
84
|
+
<Loader2 className="h-3.5 w-3.5 animate-spin text-muted-foreground dark:text-gray-400" />
|
|
85
|
+
<span className="text-muted-foreground dark:text-gray-400">Loading...</span>
|
|
86
|
+
</div>
|
|
87
|
+
) : children}
|
|
88
|
+
</div>
|
|
89
|
+
{!loading && (
|
|
90
|
+
<SelectPrimitive.Icon asChild>
|
|
91
|
+
{rightIcon ? (
|
|
92
|
+
<span className="opacity-60">{rightIcon}</span>
|
|
93
|
+
) : (
|
|
94
|
+
<ChevronDown className="h-4 w-4 opacity-60 transition-transform duration-200 ease-out group-data-[state=open]:rotate-180" />
|
|
95
|
+
)}
|
|
96
|
+
</SelectPrimitive.Icon>
|
|
97
|
+
)}
|
|
98
|
+
</SelectPrimitive.Trigger>
|
|
99
|
+
))
|
|
100
|
+
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Scroll Up Button Component
|
|
104
|
+
*/
|
|
105
|
+
const SelectScrollUpButton = React.forwardRef<
|
|
106
|
+
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
|
107
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
|
108
|
+
>(({ className, ...props }, ref) => (
|
|
109
|
+
<SelectPrimitive.ScrollUpButton
|
|
110
|
+
ref={ref}
|
|
111
|
+
className={cn(
|
|
112
|
+
"flex cursor-default items-center justify-center py-1 text-muted-foreground hover:text-foreground transition-colors",
|
|
113
|
+
className
|
|
114
|
+
)}
|
|
115
|
+
{...props}
|
|
116
|
+
>
|
|
117
|
+
<ChevronUp className="h-4 w-4" />
|
|
118
|
+
</SelectPrimitive.ScrollUpButton>
|
|
119
|
+
))
|
|
120
|
+
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Scroll Down Button Component
|
|
124
|
+
*/
|
|
125
|
+
const SelectScrollDownButton = React.forwardRef<
|
|
126
|
+
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
|
127
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
|
128
|
+
>(({ className, ...props }, ref) => (
|
|
129
|
+
<SelectPrimitive.ScrollDownButton
|
|
130
|
+
ref={ref}
|
|
131
|
+
className={cn(
|
|
132
|
+
"flex cursor-default items-center justify-center py-1 text-muted-foreground hover:text-foreground transition-colors",
|
|
133
|
+
className
|
|
134
|
+
)}
|
|
135
|
+
{...props}
|
|
136
|
+
>
|
|
137
|
+
<ChevronDown className="h-4 w-4" />
|
|
138
|
+
</SelectPrimitive.ScrollDownButton>
|
|
139
|
+
))
|
|
140
|
+
SelectScrollDownButton.displayName =
|
|
141
|
+
SelectPrimitive.ScrollDownButton.displayName
|
|
142
|
+
|
|
143
|
+
const SelectContent = React.forwardRef<
|
|
144
|
+
React.ElementRef<typeof SelectPrimitive.Content>,
|
|
145
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
|
146
|
+
>(({ className, children, position = "item-aligned", ...props }, ref) => (
|
|
147
|
+
<SelectPrimitive.Portal>
|
|
148
|
+
<SelectPrimitive.Content
|
|
149
|
+
ref={ref}
|
|
150
|
+
className={cn(
|
|
151
|
+
["relative z-50 max-h-96 min-w-[8rem] overflow-hidden",
|
|
152
|
+
"rounded-md border border-gray-200 dark:border-gray-700",
|
|
153
|
+
"bg-white dark:bg-gray-900 text-foreground",
|
|
154
|
+
"shadow-lg dark:shadow-gray-900/20",
|
|
155
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
156
|
+
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
157
|
+
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
158
|
+
"data-[state=open]:duration-150"],
|
|
159
|
+
position === "popper" &&
|
|
160
|
+
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
161
|
+
className
|
|
162
|
+
)}
|
|
163
|
+
position={position}
|
|
164
|
+
{...props}
|
|
165
|
+
>
|
|
166
|
+
<SelectScrollUpButton />
|
|
167
|
+
<SelectPrimitive.Viewport
|
|
168
|
+
className={cn(
|
|
169
|
+
"p-1",
|
|
170
|
+
position === "popper" &&
|
|
171
|
+
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
|
172
|
+
)}
|
|
173
|
+
>
|
|
174
|
+
{children}
|
|
175
|
+
</SelectPrimitive.Viewport>
|
|
176
|
+
<SelectScrollDownButton />
|
|
177
|
+
</SelectPrimitive.Content>
|
|
178
|
+
</SelectPrimitive.Portal>
|
|
179
|
+
))
|
|
180
|
+
SelectContent.displayName = SelectPrimitive.Content.displayName
|
|
181
|
+
|
|
182
|
+
const SelectLabel = React.forwardRef<
|
|
183
|
+
React.ElementRef<typeof SelectPrimitive.Label>,
|
|
184
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
|
185
|
+
>(({ className, ...props }, ref) => (
|
|
186
|
+
<SelectPrimitive.Label
|
|
187
|
+
ref={ref}
|
|
188
|
+
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
|
189
|
+
{...props}
|
|
190
|
+
/>
|
|
191
|
+
))
|
|
192
|
+
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
|
193
|
+
|
|
194
|
+
type SelectItemVariant = "default" | "subtle" | "destructive" | "success" | "warning";
|
|
195
|
+
type SelectItemSize = "sm" | "md" | "lg";
|
|
196
|
+
|
|
197
|
+
interface SelectItemProps extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> {
|
|
198
|
+
/** Visual variant */
|
|
199
|
+
variant?: SelectItemVariant;
|
|
200
|
+
/** Size */
|
|
201
|
+
size?: SelectItemSize;
|
|
202
|
+
/** Icon displayed on the right */
|
|
203
|
+
rightIcon?: React.ReactNode;
|
|
204
|
+
/** Custom indicator icon (instead of default check) */
|
|
205
|
+
customIndicator?: React.ReactNode;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const SelectItem = React.forwardRef<
|
|
209
|
+
React.ElementRef<typeof SelectPrimitive.Item>,
|
|
210
|
+
SelectItemProps
|
|
211
|
+
>(({ className, children, variant = "default", size = "md", rightIcon, customIndicator, ...props }, ref) => (
|
|
212
|
+
<SelectPrimitive.Item
|
|
213
|
+
ref={ref}
|
|
214
|
+
className={cn(
|
|
215
|
+
"relative flex w-full cursor-default select-none items-center rounded-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
216
|
+
|
|
217
|
+
/* Size variants */
|
|
218
|
+
size === "sm" && "py-1 pl-7 pr-2 text-xs",
|
|
219
|
+
size === "md" && "py-1.5 pl-8 pr-2 text-sm",
|
|
220
|
+
size === "lg" && "py-2 pl-9 pr-3 text-base",
|
|
221
|
+
|
|
222
|
+
/* Color variants */
|
|
223
|
+
variant === "default" && "focus:bg-accent focus:text-accent-foreground dark:focus:bg-gray-700 dark:focus:text-gray-100",
|
|
224
|
+
variant === "subtle" && "focus:bg-gray-100 dark:focus:bg-gray-800 focus:text-foreground dark:focus:text-gray-200",
|
|
225
|
+
variant === "destructive" && "text-error dark:text-red-400 focus:bg-error/10 dark:focus:bg-red-900/20 focus:text-error dark:focus:text-red-300",
|
|
226
|
+
variant === "success" && "text-success dark:text-green-400 focus:bg-success/10 dark:focus:bg-green-900/20 focus:text-success dark:focus:text-green-300",
|
|
227
|
+
variant === "warning" && "text-warning dark:text-yellow-400 focus:bg-warning/10 dark:focus:bg-yellow-900/20 focus:text-warning dark:focus:text-yellow-300",
|
|
228
|
+
|
|
229
|
+
className
|
|
230
|
+
)}
|
|
231
|
+
{...props}
|
|
232
|
+
>
|
|
233
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
234
|
+
<SelectPrimitive.ItemIndicator>
|
|
235
|
+
{customIndicator || <Check className="h-4 w-4" />}
|
|
236
|
+
</SelectPrimitive.ItemIndicator>
|
|
237
|
+
</span>
|
|
238
|
+
|
|
239
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
240
|
+
|
|
241
|
+
{rightIcon && (
|
|
242
|
+
<span className="ml-auto pl-2 opacity-70">
|
|
243
|
+
{rightIcon}
|
|
244
|
+
</span>
|
|
245
|
+
)}
|
|
246
|
+
</SelectPrimitive.Item>
|
|
247
|
+
))
|
|
248
|
+
SelectItem.displayName = SelectPrimitive.Item.displayName
|
|
249
|
+
|
|
250
|
+
const SelectSeparator = React.forwardRef<
|
|
251
|
+
React.ElementRef<typeof SelectPrimitive.Separator>,
|
|
252
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
|
253
|
+
>(({ className, ...props }, ref) => (
|
|
254
|
+
<SelectPrimitive.Separator
|
|
255
|
+
ref={ref}
|
|
256
|
+
className={cn("-mx-1 my-1 h-px bg-muted dark:bg-gray-700", className)}
|
|
257
|
+
{...props}
|
|
258
|
+
/>
|
|
259
|
+
))
|
|
260
|
+
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
|
261
|
+
|
|
262
|
+
export {
|
|
263
|
+
Select,
|
|
264
|
+
SelectGroup,
|
|
265
|
+
SelectValue,
|
|
266
|
+
SelectTrigger,
|
|
267
|
+
SelectContent,
|
|
268
|
+
SelectLabel,
|
|
269
|
+
SelectItem,
|
|
270
|
+
SelectSeparator,
|
|
271
|
+
SelectScrollUpButton,
|
|
272
|
+
SelectScrollDownButton,
|
|
273
|
+
}
|