@turtleclub/ui 0.0.1
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/.turbo/turbo-build.log +15 -0
- package/.turbo/turbo-type-check.log +360 -0
- package/README.md +3 -0
- package/components.json +21 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +1672 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +1 -0
- package/package.json +66 -0
- package/src/components/molecules/index.ts +7 -0
- package/src/components/molecules/opportunity-details.tsx +145 -0
- package/src/components/molecules/opportunity-item.tsx +63 -0
- package/src/components/molecules/route-details.tsx +87 -0
- package/src/components/molecules/swap-details.tsx +95 -0
- package/src/components/molecules/swap-input.tsx +115 -0
- package/src/components/molecules/token-selector.tsx +72 -0
- package/src/components/molecules/tx-status.tsx +254 -0
- package/src/components/ui/button.tsx +65 -0
- package/src/components/ui/card.tsx +101 -0
- package/src/components/ui/chip.tsx +48 -0
- package/src/components/ui/icon-animation.tsx +82 -0
- package/src/components/ui/index.ts +18 -0
- package/src/components/ui/info-card.tsx +128 -0
- package/src/components/ui/input.tsx +78 -0
- package/src/components/ui/label-with-icon.tsx +112 -0
- package/src/components/ui/label.tsx +22 -0
- package/src/components/ui/navigation-bar.tsx +135 -0
- package/src/components/ui/opportunity-details-v1.tsx +90 -0
- package/src/components/ui/scroll-area.tsx +56 -0
- package/src/components/ui/select.tsx +180 -0
- package/src/components/ui/separator.tsx +26 -0
- package/src/components/ui/sonner.tsx +23 -0
- package/src/components/ui/switch.tsx +29 -0
- package/src/components/ui/toggle-group.tsx +71 -0
- package/src/components/ui/toggle.tsx +47 -0
- package/src/components/ui/tooltip.tsx +59 -0
- package/src/index.ts +9 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles/globals.css +75 -0
- package/src/styles/themes/index.css +9 -0
- package/src/styles/themes/semantic.css +107 -0
- package/src/styles/tokens/colors.css +77 -0
- package/src/styles/tokens/index.css +15 -0
- package/src/styles/tokens/radius.css +46 -0
- package/src/styles/tokens/spacing.css +52 -0
- package/src/styles/tokens/typography.css +86 -0
- package/src/tokens/index.ts +108 -0
- package/tsconfig.json +21 -0
- package/vite.config.js +65 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
const inputVariants = cva(
|
|
7
|
+
"flex w-full bg-transparent text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground transition-colors outline-none disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
// Turtle Design System - transparent input with no borders
|
|
12
|
+
default: "border-none focus:ring-0 focus:border-none caret-primary",
|
|
13
|
+
// Optional bordered version for other use cases
|
|
14
|
+
bordered: "border border-border rounded-md focus:border-primary focus:ring-2 focus:ring-primary/20 caret-primary",
|
|
15
|
+
// No focus variant - cursor color only, no focus styles
|
|
16
|
+
nofocus: "border-none focus:ring-0 focus:border-none focus:outline-none caret-primary",
|
|
17
|
+
},
|
|
18
|
+
size: {
|
|
19
|
+
default: "h-10 px-3 py-2 text-sm",
|
|
20
|
+
sm: "h-8 px-2 py-1 text-xs",
|
|
21
|
+
lg: "h-12 px-4 py-3 text-base",
|
|
22
|
+
},
|
|
23
|
+
cursor: {
|
|
24
|
+
primary: "caret-primary",
|
|
25
|
+
foreground: "caret-foreground",
|
|
26
|
+
white: "caret-white",
|
|
27
|
+
green: "caret-green-500",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
defaultVariants: {
|
|
31
|
+
variant: "default",
|
|
32
|
+
size: "default",
|
|
33
|
+
cursor: "primary",
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
export interface InputProps
|
|
39
|
+
extends Omit<React.ComponentProps<"input">, "size">,
|
|
40
|
+
VariantProps<typeof inputVariants> {
|
|
41
|
+
// Optional prompt text that appears before the input
|
|
42
|
+
prompt?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
46
|
+
({ className, variant, size, cursor, type, prompt, ...props }, ref) => {
|
|
47
|
+
if (prompt) {
|
|
48
|
+
return (
|
|
49
|
+
<div className="flex items-center gap-2">
|
|
50
|
+
<span className="text-primary text-sm font-medium shrink-0">
|
|
51
|
+
{prompt}
|
|
52
|
+
</span>
|
|
53
|
+
<input
|
|
54
|
+
type={type}
|
|
55
|
+
data-slot="input"
|
|
56
|
+
className={cn(inputVariants({ variant, size, cursor, className }))}
|
|
57
|
+
ref={ref}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<input
|
|
66
|
+
type={type}
|
|
67
|
+
data-slot="input"
|
|
68
|
+
className={cn(inputVariants({ variant, size, cursor, className }))}
|
|
69
|
+
ref={ref}
|
|
70
|
+
{...props}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
Input.displayName = "Input";
|
|
77
|
+
|
|
78
|
+
export { Input, inputVariants };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
const labelWithIconVariants = cva("inline-flex items-center gap-2 font-medium", {
|
|
7
|
+
variants: {
|
|
8
|
+
variant: {
|
|
9
|
+
default: "text-foreground",
|
|
10
|
+
muted: "text-muted-foreground",
|
|
11
|
+
primary: "text-primary",
|
|
12
|
+
secondary: "text-secondary-foreground",
|
|
13
|
+
},
|
|
14
|
+
textSize: {
|
|
15
|
+
xs: "text-xs",
|
|
16
|
+
sm: "text-sm",
|
|
17
|
+
base: "text-base",
|
|
18
|
+
lg: "text-lg",
|
|
19
|
+
xl: "text-xl",
|
|
20
|
+
"2xl": "text-2xl",
|
|
21
|
+
"3xl": "text-3xl",
|
|
22
|
+
"4xl": "text-4xl",
|
|
23
|
+
"5xl": "text-5xl",
|
|
24
|
+
"6xl": "text-6xl",
|
|
25
|
+
"7xl": "text-7xl",
|
|
26
|
+
"8xl": "text-8xl",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
defaultVariants: {
|
|
30
|
+
variant: "default",
|
|
31
|
+
textSize: "sm",
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const iconSizeClasses = {
|
|
36
|
+
xs: "w-3 h-3",
|
|
37
|
+
sm: "w-4 h-4",
|
|
38
|
+
base: "w-5 h-5",
|
|
39
|
+
lg: "w-6 h-6",
|
|
40
|
+
xl: "w-8 h-8",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export interface LabelWithIconProps
|
|
44
|
+
extends React.ComponentProps<"div">,
|
|
45
|
+
VariantProps<typeof labelWithIconVariants> {
|
|
46
|
+
icon: React.ReactNode | string; // Can be a component or URL string
|
|
47
|
+
children: React.ReactNode;
|
|
48
|
+
iconPosition?: "left" | "right";
|
|
49
|
+
iconSize?: keyof typeof iconSizeClasses;
|
|
50
|
+
iconClassName?: string; // Additional classes for the icon
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const LabelWithIcon = React.forwardRef<HTMLDivElement, LabelWithIconProps>(
|
|
54
|
+
(
|
|
55
|
+
{
|
|
56
|
+
className,
|
|
57
|
+
variant,
|
|
58
|
+
textSize,
|
|
59
|
+
icon,
|
|
60
|
+
children,
|
|
61
|
+
iconPosition = "left",
|
|
62
|
+
iconSize = "sm",
|
|
63
|
+
iconClassName,
|
|
64
|
+
...props
|
|
65
|
+
},
|
|
66
|
+
ref
|
|
67
|
+
) => {
|
|
68
|
+
const renderIcon = () => {
|
|
69
|
+
// If icon is a string (URL), render as img
|
|
70
|
+
if (typeof icon === "string") {
|
|
71
|
+
return (
|
|
72
|
+
<img
|
|
73
|
+
src={icon}
|
|
74
|
+
alt=""
|
|
75
|
+
className={cn(iconSizeClasses[iconSize], "object-contain", iconClassName)}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// If icon is a React component, render it directly
|
|
81
|
+
// If it's a Lucide icon or any other component, it will be rendered as-is
|
|
82
|
+
return (
|
|
83
|
+
<span className={cn("shrink-0", iconClassName)}>
|
|
84
|
+
{React.isValidElement(icon)
|
|
85
|
+
? React.cloneElement(icon as React.ReactElement<any>, {
|
|
86
|
+
className: cn(iconSizeClasses[iconSize], (icon as any).props?.className),
|
|
87
|
+
})
|
|
88
|
+
: icon}
|
|
89
|
+
</span>
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div
|
|
95
|
+
ref={ref}
|
|
96
|
+
className={cn(
|
|
97
|
+
labelWithIconVariants({ variant, textSize }),
|
|
98
|
+
iconPosition === "right" && "flex-row-reverse",
|
|
99
|
+
className
|
|
100
|
+
)}
|
|
101
|
+
{...props}
|
|
102
|
+
>
|
|
103
|
+
{renderIcon()}
|
|
104
|
+
<span>{children}</span>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
LabelWithIcon.displayName = "LabelWithIcon";
|
|
111
|
+
|
|
112
|
+
export { LabelWithIcon, labelWithIconVariants };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import * as LabelPrimitive from "@radix-ui/react-label"
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
|
|
6
|
+
function Label({
|
|
7
|
+
className,
|
|
8
|
+
...props
|
|
9
|
+
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
10
|
+
return (
|
|
11
|
+
<LabelPrimitive.Root
|
|
12
|
+
data-slot="label"
|
|
13
|
+
className={cn(
|
|
14
|
+
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
15
|
+
className
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { Label }
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
const navigationBarVariants = cva("flex w-full", {
|
|
7
|
+
variants: {
|
|
8
|
+
variant: {
|
|
9
|
+
default: "justify-between border border-border shadow-sm bg-background",
|
|
10
|
+
transparent: "justify-between border border-border shadow-sm bg-transparent",
|
|
11
|
+
menuBar: "relative h-12 gap-3 rounded-full border border-border bg-background font-medium shadow-sm",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
defaultVariants: {
|
|
15
|
+
variant: "default",
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const navigationItemVariants = cva(
|
|
20
|
+
"flex items-center justify-center whitespace-nowrap font-medium transition-all disabled:pointer-events-none disabled:opacity-50 cursor-pointer",
|
|
21
|
+
{
|
|
22
|
+
variants: {
|
|
23
|
+
variant: {
|
|
24
|
+
default: "text-sm text-muted-foreground hover:text-foreground",
|
|
25
|
+
active: "text-sm text-primary bg-muted",
|
|
26
|
+
menuBarDefault: "relative z-[1] w-full text-base text-foreground rounded-full px-3 py-2 hover:text-primary",
|
|
27
|
+
menuBarActive: "relative z-[1] w-full text-base text-primary rounded-full px-3 py-2",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
defaultVariants: {
|
|
31
|
+
variant: "default",
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
export interface NavigationBarProps
|
|
37
|
+
extends React.ComponentProps<"nav">,
|
|
38
|
+
VariantProps<typeof navigationBarVariants> {
|
|
39
|
+
activeValue?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface NavigationItemProps
|
|
43
|
+
extends React.ComponentProps<"button">,
|
|
44
|
+
VariantProps<typeof navigationItemVariants> {
|
|
45
|
+
active?: boolean;
|
|
46
|
+
value?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const NavigationBar = React.forwardRef<HTMLElement, NavigationBarProps>(
|
|
50
|
+
({ className, variant, activeValue, children, ...props }, ref) => {
|
|
51
|
+
const containerRef = React.useRef<HTMLElement>(null);
|
|
52
|
+
const indicatorRef = React.useRef<HTMLDivElement>(null);
|
|
53
|
+
|
|
54
|
+
// Function to update the indicator position and size based on the active button
|
|
55
|
+
const updateIndicatorPosition = React.useCallback(() => {
|
|
56
|
+
if (variant !== "menuBar" || !containerRef.current || !indicatorRef.current) return;
|
|
57
|
+
|
|
58
|
+
const activeButton = containerRef.current.querySelector(`[data-active="true"]`) as HTMLElement;
|
|
59
|
+
if (activeButton) {
|
|
60
|
+
indicatorRef.current.style.width = `${activeButton.offsetWidth}px`;
|
|
61
|
+
indicatorRef.current.style.left = `${activeButton.offsetLeft}px`;
|
|
62
|
+
}
|
|
63
|
+
}, [variant]);
|
|
64
|
+
|
|
65
|
+
// Create ResizeObserver to handle indicator container size changes
|
|
66
|
+
React.useEffect(() => {
|
|
67
|
+
if (variant !== "menuBar") return;
|
|
68
|
+
|
|
69
|
+
updateIndicatorPosition();
|
|
70
|
+
|
|
71
|
+
const resizeObserver = new ResizeObserver(updateIndicatorPosition);
|
|
72
|
+
|
|
73
|
+
if (containerRef.current) {
|
|
74
|
+
resizeObserver.observe(containerRef.current);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return () => {
|
|
78
|
+
resizeObserver.disconnect();
|
|
79
|
+
};
|
|
80
|
+
}, [activeValue, updateIndicatorPosition, variant]);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<nav
|
|
84
|
+
ref={ref || containerRef}
|
|
85
|
+
className={cn(navigationBarVariants({ variant, className }))}
|
|
86
|
+
{...props}
|
|
87
|
+
>
|
|
88
|
+
{variant === "menuBar" && (
|
|
89
|
+
<div
|
|
90
|
+
ref={indicatorRef}
|
|
91
|
+
className="absolute bottom-0 h-full origin-left rounded-full bg-secondary transition-all duration-300"
|
|
92
|
+
/>
|
|
93
|
+
)}
|
|
94
|
+
{children}
|
|
95
|
+
</nav>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
NavigationBar.displayName = "NavigationBar";
|
|
101
|
+
|
|
102
|
+
const NavigationItem = React.forwardRef<HTMLButtonElement, NavigationItemProps>(
|
|
103
|
+
({ className, variant, active, value, ...props }, ref) => {
|
|
104
|
+
// Determine the correct variant based on parent and state
|
|
105
|
+
const getItemVariant = () => {
|
|
106
|
+
if (variant === "menuBarDefault" || variant === "menuBarActive") {
|
|
107
|
+
return active ? "menuBarActive" : "menuBarDefault";
|
|
108
|
+
}
|
|
109
|
+
return active ? "active" : variant || "default";
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const appliedVariant = getItemVariant();
|
|
113
|
+
const isMenuBar = appliedVariant === "menuBarDefault" || appliedVariant === "menuBarActive";
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<button
|
|
117
|
+
ref={ref}
|
|
118
|
+
data-active={active}
|
|
119
|
+
data-value={value}
|
|
120
|
+
className={cn(
|
|
121
|
+
navigationItemVariants({
|
|
122
|
+
variant: appliedVariant,
|
|
123
|
+
className,
|
|
124
|
+
}),
|
|
125
|
+
!isMenuBar && "px-4 py-2 rounded-md"
|
|
126
|
+
)}
|
|
127
|
+
{...props}
|
|
128
|
+
/>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
NavigationItem.displayName = "NavigationItem";
|
|
134
|
+
|
|
135
|
+
export { NavigationBar, NavigationItem, navigationBarVariants, navigationItemVariants };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
import { Card } from "./card";
|
|
4
|
+
import { InfoCard } from "./info-card";
|
|
5
|
+
import { LabelWithIcon } from "./label-with-icon";
|
|
6
|
+
|
|
7
|
+
interface InfoCardData {
|
|
8
|
+
title: string;
|
|
9
|
+
value: string;
|
|
10
|
+
color?: "primary" | "secondary" | "accent" | "success" | "warning" | "error";
|
|
11
|
+
icon?: React.ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface OpportunityDetailsV1Props {
|
|
15
|
+
title?: string;
|
|
16
|
+
titleIcon?: React.ReactNode;
|
|
17
|
+
topCards: readonly InfoCardData[]; // Flexible array for top row
|
|
18
|
+
bottomCards: readonly InfoCardData[]; // Flexible array for bottom
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function OpportunityDetailsV1({
|
|
23
|
+
className,
|
|
24
|
+
title,
|
|
25
|
+
titleIcon,
|
|
26
|
+
topCards,
|
|
27
|
+
bottomCards,
|
|
28
|
+
}: OpportunityDetailsV1Props) {
|
|
29
|
+
const defaultTitleIcon = (
|
|
30
|
+
<div className="w-8 h-8 rounded-full bg-primary/20 flex items-center justify-center">
|
|
31
|
+
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
|
|
32
|
+
<path d="M12 2L13.09 8.26L20 7L18.74 13.09L22 14L16.74 19.26L17 21L10.91 19.74L10 22L8.09 15.74L2 17L3.26 10.91L0 10L5.26 4.74L5 3L11.09 4.26L12 2Z" />
|
|
33
|
+
</svg>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Card className={cn("space-y-4", className)}>
|
|
39
|
+
{/* TODO: Add scroll area */}
|
|
40
|
+
<div className="h-full overflow-y-auto">
|
|
41
|
+
<div className="space-y-4">
|
|
42
|
+
{/* Header with LabelWithIcon */}
|
|
43
|
+
{(title || titleIcon) && (
|
|
44
|
+
<div className="flex items-center">
|
|
45
|
+
<LabelWithIcon icon={titleIcon || defaultTitleIcon} textSize="lg" iconSize="lg">
|
|
46
|
+
{title}
|
|
47
|
+
</LabelWithIcon>
|
|
48
|
+
</div>
|
|
49
|
+
)}
|
|
50
|
+
|
|
51
|
+
{/* Top row: 3 InfoCards side by side */}
|
|
52
|
+
<div className="grid grid-cols-3 gap-3">
|
|
53
|
+
{topCards.map((card, index) => (
|
|
54
|
+
<InfoCard
|
|
55
|
+
key={index}
|
|
56
|
+
title={card.title}
|
|
57
|
+
value={card.value}
|
|
58
|
+
color={card.color || "primary"}
|
|
59
|
+
icon={card.icon}
|
|
60
|
+
size="sm"
|
|
61
|
+
valueSize="sm"
|
|
62
|
+
titleSize="sm"
|
|
63
|
+
align="left"
|
|
64
|
+
/>
|
|
65
|
+
))}
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
{/* Bottom section: 4 InfoCards full width, one below another */}
|
|
69
|
+
<div className="space-y-2">
|
|
70
|
+
{bottomCards.map((card, index) => (
|
|
71
|
+
<InfoCard
|
|
72
|
+
key={index}
|
|
73
|
+
title={card.title}
|
|
74
|
+
value={card.value}
|
|
75
|
+
color={card.color || "primary"}
|
|
76
|
+
size="sm"
|
|
77
|
+
valueSize="sm"
|
|
78
|
+
titleSize="sm"
|
|
79
|
+
align="left"
|
|
80
|
+
className="w-full"
|
|
81
|
+
/>
|
|
82
|
+
))}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</Card>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { OpportunityDetailsV1, type InfoCardData };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
|
|
6
|
+
function ScrollArea({
|
|
7
|
+
className,
|
|
8
|
+
children,
|
|
9
|
+
...props
|
|
10
|
+
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
|
|
11
|
+
return (
|
|
12
|
+
<ScrollAreaPrimitive.Root
|
|
13
|
+
data-slot="scroll-area"
|
|
14
|
+
className={cn("relative", className)}
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<ScrollAreaPrimitive.Viewport
|
|
18
|
+
data-slot="scroll-area-viewport"
|
|
19
|
+
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
|
|
20
|
+
>
|
|
21
|
+
{children}
|
|
22
|
+
</ScrollAreaPrimitive.Viewport>
|
|
23
|
+
<ScrollBar />
|
|
24
|
+
<ScrollAreaPrimitive.Corner />
|
|
25
|
+
</ScrollAreaPrimitive.Root>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ScrollBar({
|
|
30
|
+
className,
|
|
31
|
+
orientation = "vertical",
|
|
32
|
+
...props
|
|
33
|
+
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
|
|
34
|
+
return (
|
|
35
|
+
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
|
36
|
+
data-slot="scroll-area-scrollbar"
|
|
37
|
+
orientation={orientation}
|
|
38
|
+
className={cn(
|
|
39
|
+
"flex touch-none p-px transition-colors select-none",
|
|
40
|
+
orientation === "vertical" &&
|
|
41
|
+
"h-full w-2.5 border-l border-l-transparent",
|
|
42
|
+
orientation === "horizontal" &&
|
|
43
|
+
"h-2.5 flex-col border-t border-t-transparent",
|
|
44
|
+
className
|
|
45
|
+
)}
|
|
46
|
+
{...props}
|
|
47
|
+
>
|
|
48
|
+
<ScrollAreaPrimitive.ScrollAreaThumb
|
|
49
|
+
data-slot="scroll-area-thumb"
|
|
50
|
+
className="bg-border relative flex-1 rounded-full"
|
|
51
|
+
/>
|
|
52
|
+
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { ScrollArea, ScrollBar }
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
3
|
+
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
function Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
|
8
|
+
return <SelectPrimitive.Root data-slot="select" {...props} />;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function SelectGroup({ ...props }: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
|
12
|
+
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
|
16
|
+
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function SelectTrigger({
|
|
20
|
+
className,
|
|
21
|
+
size = "default",
|
|
22
|
+
children,
|
|
23
|
+
...props
|
|
24
|
+
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
|
25
|
+
size?: "sm" | "default";
|
|
26
|
+
}) {
|
|
27
|
+
return (
|
|
28
|
+
<SelectPrimitive.Trigger
|
|
29
|
+
data-slot="select-trigger"
|
|
30
|
+
data-size={size}
|
|
31
|
+
className={cn(
|
|
32
|
+
// Turtle Design System - transparent trigger with wise white text
|
|
33
|
+
"flex w-fit items-center justify-between gap-2 bg-transparent px-3 py-2 text-sm text-foreground whitespace-nowrap transition-colors outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 border-none focus:ring-0",
|
|
34
|
+
"data-[placeholder]:text-muted-foreground",
|
|
35
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg]:text-muted-foreground",
|
|
36
|
+
"*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2",
|
|
37
|
+
className
|
|
38
|
+
)}
|
|
39
|
+
{...props}
|
|
40
|
+
>
|
|
41
|
+
{children}
|
|
42
|
+
<SelectPrimitive.Icon asChild>
|
|
43
|
+
<ChevronDownIcon className="size-4 opacity-50" />
|
|
44
|
+
</SelectPrimitive.Icon>
|
|
45
|
+
</SelectPrimitive.Trigger>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function SelectContent({
|
|
50
|
+
className,
|
|
51
|
+
children,
|
|
52
|
+
position = "popper",
|
|
53
|
+
...props
|
|
54
|
+
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
|
55
|
+
return (
|
|
56
|
+
<SelectPrimitive.Portal>
|
|
57
|
+
<SelectPrimitive.Content
|
|
58
|
+
data-slot="select-content"
|
|
59
|
+
className={cn(
|
|
60
|
+
// Turtle Design System - ninja black background for content
|
|
61
|
+
"bg-background text-foreground 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 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-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border border-border shadow-md",
|
|
62
|
+
position === "popper" &&
|
|
63
|
+
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
64
|
+
className
|
|
65
|
+
)}
|
|
66
|
+
position={position}
|
|
67
|
+
{...props}
|
|
68
|
+
>
|
|
69
|
+
<SelectScrollUpButton />
|
|
70
|
+
<SelectPrimitive.Viewport
|
|
71
|
+
className={cn(
|
|
72
|
+
"p-1",
|
|
73
|
+
position === "popper" &&
|
|
74
|
+
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
|
|
75
|
+
)}
|
|
76
|
+
>
|
|
77
|
+
{children}
|
|
78
|
+
</SelectPrimitive.Viewport>
|
|
79
|
+
<SelectScrollDownButton />
|
|
80
|
+
</SelectPrimitive.Content>
|
|
81
|
+
</SelectPrimitive.Portal>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
|
86
|
+
return (
|
|
87
|
+
<SelectPrimitive.Label
|
|
88
|
+
data-slot="select-label"
|
|
89
|
+
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function SelectItem({
|
|
96
|
+
className,
|
|
97
|
+
children,
|
|
98
|
+
...props
|
|
99
|
+
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
|
100
|
+
return (
|
|
101
|
+
<SelectPrimitive.Item
|
|
102
|
+
data-slot="select-item"
|
|
103
|
+
className={cn(
|
|
104
|
+
// Turtle Design System - items with wise white alpha (2%) background and ninja black text
|
|
105
|
+
"relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm text-foreground outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
106
|
+
"bg-muted hover:bg-muted/80 focus:bg-muted/80", // wise white alpha (2%) background
|
|
107
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
108
|
+
"*:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
|
109
|
+
className
|
|
110
|
+
)}
|
|
111
|
+
{...props}
|
|
112
|
+
>
|
|
113
|
+
<span className="absolute right-2 flex size-3.5 items-center justify-center">
|
|
114
|
+
{/* Single dot that changes color based on selection */}
|
|
115
|
+
<div className="w-2 h-2 rounded-full bg-muted-foreground/30 transition-colors" />
|
|
116
|
+
{/* Use Radix's ItemIndicator to show selected state */}
|
|
117
|
+
<SelectPrimitive.ItemIndicator className="absolute">
|
|
118
|
+
<div className="w-2 h-2 rounded-full bg-primary" />
|
|
119
|
+
</SelectPrimitive.ItemIndicator>
|
|
120
|
+
</span>
|
|
121
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
122
|
+
</SelectPrimitive.Item>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function SelectSeparator({
|
|
127
|
+
className,
|
|
128
|
+
...props
|
|
129
|
+
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
|
130
|
+
return (
|
|
131
|
+
<SelectPrimitive.Separator
|
|
132
|
+
data-slot="select-separator"
|
|
133
|
+
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
|
134
|
+
{...props}
|
|
135
|
+
/>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function SelectScrollUpButton({
|
|
140
|
+
className,
|
|
141
|
+
...props
|
|
142
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
|
143
|
+
return (
|
|
144
|
+
<SelectPrimitive.ScrollUpButton
|
|
145
|
+
data-slot="select-scroll-up-button"
|
|
146
|
+
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
|
147
|
+
{...props}
|
|
148
|
+
>
|
|
149
|
+
<ChevronUpIcon className="size-4" />
|
|
150
|
+
</SelectPrimitive.ScrollUpButton>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function SelectScrollDownButton({
|
|
155
|
+
className,
|
|
156
|
+
...props
|
|
157
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
|
158
|
+
return (
|
|
159
|
+
<SelectPrimitive.ScrollDownButton
|
|
160
|
+
data-slot="select-scroll-down-button"
|
|
161
|
+
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
|
162
|
+
{...props}
|
|
163
|
+
>
|
|
164
|
+
<ChevronDownIcon className="size-4" />
|
|
165
|
+
</SelectPrimitive.ScrollDownButton>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export {
|
|
170
|
+
Select,
|
|
171
|
+
SelectContent,
|
|
172
|
+
SelectGroup,
|
|
173
|
+
SelectItem,
|
|
174
|
+
SelectLabel,
|
|
175
|
+
SelectScrollDownButton,
|
|
176
|
+
SelectScrollUpButton,
|
|
177
|
+
SelectSeparator,
|
|
178
|
+
SelectTrigger,
|
|
179
|
+
SelectValue,
|
|
180
|
+
};
|