@m5kdev/web-ui 0.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/LICENSE +621 -0
- package/README.md +17 -0
- package/package.json +169 -0
- package/src/animations/card.motion.ts +9 -0
- package/src/components/AvatarUpload.tsx +133 -0
- package/src/components/Button.tsx +14 -0
- package/src/components/Calendar.css +684 -0
- package/src/components/Calendar.tsx +32 -0
- package/src/components/CardsSelect.tsx +155 -0
- package/src/components/CollapsibleSidebarMenuItem.tsx +57 -0
- package/src/components/ColorPicker.tsx +56 -0
- package/src/components/CopyButton.tsx +45 -0
- package/src/components/CropDialog.tsx +154 -0
- package/src/components/DialogProvider.tsx +105 -0
- package/src/components/ErrorFallback.tsx +17 -0
- package/src/components/FileDropzone.tsx +120 -0
- package/src/components/MultiSelectDropdown.tsx +233 -0
- package/src/components/Orb.tsx +288 -0
- package/src/components/PageAlert.tsx +121 -0
- package/src/components/SelectChips.tsx +40 -0
- package/src/components/SidebarItem.tsx +26 -0
- package/src/components/Steps.tsx +340 -0
- package/src/components/TablerIconPicker.tsx +4260 -0
- package/src/components/app-header.tsx +40 -0
- package/src/components/blur-card.tsx +132 -0
- package/src/components/features-section-demo-1.tsx +127 -0
- package/src/components/features-section-demo-2.tsx +102 -0
- package/src/components/features-section-demo-3.tsx +272 -0
- package/src/components/mode-toggle.tsx +31 -0
- package/src/components/nav-main.tsx +69 -0
- package/src/components/pricing-cards.tsx +133 -0
- package/src/components/shared/ButtonCopy.tsx +50 -0
- package/src/components/team-switcher.tsx +83 -0
- package/src/components/theme-provider.tsx +74 -0
- package/src/components/typewriter.tsx +90 -0
- package/src/components/ui/alert-dialog.tsx +133 -0
- package/src/components/ui/alert.tsx +60 -0
- package/src/components/ui/avatar.tsx +47 -0
- package/src/components/ui/badge.tsx +33 -0
- package/src/components/ui/bento-grid.tsx +54 -0
- package/src/components/ui/bento-grid2.tsx +66 -0
- package/src/components/ui/breadcrumb.tsx +101 -0
- package/src/components/ui/button.tsx +50 -0
- package/src/components/ui/card.tsx +55 -0
- package/src/components/ui/checkbox.tsx +26 -0
- package/src/components/ui/collapsible.tsx +9 -0
- package/src/components/ui/dialog.tsx +119 -0
- package/src/components/ui/dropdown-menu.tsx +186 -0
- package/src/components/ui/floating-navbar.tsx +78 -0
- package/src/components/ui/form.tsx +167 -0
- package/src/components/ui/image.tsx +55 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/label.tsx +19 -0
- package/src/components/ui/pagination.tsx +105 -0
- package/src/components/ui/progress.tsx +23 -0
- package/src/components/ui/resizable-navbar.tsx +260 -0
- package/src/components/ui/segment-control.tsx +143 -0
- package/src/components/ui/select.tsx +153 -0
- package/src/components/ui/separator.tsx +24 -0
- package/src/components/ui/sheet.tsx +121 -0
- package/src/components/ui/sidebar.tsx +736 -0
- package/src/components/ui/skeleton.tsx +7 -0
- package/src/components/ui/slider.tsx +23 -0
- package/src/components/ui/sonner.tsx +27 -0
- package/src/components/ui/spinner.tsx +45 -0
- package/src/components/ui/switch.tsx +27 -0
- package/src/components/ui/table.tsx +90 -0
- package/src/components/ui/tabs.tsx +52 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/timeline.tsx +95 -0
- package/src/components/ui/toast.tsx +126 -0
- package/src/components/ui/tooltip.tsx +55 -0
- package/src/components/ui/typewriter-effect.tsx +181 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/hooks/useDialog.ts +25 -0
- package/src/icons/GoogleIcon.tsx +32 -0
- package/src/icons/LinkedInIcon.tsx +30 -0
- package/src/icons/MicrosoftIcon.tsx +21 -0
- package/src/lib/chatwoot.ts +51 -0
- package/src/lib/utils.ts +6 -0
- package/src/modules/app/components/AppLoader.tsx +9 -0
- package/src/modules/app/components/AppShell.tsx +21 -0
- package/src/modules/app/components/AppSidebar.tsx +26 -0
- package/src/modules/app/components/AppSidebarContent.tsx +73 -0
- package/src/modules/app/components/AppSidebarHeader.tsx +57 -0
- package/src/modules/app/components/AppSidebarInvites.tsx +32 -0
- package/src/modules/app/components/AppSidebarUser.tsx +128 -0
- package/src/modules/auth/components/AdminUserManagement.tsx +1136 -0
- package/src/modules/auth/components/AdminWaitlist.tsx +358 -0
- package/src/modules/auth/components/AuthLayout.tsx +13 -0
- package/src/modules/auth/components/AuthProviders.tsx +105 -0
- package/src/modules/auth/components/AuthRouter.tsx +29 -0
- package/src/modules/auth/components/ClaimAccountRoute.tsx +242 -0
- package/src/modules/auth/components/ErrorAuthRoute.tsx +121 -0
- package/src/modules/auth/components/ForgotPasswordForm.tsx +58 -0
- package/src/modules/auth/components/ForgotPasswordRoute.tsx +27 -0
- package/src/modules/auth/components/InviteFriends.tsx +273 -0
- package/src/modules/auth/components/LastUsedBadge.tsx +22 -0
- package/src/modules/auth/components/LoginForm.tsx +104 -0
- package/src/modules/auth/components/LoginRoute.tsx +31 -0
- package/src/modules/auth/components/LogoutRoute.tsx +21 -0
- package/src/modules/auth/components/OrganizationAcceptInvitationRoute.tsx +161 -0
- package/src/modules/auth/components/OrganizationMembersRoute.tsx +730 -0
- package/src/modules/auth/components/OrganizationSettingsRoute.tsx +280 -0
- package/src/modules/auth/components/OrganizationSwitcher.tsx +148 -0
- package/src/modules/auth/components/ProfileRoute.tsx +104 -0
- package/src/modules/auth/components/RangeNuqsDatePicker.tsx +365 -0
- package/src/modules/auth/components/ResetPasswordForm.tsx +103 -0
- package/src/modules/auth/components/ResetPasswordRoute.tsx +27 -0
- package/src/modules/auth/components/SignupFormRoute.tsx +189 -0
- package/src/modules/auth/components/SignupRoute.tsx +53 -0
- package/src/modules/auth/components/UserPreferences.tsx +144 -0
- package/src/modules/auth/components/WaitlistCard.tsx +78 -0
- package/src/modules/auth/components/WaitlistCodeValidation.tsx +79 -0
- package/src/modules/billing/components/BillingBetaPage.tsx +124 -0
- package/src/modules/billing/components/BillingInvoicePage.tsx +180 -0
- package/src/modules/billing/components/BillingPlanSelect.tsx +14 -0
- package/src/modules/billing/components/BillingRouter.tsx +20 -0
- package/src/modules/billing/components/BillingSinglePlanSelect.tsx +172 -0
- package/src/modules/table/components/ColumnOrderAndVisibility.tsx +127 -0
- package/src/modules/table/components/NuqsTable.tsx +396 -0
- package/src/modules/table/components/TableFiltering.tsx +520 -0
- package/src/modules/table/components/TablePagination.tsx +59 -0
- package/src/modules/table/components/table.types.ts +11 -0
- package/src/modules/table/filterTransformers.ts +323 -0
- package/src/types.ts +4 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { MenuIcon, XIcon } from "lucide-react";
|
|
4
|
+
import { AnimatePresence, motion, useMotionValueEvent, useScroll } from "motion/react";
|
|
5
|
+
import React, { useRef, useState } from "react";
|
|
6
|
+
import { cn } from "#utils";
|
|
7
|
+
|
|
8
|
+
interface NavbarProps {
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface NavBodyProps {
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
className?: string;
|
|
16
|
+
visible?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface NavItemsProps {
|
|
20
|
+
items: {
|
|
21
|
+
name: string;
|
|
22
|
+
link: string;
|
|
23
|
+
}[];
|
|
24
|
+
className?: string;
|
|
25
|
+
onItemClick?: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface MobileNavProps {
|
|
29
|
+
children: React.ReactNode;
|
|
30
|
+
className?: string;
|
|
31
|
+
visible?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface MobileNavHeaderProps {
|
|
35
|
+
children: React.ReactNode;
|
|
36
|
+
className?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface MobileNavMenuProps {
|
|
40
|
+
children: React.ReactNode;
|
|
41
|
+
className?: string;
|
|
42
|
+
isOpen: boolean;
|
|
43
|
+
onClose: () => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const Navbar = ({ children, className }: NavbarProps) => {
|
|
47
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
48
|
+
const { scrollY } = useScroll({
|
|
49
|
+
target: ref,
|
|
50
|
+
offset: ["start start", "end start"],
|
|
51
|
+
});
|
|
52
|
+
const [visible, setVisible] = useState<boolean>(false);
|
|
53
|
+
|
|
54
|
+
useMotionValueEvent(scrollY, "change", (latest) => {
|
|
55
|
+
if (latest > 20) {
|
|
56
|
+
setVisible(true);
|
|
57
|
+
} else {
|
|
58
|
+
setVisible(false);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<motion.div
|
|
64
|
+
ref={ref}
|
|
65
|
+
// IMPORTANT: Change this to class of `fixed` if you want the navbar to be fixed
|
|
66
|
+
className={cn("fixed inset-x-0 top-8 z-100 w-full", className)}
|
|
67
|
+
>
|
|
68
|
+
{React.Children.map(children, (child) =>
|
|
69
|
+
React.isValidElement(child)
|
|
70
|
+
? React.cloneElement(child as React.ReactElement<{ visible?: boolean }>, { visible })
|
|
71
|
+
: child
|
|
72
|
+
)}
|
|
73
|
+
</motion.div>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const NavBody = ({ children, className, visible }: NavBodyProps) => {
|
|
78
|
+
return (
|
|
79
|
+
<motion.div
|
|
80
|
+
animate={{
|
|
81
|
+
backdropFilter: visible ? "blur(10px)" : "none",
|
|
82
|
+
boxShadow: visible
|
|
83
|
+
? "0 0 24px rgba(34, 42, 53, 0.06), 0 1px 1px rgba(0, 0, 0, 0.05), 0 0 0 1px rgba(34, 42, 53, 0.04), 0 0 4px rgba(34, 42, 53, 0.08), 0 16px 68px rgba(47, 48, 55, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1) inset"
|
|
84
|
+
: "none",
|
|
85
|
+
width: visible ? "40%" : "100%",
|
|
86
|
+
y: visible ? 20 : 0,
|
|
87
|
+
}}
|
|
88
|
+
transition={{
|
|
89
|
+
type: "spring",
|
|
90
|
+
stiffness: 200,
|
|
91
|
+
damping: 50,
|
|
92
|
+
}}
|
|
93
|
+
style={{
|
|
94
|
+
minWidth: "800px",
|
|
95
|
+
}}
|
|
96
|
+
className={cn(
|
|
97
|
+
"relative z-[60] mx-auto hidden w-full max-w-7xl flex-row items-center justify-between self-start rounded-full bg-transparent px-4 py-2 lg:flex dark:bg-transparent",
|
|
98
|
+
visible && "bg-white/80 dark:bg-neutral-950/80",
|
|
99
|
+
className
|
|
100
|
+
)}
|
|
101
|
+
>
|
|
102
|
+
{children}
|
|
103
|
+
</motion.div>
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const NavItems = ({ items, className, onItemClick }: NavItemsProps) => {
|
|
108
|
+
const [hovered, setHovered] = useState<number | null>(null);
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<motion.div
|
|
112
|
+
onMouseLeave={() => setHovered(null)}
|
|
113
|
+
className={cn(
|
|
114
|
+
"absolute inset-0 hidden flex-1 flex-row items-center justify-center space-x-2 text-sm font-medium text-zinc-600 transition duration-200 hover:text-zinc-800 lg:flex lg:space-x-2",
|
|
115
|
+
className
|
|
116
|
+
)}
|
|
117
|
+
>
|
|
118
|
+
{items.map((item, idx) => (
|
|
119
|
+
<a
|
|
120
|
+
onMouseEnter={() => setHovered(idx)}
|
|
121
|
+
onClick={onItemClick}
|
|
122
|
+
className="relative px-4 py-2 text-neutral-600 dark:text-neutral-300"
|
|
123
|
+
key={`link-${idx}`}
|
|
124
|
+
href={item.link}
|
|
125
|
+
>
|
|
126
|
+
{hovered === idx && (
|
|
127
|
+
<motion.div
|
|
128
|
+
layoutId="hovered"
|
|
129
|
+
className="absolute inset-0 h-full w-full rounded-full bg-gray-100 dark:bg-neutral-800"
|
|
130
|
+
/>
|
|
131
|
+
)}
|
|
132
|
+
<span className="relative z-20">{item.name}</span>
|
|
133
|
+
</a>
|
|
134
|
+
))}
|
|
135
|
+
</motion.div>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const MobileNav = ({ children, className, visible }: MobileNavProps) => {
|
|
140
|
+
return (
|
|
141
|
+
<motion.div
|
|
142
|
+
animate={{
|
|
143
|
+
backdropFilter: visible ? "blur(10px)" : "none",
|
|
144
|
+
boxShadow: visible
|
|
145
|
+
? "0 0 24px rgba(34, 42, 53, 0.06), 0 1px 1px rgba(0, 0, 0, 0.05), 0 0 0 1px rgba(34, 42, 53, 0.04), 0 0 4px rgba(34, 42, 53, 0.08), 0 16px 68px rgba(47, 48, 55, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1) inset"
|
|
146
|
+
: "none",
|
|
147
|
+
width: visible ? "90%" : "100%",
|
|
148
|
+
paddingRight: visible ? "12px" : "0px",
|
|
149
|
+
paddingLeft: visible ? "12px" : "0px",
|
|
150
|
+
borderRadius: visible ? "4px" : "2rem",
|
|
151
|
+
y: visible ? 20 : 0,
|
|
152
|
+
}}
|
|
153
|
+
transition={{
|
|
154
|
+
type: "spring",
|
|
155
|
+
stiffness: 200,
|
|
156
|
+
damping: 50,
|
|
157
|
+
}}
|
|
158
|
+
className={cn(
|
|
159
|
+
"relative z-50 mx-auto flex w-full max-w-[calc(100vw-2rem)] flex-col items-center justify-between bg-transparent px-0 py-2 lg:hidden",
|
|
160
|
+
visible && "bg-white/80 dark:bg-neutral-950/80",
|
|
161
|
+
className
|
|
162
|
+
)}
|
|
163
|
+
>
|
|
164
|
+
{children}
|
|
165
|
+
</motion.div>
|
|
166
|
+
);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export const MobileNavHeader = ({ children, className }: MobileNavHeaderProps) => {
|
|
170
|
+
return (
|
|
171
|
+
<div className={cn("flex w-full flex-row items-center justify-between", className)}>
|
|
172
|
+
{children}
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const MobileNavMenu = ({ children, className, isOpen }: MobileNavMenuProps) => {
|
|
178
|
+
return (
|
|
179
|
+
<AnimatePresence>
|
|
180
|
+
{isOpen && (
|
|
181
|
+
<motion.div
|
|
182
|
+
initial={{ opacity: 0 }}
|
|
183
|
+
animate={{ opacity: 1 }}
|
|
184
|
+
exit={{ opacity: 0 }}
|
|
185
|
+
className={cn(
|
|
186
|
+
"absolute inset-x-0 top-16 z-50 flex w-full flex-col items-start justify-start gap-4 rounded-lg bg-white px-4 py-8 shadow-[0_0_24px_rgba(34,_42,_53,_0.06),_0_1px_1px_rgba(0,_0,_0,_0.05),_0_0_0_1px_rgba(34,_42,_53,_0.04),_0_0_4px_rgba(34,_42,_53,_0.08),_0_16px_68px_rgba(47,_48,_55,_0.05),_0_1px_0_rgba(255,_255,_255,_0.1)_inset] dark:bg-neutral-950",
|
|
187
|
+
className
|
|
188
|
+
)}
|
|
189
|
+
>
|
|
190
|
+
{children}
|
|
191
|
+
</motion.div>
|
|
192
|
+
)}
|
|
193
|
+
</AnimatePresence>
|
|
194
|
+
);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export const MobileNavToggle = ({ isOpen, onClick }: { isOpen: boolean; onClick: () => void }) => {
|
|
198
|
+
return isOpen ? (
|
|
199
|
+
<XIcon className="text-black dark:text-white" onClick={onClick} />
|
|
200
|
+
) : (
|
|
201
|
+
<MenuIcon className="text-black dark:text-white" onClick={onClick} />
|
|
202
|
+
);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export const NavbarLogo = ({
|
|
206
|
+
logo = "/mk.png",
|
|
207
|
+
title = "Kowalkowski",
|
|
208
|
+
alt,
|
|
209
|
+
}: {
|
|
210
|
+
logo?: string;
|
|
211
|
+
title?: string;
|
|
212
|
+
alt?: string;
|
|
213
|
+
}) => {
|
|
214
|
+
return (
|
|
215
|
+
<a
|
|
216
|
+
href="/"
|
|
217
|
+
className="relative z-20 mr-4 flex items-center space-x-2 px-2 py-1 text-sm font-normal text-black"
|
|
218
|
+
>
|
|
219
|
+
<img src={logo} alt={alt || `${title} logo`} width={30} height={30} />
|
|
220
|
+
<span className="font-medium text-black dark:text-white">{title}</span>
|
|
221
|
+
</a>
|
|
222
|
+
);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
export const NavbarButton = ({
|
|
226
|
+
href,
|
|
227
|
+
as: Tag = "a",
|
|
228
|
+
children,
|
|
229
|
+
className,
|
|
230
|
+
variant = "primary",
|
|
231
|
+
...props
|
|
232
|
+
}: {
|
|
233
|
+
href?: string;
|
|
234
|
+
as?: React.ElementType;
|
|
235
|
+
children: React.ReactNode;
|
|
236
|
+
className?: string;
|
|
237
|
+
variant?: "primary" | "secondary" | "dark" | "gradient";
|
|
238
|
+
} & (React.ComponentPropsWithoutRef<"a"> | React.ComponentPropsWithoutRef<"button">)) => {
|
|
239
|
+
const baseStyles =
|
|
240
|
+
"px-4 py-2 rounded-md bg-white button bg-white text-black text-sm font-bold relative cursor-pointer hover:-translate-y-0.5 transition duration-200 inline-block text-center";
|
|
241
|
+
|
|
242
|
+
const variantStyles = {
|
|
243
|
+
primary:
|
|
244
|
+
"shadow-[0_0_24px_rgba(34,_42,_53,_0.06),_0_1px_1px_rgba(0,_0,_0,_0.05),_0_0_0_1px_rgba(34,_42,_53,_0.04),_0_0_4px_rgba(34,_42,_53,_0.08),_0_16px_68px_rgba(47,_48,_55,_0.05),_0_1px_0_rgba(255,_255,_255,_0.1)_inset]",
|
|
245
|
+
secondary: "bg-transparent shadow-none dark:text-white",
|
|
246
|
+
dark: "bg-black text-white shadow-[0_0_24px_rgba(34,_42,_53,_0.06),_0_1px_1px_rgba(0,_0,_0,_0.05),_0_0_0_1px_rgba(34,_42,_53,_0.04),_0_0_4px_rgba(34,_42,_53,_0.08),_0_16px_68px_rgba(47,_48,_55,_0.05),_0_1px_0_rgba(255,_255,_255,_0.1)_inset]",
|
|
247
|
+
gradient:
|
|
248
|
+
"bg-gradient-to-b from-blue-500 to-blue-700 text-white shadow-[0px_2px_0px_0px_rgba(255,255,255,0.3)_inset]",
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
return (
|
|
252
|
+
<Tag
|
|
253
|
+
href={href || undefined}
|
|
254
|
+
className={cn(baseStyles, variantStyles[variant], className)}
|
|
255
|
+
{...props}
|
|
256
|
+
>
|
|
257
|
+
{children}
|
|
258
|
+
</Tag>
|
|
259
|
+
);
|
|
260
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { IconChevronDown } from "@tabler/icons-react";
|
|
2
|
+
import { useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { Button } from "#components/ui/button";
|
|
4
|
+
import {
|
|
5
|
+
DropdownMenu,
|
|
6
|
+
DropdownMenuContent,
|
|
7
|
+
DropdownMenuItem,
|
|
8
|
+
DropdownMenuTrigger,
|
|
9
|
+
} from "#components/ui/dropdown-menu";
|
|
10
|
+
|
|
11
|
+
interface SegmentControlProps {
|
|
12
|
+
options: string[];
|
|
13
|
+
value: string | null;
|
|
14
|
+
onChange: (value: string | null) => void;
|
|
15
|
+
className?: string;
|
|
16
|
+
resetButton: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const SegmentDropdown = ({ options, value, resetButton, onChange }: SegmentControlProps) => {
|
|
20
|
+
const [triggerWidth, setTriggerWidth] = useState<number>();
|
|
21
|
+
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
22
|
+
const [open, setOpen] = useState(false);
|
|
23
|
+
|
|
24
|
+
useLayoutEffect(() => {
|
|
25
|
+
if (triggerRef.current) {
|
|
26
|
+
console.log(triggerRef.current.offsetWidth);
|
|
27
|
+
setTriggerWidth(triggerRef.current.offsetWidth);
|
|
28
|
+
}
|
|
29
|
+
}, []);
|
|
30
|
+
return (
|
|
31
|
+
<DropdownMenu open={open} onOpenChange={setOpen}>
|
|
32
|
+
<DropdownMenuTrigger asChild ref={triggerRef}>
|
|
33
|
+
<Button className="w-full justify-between rounded-full border border-muted bg-muted/70 px-4 py-2 text-sm font-medium text-white">
|
|
34
|
+
{value ?? resetButton ?? options[0]}
|
|
35
|
+
<IconChevronDown className="ml-2 h-4 w-4 opacity-60" />
|
|
36
|
+
</Button>
|
|
37
|
+
</DropdownMenuTrigger>
|
|
38
|
+
<DropdownMenuContent
|
|
39
|
+
style={{ width: triggerWidth }}
|
|
40
|
+
onInteractOutside={(e) => {
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
setOpen(false);
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
{resetButton && (
|
|
46
|
+
<DropdownMenuItem
|
|
47
|
+
onSelect={(e) => {
|
|
48
|
+
e.preventDefault();
|
|
49
|
+
onChange(null);
|
|
50
|
+
setOpen(false);
|
|
51
|
+
}}
|
|
52
|
+
className={value === null ? "bg-primary text-primary-foreground" : ""}
|
|
53
|
+
>
|
|
54
|
+
{resetButton}
|
|
55
|
+
</DropdownMenuItem>
|
|
56
|
+
)}
|
|
57
|
+
{options.map((option) => (
|
|
58
|
+
<DropdownMenuItem
|
|
59
|
+
key={option}
|
|
60
|
+
onSelect={(e) => {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
onChange(option);
|
|
63
|
+
setOpen(false);
|
|
64
|
+
}}
|
|
65
|
+
className={value === option ? "bg-primary text-primary-foreground" : ""}
|
|
66
|
+
>
|
|
67
|
+
{option}
|
|
68
|
+
</DropdownMenuItem>
|
|
69
|
+
))}
|
|
70
|
+
</DropdownMenuContent>
|
|
71
|
+
</DropdownMenu>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const SegmentControl: React.FC<SegmentControlProps> = ({
|
|
76
|
+
options,
|
|
77
|
+
value,
|
|
78
|
+
onChange,
|
|
79
|
+
className = "",
|
|
80
|
+
resetButton = false,
|
|
81
|
+
}) => {
|
|
82
|
+
// Dropdown for mobile (below sm)
|
|
83
|
+
return (
|
|
84
|
+
<>
|
|
85
|
+
{/* Mobile: Dropdown */}
|
|
86
|
+
<div className={"block sm:hidden w-full " + className}>
|
|
87
|
+
<SegmentDropdown
|
|
88
|
+
options={options}
|
|
89
|
+
value={value}
|
|
90
|
+
resetButton={resetButton}
|
|
91
|
+
onChange={onChange}
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
{/* Desktop: Segmented buttons */}
|
|
95
|
+
<fieldset
|
|
96
|
+
className={
|
|
97
|
+
"hidden sm:inline-flex flex-wrap items-center justify-center gap-1 sm:gap-2 bg-muted/70 rounded-full px-2 py-1.5 sm:px-4 sm:py-2 border-0 " +
|
|
98
|
+
className
|
|
99
|
+
}
|
|
100
|
+
>
|
|
101
|
+
{options.map((option) => {
|
|
102
|
+
const selected = value === option;
|
|
103
|
+
return (
|
|
104
|
+
<Button
|
|
105
|
+
key={option}
|
|
106
|
+
type="button"
|
|
107
|
+
variant="ghost"
|
|
108
|
+
onClick={() => onChange(option)}
|
|
109
|
+
className={
|
|
110
|
+
"transition px-2 py-1.5 sm:px-4 sm:py-2 rounded-full text-xs sm:text-sm font-medium min-w-[60px] sm:min-w-[80px] " +
|
|
111
|
+
(selected
|
|
112
|
+
? "bg-primary text-primary-foreground shadow hover:bg-primary hover:text-primary-foreground"
|
|
113
|
+
: "bg-transparent text-muted-foreground hover:bg-muted hover:text-foreground hover:shadow")
|
|
114
|
+
}
|
|
115
|
+
aria-pressed={selected}
|
|
116
|
+
>
|
|
117
|
+
{option}
|
|
118
|
+
</Button>
|
|
119
|
+
);
|
|
120
|
+
})}
|
|
121
|
+
{resetButton && (
|
|
122
|
+
<>
|
|
123
|
+
<div className="border-l border-muted-foreground/20 h-4 sm:h-6 mx-1 sm:mx-2" />
|
|
124
|
+
<Button
|
|
125
|
+
type="button"
|
|
126
|
+
variant="ghost"
|
|
127
|
+
onClick={() => onChange(null)}
|
|
128
|
+
className={
|
|
129
|
+
"transition px-2 py-1.5 sm:px-4 sm:py-2 rounded-full text-xs sm:text-sm font-medium min-w-[60px] sm:min-w-[80px] " +
|
|
130
|
+
(value === null
|
|
131
|
+
? "bg-primary text-primary-foreground shadow hover:bg-primary hover:text-primary-foreground"
|
|
132
|
+
: "bg-transparent text-muted-foreground hover:bg-muted hover:text-foreground hover:shadow")
|
|
133
|
+
}
|
|
134
|
+
aria-label="Reset"
|
|
135
|
+
>
|
|
136
|
+
{resetButton}
|
|
137
|
+
</Button>
|
|
138
|
+
</>
|
|
139
|
+
)}
|
|
140
|
+
</fieldset>
|
|
141
|
+
</>
|
|
142
|
+
);
|
|
143
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
4
|
+
import { Check, ChevronDown, ChevronUp } from "lucide-react";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
|
|
7
|
+
import { cn } from "#utils";
|
|
8
|
+
|
|
9
|
+
const Select = SelectPrimitive.Root;
|
|
10
|
+
|
|
11
|
+
const SelectGroup = SelectPrimitive.Group;
|
|
12
|
+
|
|
13
|
+
const SelectValue = SelectPrimitive.Value;
|
|
14
|
+
|
|
15
|
+
const SelectTrigger = React.forwardRef<
|
|
16
|
+
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
|
17
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
|
18
|
+
>(({ className, children, ...props }, ref) => (
|
|
19
|
+
<SelectPrimitive.Trigger
|
|
20
|
+
ref={ref}
|
|
21
|
+
className={cn(
|
|
22
|
+
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
|
23
|
+
className
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
>
|
|
27
|
+
{children}
|
|
28
|
+
<SelectPrimitive.Icon asChild>
|
|
29
|
+
<ChevronDown className="h-4 w-4 opacity-50" />
|
|
30
|
+
</SelectPrimitive.Icon>
|
|
31
|
+
</SelectPrimitive.Trigger>
|
|
32
|
+
));
|
|
33
|
+
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
|
34
|
+
|
|
35
|
+
const SelectScrollUpButton = React.forwardRef<
|
|
36
|
+
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
|
37
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
|
38
|
+
>(({ className, ...props }, ref) => (
|
|
39
|
+
<SelectPrimitive.ScrollUpButton
|
|
40
|
+
ref={ref}
|
|
41
|
+
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
<ChevronUp className="h-4 w-4" />
|
|
45
|
+
</SelectPrimitive.ScrollUpButton>
|
|
46
|
+
));
|
|
47
|
+
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
|
|
48
|
+
|
|
49
|
+
const SelectScrollDownButton = React.forwardRef<
|
|
50
|
+
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
|
51
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
|
52
|
+
>(({ className, ...props }, ref) => (
|
|
53
|
+
<SelectPrimitive.ScrollDownButton
|
|
54
|
+
ref={ref}
|
|
55
|
+
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
|
56
|
+
{...props}
|
|
57
|
+
>
|
|
58
|
+
<ChevronDown className="h-4 w-4" />
|
|
59
|
+
</SelectPrimitive.ScrollDownButton>
|
|
60
|
+
));
|
|
61
|
+
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
|
|
62
|
+
|
|
63
|
+
const SelectContent = React.forwardRef<
|
|
64
|
+
React.ElementRef<typeof SelectPrimitive.Content>,
|
|
65
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
|
66
|
+
>(({ className, children, position = "popper", ...props }, ref) => (
|
|
67
|
+
<SelectPrimitive.Portal>
|
|
68
|
+
<SelectPrimitive.Content
|
|
69
|
+
ref={ref}
|
|
70
|
+
className={cn(
|
|
71
|
+
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md 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",
|
|
72
|
+
position === "popper" &&
|
|
73
|
+
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
74
|
+
className
|
|
75
|
+
)}
|
|
76
|
+
position={position}
|
|
77
|
+
{...props}
|
|
78
|
+
>
|
|
79
|
+
<SelectScrollUpButton />
|
|
80
|
+
<SelectPrimitive.Viewport
|
|
81
|
+
className={cn(
|
|
82
|
+
"p-1",
|
|
83
|
+
position === "popper" &&
|
|
84
|
+
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
|
85
|
+
)}
|
|
86
|
+
>
|
|
87
|
+
{children}
|
|
88
|
+
</SelectPrimitive.Viewport>
|
|
89
|
+
<SelectScrollDownButton />
|
|
90
|
+
</SelectPrimitive.Content>
|
|
91
|
+
</SelectPrimitive.Portal>
|
|
92
|
+
));
|
|
93
|
+
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
|
94
|
+
|
|
95
|
+
const SelectLabel = React.forwardRef<
|
|
96
|
+
React.ElementRef<typeof SelectPrimitive.Label>,
|
|
97
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
|
98
|
+
>(({ className, ...props }, ref) => (
|
|
99
|
+
<SelectPrimitive.Label
|
|
100
|
+
ref={ref}
|
|
101
|
+
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
));
|
|
105
|
+
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
|
106
|
+
|
|
107
|
+
const SelectItem = React.forwardRef<
|
|
108
|
+
React.ElementRef<typeof SelectPrimitive.Item>,
|
|
109
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
|
110
|
+
>(({ className, children, ...props }, ref) => (
|
|
111
|
+
<SelectPrimitive.Item
|
|
112
|
+
ref={ref}
|
|
113
|
+
className={cn(
|
|
114
|
+
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
115
|
+
className
|
|
116
|
+
)}
|
|
117
|
+
{...props}
|
|
118
|
+
>
|
|
119
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
120
|
+
<SelectPrimitive.ItemIndicator>
|
|
121
|
+
<Check className="h-4 w-4" />
|
|
122
|
+
</SelectPrimitive.ItemIndicator>
|
|
123
|
+
</span>
|
|
124
|
+
|
|
125
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
126
|
+
</SelectPrimitive.Item>
|
|
127
|
+
));
|
|
128
|
+
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
|
129
|
+
|
|
130
|
+
const SelectSeparator = React.forwardRef<
|
|
131
|
+
React.ElementRef<typeof SelectPrimitive.Separator>,
|
|
132
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
|
133
|
+
>(({ className, ...props }, ref) => (
|
|
134
|
+
<SelectPrimitive.Separator
|
|
135
|
+
ref={ref}
|
|
136
|
+
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
|
137
|
+
{...props}
|
|
138
|
+
/>
|
|
139
|
+
));
|
|
140
|
+
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
|
141
|
+
|
|
142
|
+
export {
|
|
143
|
+
Select,
|
|
144
|
+
SelectGroup,
|
|
145
|
+
SelectValue,
|
|
146
|
+
SelectTrigger,
|
|
147
|
+
SelectContent,
|
|
148
|
+
SelectLabel,
|
|
149
|
+
SelectItem,
|
|
150
|
+
SelectSeparator,
|
|
151
|
+
SelectScrollUpButton,
|
|
152
|
+
SelectScrollDownButton,
|
|
153
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "#utils";
|
|
5
|
+
|
|
6
|
+
const Separator = React.forwardRef<
|
|
7
|
+
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
|
8
|
+
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
|
9
|
+
>(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
|
|
10
|
+
<SeparatorPrimitive.Root
|
|
11
|
+
ref={ref}
|
|
12
|
+
decorative={decorative}
|
|
13
|
+
orientation={orientation}
|
|
14
|
+
className={cn(
|
|
15
|
+
"shrink-0 bg-border",
|
|
16
|
+
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
));
|
|
22
|
+
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
|
23
|
+
|
|
24
|
+
export { Separator };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
|
+
import { X } from "lucide-react";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
|
|
8
|
+
import { cn } from "#utils";
|
|
9
|
+
|
|
10
|
+
const Sheet = SheetPrimitive.Root;
|
|
11
|
+
|
|
12
|
+
const SheetTrigger = SheetPrimitive.Trigger;
|
|
13
|
+
|
|
14
|
+
const SheetClose = SheetPrimitive.Close;
|
|
15
|
+
|
|
16
|
+
const SheetPortal = SheetPrimitive.Portal;
|
|
17
|
+
|
|
18
|
+
const SheetOverlay = React.forwardRef<
|
|
19
|
+
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
|
20
|
+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
|
21
|
+
>(({ className, ...props }, ref) => (
|
|
22
|
+
<SheetPrimitive.Overlay
|
|
23
|
+
className={cn(
|
|
24
|
+
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
25
|
+
className
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
ref={ref}
|
|
29
|
+
/>
|
|
30
|
+
));
|
|
31
|
+
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
|
32
|
+
|
|
33
|
+
const sheetVariants = cva(
|
|
34
|
+
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
35
|
+
{
|
|
36
|
+
variants: {
|
|
37
|
+
side: {
|
|
38
|
+
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
|
39
|
+
bottom:
|
|
40
|
+
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
|
41
|
+
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
|
42
|
+
right:
|
|
43
|
+
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
defaultVariants: {
|
|
47
|
+
side: "right",
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
interface SheetContentProps
|
|
53
|
+
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
|
54
|
+
VariantProps<typeof sheetVariants> {}
|
|
55
|
+
|
|
56
|
+
const SheetContent = React.forwardRef<
|
|
57
|
+
React.ElementRef<typeof SheetPrimitive.Content>,
|
|
58
|
+
SheetContentProps
|
|
59
|
+
>(({ side = "right", className, children, ...props }, ref) => (
|
|
60
|
+
<SheetPortal>
|
|
61
|
+
<SheetOverlay />
|
|
62
|
+
<SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>
|
|
63
|
+
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
|
64
|
+
<X className="h-4 w-4" />
|
|
65
|
+
<span className="sr-only">Close</span>
|
|
66
|
+
</SheetPrimitive.Close>
|
|
67
|
+
{children}
|
|
68
|
+
</SheetPrimitive.Content>
|
|
69
|
+
</SheetPortal>
|
|
70
|
+
));
|
|
71
|
+
SheetContent.displayName = SheetPrimitive.Content.displayName;
|
|
72
|
+
|
|
73
|
+
const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
74
|
+
<div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
|
|
75
|
+
);
|
|
76
|
+
SheetHeader.displayName = "SheetHeader";
|
|
77
|
+
|
|
78
|
+
const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
79
|
+
<div
|
|
80
|
+
className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
SheetFooter.displayName = "SheetFooter";
|
|
85
|
+
|
|
86
|
+
const SheetTitle = React.forwardRef<
|
|
87
|
+
React.ElementRef<typeof SheetPrimitive.Title>,
|
|
88
|
+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
|
89
|
+
>(({ className, ...props }, ref) => (
|
|
90
|
+
<SheetPrimitive.Title
|
|
91
|
+
ref={ref}
|
|
92
|
+
className={cn("text-lg font-semibold text-foreground", className)}
|
|
93
|
+
{...props}
|
|
94
|
+
/>
|
|
95
|
+
));
|
|
96
|
+
SheetTitle.displayName = SheetPrimitive.Title.displayName;
|
|
97
|
+
|
|
98
|
+
const SheetDescription = React.forwardRef<
|
|
99
|
+
React.ElementRef<typeof SheetPrimitive.Description>,
|
|
100
|
+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
|
101
|
+
>(({ className, ...props }, ref) => (
|
|
102
|
+
<SheetPrimitive.Description
|
|
103
|
+
ref={ref}
|
|
104
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
105
|
+
{...props}
|
|
106
|
+
/>
|
|
107
|
+
));
|
|
108
|
+
SheetDescription.displayName = SheetPrimitive.Description.displayName;
|
|
109
|
+
|
|
110
|
+
export {
|
|
111
|
+
Sheet,
|
|
112
|
+
SheetPortal,
|
|
113
|
+
SheetOverlay,
|
|
114
|
+
SheetTrigger,
|
|
115
|
+
SheetClose,
|
|
116
|
+
SheetContent,
|
|
117
|
+
SheetHeader,
|
|
118
|
+
SheetFooter,
|
|
119
|
+
SheetTitle,
|
|
120
|
+
SheetDescription,
|
|
121
|
+
};
|