@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.
Files changed (127) hide show
  1. package/LICENSE +621 -0
  2. package/README.md +17 -0
  3. package/package.json +169 -0
  4. package/src/animations/card.motion.ts +9 -0
  5. package/src/components/AvatarUpload.tsx +133 -0
  6. package/src/components/Button.tsx +14 -0
  7. package/src/components/Calendar.css +684 -0
  8. package/src/components/Calendar.tsx +32 -0
  9. package/src/components/CardsSelect.tsx +155 -0
  10. package/src/components/CollapsibleSidebarMenuItem.tsx +57 -0
  11. package/src/components/ColorPicker.tsx +56 -0
  12. package/src/components/CopyButton.tsx +45 -0
  13. package/src/components/CropDialog.tsx +154 -0
  14. package/src/components/DialogProvider.tsx +105 -0
  15. package/src/components/ErrorFallback.tsx +17 -0
  16. package/src/components/FileDropzone.tsx +120 -0
  17. package/src/components/MultiSelectDropdown.tsx +233 -0
  18. package/src/components/Orb.tsx +288 -0
  19. package/src/components/PageAlert.tsx +121 -0
  20. package/src/components/SelectChips.tsx +40 -0
  21. package/src/components/SidebarItem.tsx +26 -0
  22. package/src/components/Steps.tsx +340 -0
  23. package/src/components/TablerIconPicker.tsx +4260 -0
  24. package/src/components/app-header.tsx +40 -0
  25. package/src/components/blur-card.tsx +132 -0
  26. package/src/components/features-section-demo-1.tsx +127 -0
  27. package/src/components/features-section-demo-2.tsx +102 -0
  28. package/src/components/features-section-demo-3.tsx +272 -0
  29. package/src/components/mode-toggle.tsx +31 -0
  30. package/src/components/nav-main.tsx +69 -0
  31. package/src/components/pricing-cards.tsx +133 -0
  32. package/src/components/shared/ButtonCopy.tsx +50 -0
  33. package/src/components/team-switcher.tsx +83 -0
  34. package/src/components/theme-provider.tsx +74 -0
  35. package/src/components/typewriter.tsx +90 -0
  36. package/src/components/ui/alert-dialog.tsx +133 -0
  37. package/src/components/ui/alert.tsx +60 -0
  38. package/src/components/ui/avatar.tsx +47 -0
  39. package/src/components/ui/badge.tsx +33 -0
  40. package/src/components/ui/bento-grid.tsx +54 -0
  41. package/src/components/ui/bento-grid2.tsx +66 -0
  42. package/src/components/ui/breadcrumb.tsx +101 -0
  43. package/src/components/ui/button.tsx +50 -0
  44. package/src/components/ui/card.tsx +55 -0
  45. package/src/components/ui/checkbox.tsx +26 -0
  46. package/src/components/ui/collapsible.tsx +9 -0
  47. package/src/components/ui/dialog.tsx +119 -0
  48. package/src/components/ui/dropdown-menu.tsx +186 -0
  49. package/src/components/ui/floating-navbar.tsx +78 -0
  50. package/src/components/ui/form.tsx +167 -0
  51. package/src/components/ui/image.tsx +55 -0
  52. package/src/components/ui/input.tsx +22 -0
  53. package/src/components/ui/label.tsx +19 -0
  54. package/src/components/ui/pagination.tsx +105 -0
  55. package/src/components/ui/progress.tsx +23 -0
  56. package/src/components/ui/resizable-navbar.tsx +260 -0
  57. package/src/components/ui/segment-control.tsx +143 -0
  58. package/src/components/ui/select.tsx +153 -0
  59. package/src/components/ui/separator.tsx +24 -0
  60. package/src/components/ui/sheet.tsx +121 -0
  61. package/src/components/ui/sidebar.tsx +736 -0
  62. package/src/components/ui/skeleton.tsx +7 -0
  63. package/src/components/ui/slider.tsx +23 -0
  64. package/src/components/ui/sonner.tsx +27 -0
  65. package/src/components/ui/spinner.tsx +45 -0
  66. package/src/components/ui/switch.tsx +27 -0
  67. package/src/components/ui/table.tsx +90 -0
  68. package/src/components/ui/tabs.tsx +52 -0
  69. package/src/components/ui/textarea.tsx +18 -0
  70. package/src/components/ui/timeline.tsx +95 -0
  71. package/src/components/ui/toast.tsx +126 -0
  72. package/src/components/ui/tooltip.tsx +55 -0
  73. package/src/components/ui/typewriter-effect.tsx +181 -0
  74. package/src/hooks/use-mobile.ts +19 -0
  75. package/src/hooks/useDialog.ts +25 -0
  76. package/src/icons/GoogleIcon.tsx +32 -0
  77. package/src/icons/LinkedInIcon.tsx +30 -0
  78. package/src/icons/MicrosoftIcon.tsx +21 -0
  79. package/src/lib/chatwoot.ts +51 -0
  80. package/src/lib/utils.ts +6 -0
  81. package/src/modules/app/components/AppLoader.tsx +9 -0
  82. package/src/modules/app/components/AppShell.tsx +21 -0
  83. package/src/modules/app/components/AppSidebar.tsx +26 -0
  84. package/src/modules/app/components/AppSidebarContent.tsx +73 -0
  85. package/src/modules/app/components/AppSidebarHeader.tsx +57 -0
  86. package/src/modules/app/components/AppSidebarInvites.tsx +32 -0
  87. package/src/modules/app/components/AppSidebarUser.tsx +128 -0
  88. package/src/modules/auth/components/AdminUserManagement.tsx +1136 -0
  89. package/src/modules/auth/components/AdminWaitlist.tsx +358 -0
  90. package/src/modules/auth/components/AuthLayout.tsx +13 -0
  91. package/src/modules/auth/components/AuthProviders.tsx +105 -0
  92. package/src/modules/auth/components/AuthRouter.tsx +29 -0
  93. package/src/modules/auth/components/ClaimAccountRoute.tsx +242 -0
  94. package/src/modules/auth/components/ErrorAuthRoute.tsx +121 -0
  95. package/src/modules/auth/components/ForgotPasswordForm.tsx +58 -0
  96. package/src/modules/auth/components/ForgotPasswordRoute.tsx +27 -0
  97. package/src/modules/auth/components/InviteFriends.tsx +273 -0
  98. package/src/modules/auth/components/LastUsedBadge.tsx +22 -0
  99. package/src/modules/auth/components/LoginForm.tsx +104 -0
  100. package/src/modules/auth/components/LoginRoute.tsx +31 -0
  101. package/src/modules/auth/components/LogoutRoute.tsx +21 -0
  102. package/src/modules/auth/components/OrganizationAcceptInvitationRoute.tsx +161 -0
  103. package/src/modules/auth/components/OrganizationMembersRoute.tsx +730 -0
  104. package/src/modules/auth/components/OrganizationSettingsRoute.tsx +280 -0
  105. package/src/modules/auth/components/OrganizationSwitcher.tsx +148 -0
  106. package/src/modules/auth/components/ProfileRoute.tsx +104 -0
  107. package/src/modules/auth/components/RangeNuqsDatePicker.tsx +365 -0
  108. package/src/modules/auth/components/ResetPasswordForm.tsx +103 -0
  109. package/src/modules/auth/components/ResetPasswordRoute.tsx +27 -0
  110. package/src/modules/auth/components/SignupFormRoute.tsx +189 -0
  111. package/src/modules/auth/components/SignupRoute.tsx +53 -0
  112. package/src/modules/auth/components/UserPreferences.tsx +144 -0
  113. package/src/modules/auth/components/WaitlistCard.tsx +78 -0
  114. package/src/modules/auth/components/WaitlistCodeValidation.tsx +79 -0
  115. package/src/modules/billing/components/BillingBetaPage.tsx +124 -0
  116. package/src/modules/billing/components/BillingInvoicePage.tsx +180 -0
  117. package/src/modules/billing/components/BillingPlanSelect.tsx +14 -0
  118. package/src/modules/billing/components/BillingRouter.tsx +20 -0
  119. package/src/modules/billing/components/BillingSinglePlanSelect.tsx +172 -0
  120. package/src/modules/table/components/ColumnOrderAndVisibility.tsx +127 -0
  121. package/src/modules/table/components/NuqsTable.tsx +396 -0
  122. package/src/modules/table/components/TableFiltering.tsx +520 -0
  123. package/src/modules/table/components/TablePagination.tsx +59 -0
  124. package/src/modules/table/components/table.types.ts +11 -0
  125. package/src/modules/table/filterTransformers.ts +323 -0
  126. package/src/types.ts +4 -0
  127. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,186 @@
1
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
2
+ import { Check, ChevronRight, Circle } from "lucide-react";
3
+ import * as React from "react";
4
+
5
+ import { cn } from "#utils";
6
+
7
+ const DropdownMenu = DropdownMenuPrimitive.Root;
8
+
9
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
10
+
11
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group;
12
+
13
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
14
+
15
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub;
16
+
17
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
18
+
19
+ const DropdownMenuSubTrigger = React.forwardRef<
20
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
21
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
22
+ inset?: boolean;
23
+ }
24
+ >(({ className, inset, children, ...props }, ref) => (
25
+ <DropdownMenuPrimitive.SubTrigger
26
+ ref={ref}
27
+ className={cn(
28
+ "flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
29
+ inset && "pl-8",
30
+ className
31
+ )}
32
+ {...props}
33
+ >
34
+ {children}
35
+ <ChevronRight className="ml-auto" />
36
+ </DropdownMenuPrimitive.SubTrigger>
37
+ ));
38
+ DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
39
+
40
+ const DropdownMenuSubContent = React.forwardRef<
41
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
42
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
43
+ >(({ className, ...props }, ref) => (
44
+ <DropdownMenuPrimitive.SubContent
45
+ ref={ref}
46
+ className={cn(
47
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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",
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+ ));
53
+ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
54
+
55
+ const DropdownMenuContent = React.forwardRef<
56
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
57
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
58
+ >(({ className, sideOffset = 4, ...props }, ref) => (
59
+ <DropdownMenuPrimitive.Portal>
60
+ <DropdownMenuPrimitive.Content
61
+ ref={ref}
62
+ sideOffset={sideOffset}
63
+ className={cn(
64
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
65
+ "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",
66
+ className
67
+ )}
68
+ {...props}
69
+ />
70
+ </DropdownMenuPrimitive.Portal>
71
+ ));
72
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
73
+
74
+ const DropdownMenuItem = React.forwardRef<
75
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
76
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
77
+ inset?: boolean;
78
+ }
79
+ >(({ className, inset, ...props }, ref) => (
80
+ <DropdownMenuPrimitive.Item
81
+ ref={ref}
82
+ className={cn(
83
+ "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
84
+ inset && "pl-8",
85
+ className
86
+ )}
87
+ {...props}
88
+ />
89
+ ));
90
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
91
+
92
+ const DropdownMenuCheckboxItem = React.forwardRef<
93
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
94
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
95
+ >(({ className, children, checked, ...props }, ref) => (
96
+ <DropdownMenuPrimitive.CheckboxItem
97
+ ref={ref}
98
+ className={cn(
99
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
100
+ className
101
+ )}
102
+ checked={checked}
103
+ {...props}
104
+ >
105
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
106
+ <DropdownMenuPrimitive.ItemIndicator>
107
+ <Check className="h-4 w-4" />
108
+ </DropdownMenuPrimitive.ItemIndicator>
109
+ </span>
110
+ {children}
111
+ </DropdownMenuPrimitive.CheckboxItem>
112
+ ));
113
+ DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
114
+
115
+ const DropdownMenuRadioItem = React.forwardRef<
116
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
117
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
118
+ >(({ className, children, ...props }, ref) => (
119
+ <DropdownMenuPrimitive.RadioItem
120
+ ref={ref}
121
+ className={cn(
122
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
123
+ className
124
+ )}
125
+ {...props}
126
+ >
127
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
128
+ <DropdownMenuPrimitive.ItemIndicator>
129
+ <Circle className="h-2 w-2 fill-current" />
130
+ </DropdownMenuPrimitive.ItemIndicator>
131
+ </span>
132
+ {children}
133
+ </DropdownMenuPrimitive.RadioItem>
134
+ ));
135
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
136
+
137
+ const DropdownMenuLabel = React.forwardRef<
138
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
139
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
140
+ inset?: boolean;
141
+ }
142
+ >(({ className, inset, ...props }, ref) => (
143
+ <DropdownMenuPrimitive.Label
144
+ ref={ref}
145
+ className={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
146
+ {...props}
147
+ />
148
+ ));
149
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
150
+
151
+ const DropdownMenuSeparator = React.forwardRef<
152
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
153
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
154
+ >(({ className, ...props }, ref) => (
155
+ <DropdownMenuPrimitive.Separator
156
+ ref={ref}
157
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
158
+ {...props}
159
+ />
160
+ ));
161
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
162
+
163
+ const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
164
+ return (
165
+ <span className={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...props} />
166
+ );
167
+ };
168
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
169
+
170
+ export {
171
+ DropdownMenu,
172
+ DropdownMenuTrigger,
173
+ DropdownMenuContent,
174
+ DropdownMenuItem,
175
+ DropdownMenuCheckboxItem,
176
+ DropdownMenuRadioItem,
177
+ DropdownMenuLabel,
178
+ DropdownMenuSeparator,
179
+ DropdownMenuShortcut,
180
+ DropdownMenuGroup,
181
+ DropdownMenuPortal,
182
+ DropdownMenuSub,
183
+ DropdownMenuSubContent,
184
+ DropdownMenuSubTrigger,
185
+ DropdownMenuRadioGroup,
186
+ };
@@ -0,0 +1,78 @@
1
+ "use client";
2
+ import { AnimatePresence, motion, useMotionValueEvent, useScroll } from "motion/react";
3
+ import type React from "react";
4
+ import { useState } from "react";
5
+ import { cn } from "#utils";
6
+
7
+ export const FloatingNav = ({
8
+ navItems,
9
+ className,
10
+ }: {
11
+ navItems: {
12
+ name: string;
13
+ link: string;
14
+ icon?: React.ReactNode;
15
+ }[];
16
+ className?: string;
17
+ }) => {
18
+ const { scrollYProgress } = useScroll();
19
+
20
+ const [visible, setVisible] = useState(true);
21
+
22
+ useMotionValueEvent(scrollYProgress, "change", (current) => {
23
+ // Check if current is not undefined and is a number
24
+ if (typeof current === "number") {
25
+ const direction = current! - scrollYProgress.getPrevious()!;
26
+
27
+ if (scrollYProgress.get() < 0.05) {
28
+ setVisible(false);
29
+ } else {
30
+ if (direction < 0) {
31
+ setVisible(true);
32
+ } else if (current === 1) {
33
+ setVisible(true);
34
+ } else {
35
+ setVisible(false);
36
+ }
37
+ }
38
+ }
39
+ });
40
+
41
+ return (
42
+ <AnimatePresence mode="wait">
43
+ <motion.div
44
+ initial={{
45
+ opacity: 0,
46
+ y: 0,
47
+ }}
48
+ animate={{
49
+ y: visible ? 0 : -100,
50
+ opacity: visible ? 1 : 0,
51
+ }}
52
+ transition={{
53
+ duration: 0.2,
54
+ }}
55
+ className={cn(
56
+ "flex max-w-fit fixed top-6 inset-x-0 mx-auto border border-transparent dark:border-white/[0.2] rounded-full dark:bg-black bg-white shadow-[0px_2px_3px_-1px_rgba(0,0,0,0.1),0px_1px_0px_0px_rgba(25,28,33,0.02),0px_0px_0px_1px_rgba(25,28,33,0.08)] z-[5000] pr-2 pl-8 py-2 items-center justify-center space-x-4",
57
+ className
58
+ )}
59
+ >
60
+ {navItems.map((navItem: any, idx: number) => (
61
+ <a
62
+ key={`link=${idx}`}
63
+ href={navItem.link}
64
+ className={cn(
65
+ "relative dark:text-neutral-50 items-center flex space-x-1 text-neutral-600 dark:hover:text-neutral-300 hover:text-neutral-500"
66
+ )}
67
+ >
68
+ <span className="block sm:hidden">{navItem.icon}</span>
69
+ <span className="hidden sm:block text-sm">{navItem.name}</span>
70
+ </a>
71
+ ))}
72
+ <button className="text-sm font-medium relative text-black bg-white px-4 py-2 rounded-full">
73
+ <span>Get in touch</span>
74
+ </button>
75
+ </motion.div>
76
+ </AnimatePresence>
77
+ );
78
+ };
@@ -0,0 +1,167 @@
1
+ import type * as LabelPrimitive from "@radix-ui/react-label";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import * as React from "react";
4
+ import {
5
+ Controller,
6
+ type ControllerProps,
7
+ type FieldPath,
8
+ type FieldValues,
9
+ FormProvider,
10
+ useFormContext,
11
+ } from "react-hook-form";
12
+
13
+ import { Label } from "#components/ui/label";
14
+ import { cn } from "#utils";
15
+
16
+ const Form = FormProvider;
17
+
18
+ type FormFieldContextValue<
19
+ TFieldValues extends FieldValues = FieldValues,
20
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
21
+ > = {
22
+ name: TName;
23
+ };
24
+
25
+ const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
26
+
27
+ const FormField = <
28
+ TFieldValues extends FieldValues = FieldValues,
29
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
30
+ >({
31
+ ...props
32
+ }: ControllerProps<TFieldValues, TName>) => {
33
+ return (
34
+ <FormFieldContext.Provider value={{ name: props.name }}>
35
+ <Controller {...props} />
36
+ </FormFieldContext.Provider>
37
+ );
38
+ };
39
+
40
+ const useFormField = () => {
41
+ const fieldContext = React.useContext(FormFieldContext);
42
+ const itemContext = React.useContext(FormItemContext);
43
+ const { getFieldState, formState } = useFormContext();
44
+
45
+ const fieldState = getFieldState(fieldContext.name, formState);
46
+
47
+ if (!fieldContext) {
48
+ throw new Error("useFormField should be used within <FormField>");
49
+ }
50
+
51
+ const { id } = itemContext;
52
+
53
+ return {
54
+ id,
55
+ name: fieldContext.name,
56
+ formItemId: `${id}-form-item`,
57
+ formDescriptionId: `${id}-form-item-description`,
58
+ formMessageId: `${id}-form-item-message`,
59
+ ...fieldState,
60
+ };
61
+ };
62
+
63
+ type FormItemContextValue = {
64
+ id: string;
65
+ };
66
+
67
+ const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
68
+
69
+ const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
70
+ ({ className, ...props }, ref) => {
71
+ const id = React.useId();
72
+
73
+ return (
74
+ <FormItemContext.Provider value={{ id }}>
75
+ <div ref={ref} className={cn("space-y-2", className)} {...props} />
76
+ </FormItemContext.Provider>
77
+ );
78
+ }
79
+ );
80
+ FormItem.displayName = "FormItem";
81
+
82
+ const FormLabel = React.forwardRef<
83
+ React.ElementRef<typeof LabelPrimitive.Root>,
84
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
85
+ >(({ className, ...props }, ref) => {
86
+ const { error, formItemId } = useFormField();
87
+
88
+ return (
89
+ <Label
90
+ ref={ref}
91
+ className={cn(error && "text-destructive", className)}
92
+ htmlFor={formItemId}
93
+ {...props}
94
+ />
95
+ );
96
+ });
97
+ FormLabel.displayName = "FormLabel";
98
+
99
+ const FormControl = React.forwardRef<
100
+ React.ElementRef<typeof Slot>,
101
+ React.ComponentPropsWithoutRef<typeof Slot>
102
+ >(({ ...props }, ref) => {
103
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
104
+
105
+ return (
106
+ <Slot
107
+ ref={ref}
108
+ id={formItemId}
109
+ aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
110
+ aria-invalid={!!error}
111
+ {...props}
112
+ />
113
+ );
114
+ });
115
+ FormControl.displayName = "FormControl";
116
+
117
+ const FormDescription = React.forwardRef<
118
+ HTMLParagraphElement,
119
+ React.HTMLAttributes<HTMLParagraphElement>
120
+ >(({ className, ...props }, ref) => {
121
+ const { formDescriptionId } = useFormField();
122
+
123
+ return (
124
+ <p
125
+ ref={ref}
126
+ id={formDescriptionId}
127
+ className={cn("text-[0.8rem] text-muted-foreground", className)}
128
+ {...props}
129
+ />
130
+ );
131
+ });
132
+ FormDescription.displayName = "FormDescription";
133
+
134
+ const FormMessage = React.forwardRef<
135
+ HTMLParagraphElement,
136
+ React.HTMLAttributes<HTMLParagraphElement>
137
+ >(({ className, children, ...props }, ref) => {
138
+ const { error, formMessageId } = useFormField();
139
+ const body = error ? String(error?.message) : children;
140
+
141
+ if (!body) {
142
+ return null;
143
+ }
144
+
145
+ return (
146
+ <p
147
+ ref={ref}
148
+ id={formMessageId}
149
+ className={cn("text-[0.8rem] font-medium text-destructive", className)}
150
+ {...props}
151
+ >
152
+ {body}
153
+ </p>
154
+ );
155
+ });
156
+ FormMessage.displayName = "FormMessage";
157
+
158
+ export {
159
+ useFormField,
160
+ Form,
161
+ FormItem,
162
+ FormLabel,
163
+ FormControl,
164
+ FormDescription,
165
+ FormMessage,
166
+ FormField,
167
+ };
@@ -0,0 +1,55 @@
1
+ import React from "react";
2
+ import { cn } from "#utils";
3
+
4
+ interface ImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
5
+ fallback?: React.ReactNode;
6
+ }
7
+
8
+ const Image = React.forwardRef<HTMLImageElement, ImageProps>(
9
+ ({ className, src, alt, fallback, ...props }, ref) => {
10
+ const [error, setError] = React.useState(false);
11
+
12
+ const handleError = () => {
13
+ setError(true);
14
+ };
15
+
16
+ if (error || !src) {
17
+ return (
18
+ <div
19
+ className={cn(
20
+ "flex items-center justify-center bg-muted text-muted-foreground",
21
+ className
22
+ )}
23
+ {...props}
24
+ >
25
+ {fallback || (
26
+ <svg
27
+ xmlns="http://www.w3.org/2000/svg"
28
+ width="24"
29
+ height="24"
30
+ viewBox="0 0 24 24"
31
+ fill="none"
32
+ stroke="currentColor"
33
+ strokeWidth="2"
34
+ strokeLinecap="round"
35
+ strokeLinejoin="round"
36
+ className="h-8 w-8"
37
+ >
38
+ <rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
39
+ <circle cx="9" cy="9" r="2" />
40
+ <path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />
41
+ </svg>
42
+ )}
43
+ </div>
44
+ );
45
+ }
46
+
47
+ return (
48
+ <img ref={ref} src={src} alt={alt} onError={handleError} className={className} {...props} />
49
+ );
50
+ }
51
+ );
52
+
53
+ Image.displayName = "Image";
54
+
55
+ export default Image;
@@ -0,0 +1,22 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "#utils";
4
+
5
+ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
6
+ ({ className, type, ...props }, ref) => {
7
+ return (
8
+ <input
9
+ type={type}
10
+ className={cn(
11
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
12
+ className
13
+ )}
14
+ ref={ref}
15
+ {...props}
16
+ />
17
+ );
18
+ }
19
+ );
20
+ Input.displayName = "Input";
21
+
22
+ export { Input };
@@ -0,0 +1,19 @@
1
+ import * as LabelPrimitive from "@radix-ui/react-label";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import * as React from "react";
4
+
5
+ import { cn } from "#utils";
6
+
7
+ const labelVariants = cva(
8
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
9
+ );
10
+
11
+ const Label = React.forwardRef<
12
+ React.ElementRef<typeof LabelPrimitive.Root>,
13
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
14
+ >(({ className, ...props }, ref) => (
15
+ <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
16
+ ));
17
+ Label.displayName = LabelPrimitive.Root.displayName;
18
+
19
+ export { Label };
@@ -0,0 +1,105 @@
1
+ import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from "lucide-react";
2
+ import type * as React from "react";
3
+ import { type Button, buttonVariants } from "#components/ui/button";
4
+ import { cn } from "#utils";
5
+
6
+ function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
7
+ return (
8
+ <nav
9
+ role="navigation"
10
+ aria-label="pagination"
11
+ data-slot="pagination"
12
+ className={cn("mx-auto flex w-full justify-center", className)}
13
+ {...props}
14
+ />
15
+ );
16
+ }
17
+
18
+ function PaginationContent({ className, ...props }: React.ComponentProps<"ul">) {
19
+ return (
20
+ <ul
21
+ data-slot="pagination-content"
22
+ className={cn("flex flex-row items-center gap-1", className)}
23
+ {...props}
24
+ />
25
+ );
26
+ }
27
+
28
+ function PaginationItem({ ...props }: React.ComponentProps<"li">) {
29
+ return <li data-slot="pagination-item" {...props} />;
30
+ }
31
+
32
+ type PaginationLinkProps = {
33
+ isActive?: boolean;
34
+ } & Pick<React.ComponentProps<typeof Button>, "size"> &
35
+ React.ComponentProps<"a">;
36
+
37
+ function PaginationLink({ className, isActive, size = "icon", ...props }: PaginationLinkProps) {
38
+ return (
39
+ <a
40
+ aria-current={isActive ? "page" : undefined}
41
+ data-slot="pagination-link"
42
+ data-active={isActive}
43
+ className={cn(
44
+ buttonVariants({
45
+ variant: isActive ? "outline" : "ghost",
46
+ size,
47
+ }),
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+ );
53
+ }
54
+
55
+ function PaginationPrevious({ className, ...props }: React.ComponentProps<typeof PaginationLink>) {
56
+ return (
57
+ <PaginationLink
58
+ aria-label="Go to previous page"
59
+ size="default"
60
+ className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
61
+ {...props}
62
+ >
63
+ <ChevronLeftIcon />
64
+ <span className="hidden sm:block">Previous</span>
65
+ </PaginationLink>
66
+ );
67
+ }
68
+
69
+ function PaginationNext({ className, ...props }: React.ComponentProps<typeof PaginationLink>) {
70
+ return (
71
+ <PaginationLink
72
+ aria-label="Go to next page"
73
+ size="default"
74
+ className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
75
+ {...props}
76
+ >
77
+ <span className="hidden sm:block">Next</span>
78
+ <ChevronRightIcon />
79
+ </PaginationLink>
80
+ );
81
+ }
82
+
83
+ function PaginationEllipsis({ className, ...props }: React.ComponentProps<"span">) {
84
+ return (
85
+ <span
86
+ aria-hidden
87
+ data-slot="pagination-ellipsis"
88
+ className={cn("flex size-9 items-center justify-center", className)}
89
+ {...props}
90
+ >
91
+ <MoreHorizontalIcon className="size-4" />
92
+ <span className="sr-only">More pages</span>
93
+ </span>
94
+ );
95
+ }
96
+
97
+ export {
98
+ Pagination,
99
+ PaginationContent,
100
+ PaginationLink,
101
+ PaginationItem,
102
+ PaginationPrevious,
103
+ PaginationNext,
104
+ PaginationEllipsis,
105
+ };
@@ -0,0 +1,23 @@
1
+ import * as ProgressPrimitive from "@radix-ui/react-progress";
2
+ import * as React from "react";
3
+
4
+ import { cn } from "#utils";
5
+
6
+ const Progress = React.forwardRef<
7
+ React.ElementRef<typeof ProgressPrimitive.Root>,
8
+ React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
9
+ >(({ className, value, ...props }, ref) => (
10
+ <ProgressPrimitive.Root
11
+ ref={ref}
12
+ className={cn("relative h-2 w-full overflow-hidden rounded-full bg-primary/20", className)}
13
+ {...props}
14
+ >
15
+ <ProgressPrimitive.Indicator
16
+ className="h-full w-full flex-1 bg-primary transition-all"
17
+ style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
18
+ />
19
+ </ProgressPrimitive.Root>
20
+ ));
21
+ Progress.displayName = ProgressPrimitive.Root.displayName;
22
+
23
+ export { Progress };