@hobenakicoffee/libraries 1.11.0 → 1.13.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 (76) hide show
  1. package/README.md +25 -4
  2. package/package.json +84 -9
  3. package/src/App.tsx +28 -0
  4. package/src/components/turnstile-captcha.tsx +47 -0
  5. package/src/components/ui/alert-dialog.tsx +196 -0
  6. package/src/components/ui/alert.tsx +76 -0
  7. package/src/components/ui/avatar.tsx +110 -0
  8. package/src/components/ui/badge.tsx +49 -0
  9. package/src/components/ui/breadcrumb.tsx +122 -0
  10. package/src/components/ui/button-group.tsx +82 -0
  11. package/src/components/ui/button.tsx +77 -0
  12. package/src/components/ui/calendar.tsx +235 -0
  13. package/src/components/ui/card.tsx +100 -0
  14. package/src/components/ui/chart.tsx +364 -0
  15. package/src/components/ui/checkbox.tsx +30 -0
  16. package/src/components/ui/dialog.tsx +162 -0
  17. package/src/components/ui/drawer.tsx +126 -0
  18. package/src/components/ui/dropdown-menu.tsx +267 -0
  19. package/src/components/ui/empty-minimal.tsx +20 -0
  20. package/src/components/ui/empty.tsx +101 -0
  21. package/src/components/ui/field.tsx +235 -0
  22. package/src/components/ui/input-group.tsx +170 -0
  23. package/src/components/ui/input-otp.tsx +84 -0
  24. package/src/components/ui/input.tsx +37 -0
  25. package/src/components/ui/item.tsx +196 -0
  26. package/src/components/ui/label.tsx +19 -0
  27. package/src/components/ui/popover.tsx +87 -0
  28. package/src/components/ui/radio-group.tsx +47 -0
  29. package/src/components/ui/select.tsx +205 -0
  30. package/src/components/ui/separator.tsx +26 -0
  31. package/src/components/ui/sheet.tsx +141 -0
  32. package/src/components/ui/sidebar.tsx +699 -0
  33. package/src/components/ui/skeleton.tsx +13 -0
  34. package/src/components/ui/sonner.tsx +74 -0
  35. package/src/components/ui/spinner.tsx +18 -0
  36. package/src/components/ui/table.tsx +114 -0
  37. package/src/components/ui/tabs.tsx +88 -0
  38. package/src/components/ui/textarea.tsx +35 -0
  39. package/src/components/ui/toggle-group.tsx +91 -0
  40. package/src/components/ui/toggle.tsx +44 -0
  41. package/src/components/ui/tooltip.tsx +59 -0
  42. package/src/constants/common.test.ts +1 -1
  43. package/src/constants/legal.test.ts +1 -1
  44. package/src/constants/payment.test.ts +9 -9
  45. package/src/constants/platforms.test.ts +1 -1
  46. package/src/constants/services.test.ts +1 -1
  47. package/src/hooks/use-mobile.ts +19 -0
  48. package/src/index.css +135 -0
  49. package/src/lib/utils.ts +6 -0
  50. package/src/main.tsx +16 -0
  51. package/src/moderation/datasets/bn.ts +708 -708
  52. package/src/moderation/normalizer.test.ts +1 -1
  53. package/src/moderation/normalizer.ts +16 -16
  54. package/src/moderation/profanity-service.test.ts +3 -3
  55. package/src/providers/theme-provider.tsx +73 -0
  56. package/src/types/supabase.ts +751 -647
  57. package/src/utils/check-moderation.test.ts +12 -12
  58. package/src/utils/format-number.test.ts +1 -1
  59. package/src/utils/format-plain-text.test.ts +1 -1
  60. package/src/utils/get-social-handle.test.ts +3 -3
  61. package/src/utils/get-social-link.test.ts +9 -9
  62. package/src/utils/get-social-link.ts +5 -3
  63. package/src/utils/get-user-name-initials.test.ts +1 -1
  64. package/src/utils/get-user-name-initials.ts +4 -4
  65. package/src/utils/get-user-page-link.ts +1 -1
  66. package/src/utils/index.ts +5 -5
  67. package/src/utils/open-to-new-window.ts +3 -1
  68. package/src/utils/post-to-facebook.test.ts +1 -1
  69. package/src/utils/post-to-facebook.ts +9 -3
  70. package/src/utils/post-to-instagram.test.ts +1 -1
  71. package/src/utils/post-to-linkedin.test.ts +1 -1
  72. package/src/utils/post-to-linkedin.ts +9 -3
  73. package/src/utils/post-to-x.test.ts +1 -1
  74. package/src/utils/post-to-x.ts +12 -4
  75. package/src/utils/to-human-readable.ts +6 -2
  76. package/src/utils/validate-phone-number.test.ts +1 -1
@@ -0,0 +1,170 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import type * as React from "react";
3
+ import { Button } from "@/components/ui/button";
4
+ import { Input } from "@/components/ui/input";
5
+ import { Textarea } from "@/components/ui/textarea";
6
+ import { cn } from "@/lib/utils";
7
+
8
+ const inputGroupVariants = cva(
9
+ "group/input-group relative flex h-12 w-full min-w-0 items-center gap-1.5 overflow-hidden rounded-xl border border-input outline-none transition-colors has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-start]]:h-auto has-[>textarea]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:flex-col has-[textarea]:rounded-xl has-data-[align=block-end]:rounded-2xl has-data-[align=block-start]:rounded-2xl has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot=input-group-control]:focus-visible]:ring has-[[data-slot][aria-invalid=true]]:ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring has-[[data-slot][aria-invalid=true]]:ring-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-start]]:[&>input]:pl-1.5 [[data-slot=combobox-content]_&]:focus-within:border-inherit [[data-slot=combobox-content]_&]:focus-within:ring-0",
10
+ {
11
+ variants: {
12
+ variant: {
13
+ default: "bg-input/30",
14
+ inverted: "bg-background",
15
+ },
16
+ },
17
+ defaultVariants: {
18
+ variant: "default",
19
+ },
20
+ }
21
+ );
22
+
23
+ function InputGroup({
24
+ className,
25
+ variant = "default",
26
+ ...props
27
+ }: React.ComponentProps<"div"> & VariantProps<typeof inputGroupVariants>) {
28
+ return (
29
+ <div
30
+ className={cn(inputGroupVariants({ variant, className }))}
31
+ data-slot="input-group"
32
+ role="group"
33
+ {...props}
34
+ />
35
+ );
36
+ }
37
+
38
+ const inputGroupAddonVariants = cva(
39
+ "flex h-auto cursor-text select-none items-center justify-center gap-2 py-2 font-medium text-muted-foreground text-sm **:data-[slot=kbd]:rounded-xl **:data-[slot=kbd]:bg-muted-foreground/10 **:data-[slot=kbd]:px-1.5 group-data-[disabled=true]/input-group:opacity-50 [&>svg:not([class*='size-'])]:size-4",
40
+ {
41
+ variants: {
42
+ align: {
43
+ "inline-start":
44
+ "order-first pl-3 has-[>button]:ml-[-0.25rem] has-[>kbd]:ml-[-0.15rem]",
45
+ "inline-end":
46
+ "order-last pr-3 has-[>button]:mr-[-0.25rem] has-[>kbd]:mr-[-0.15rem]",
47
+ "block-start":
48
+ "order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-3 [.border-b]:pb-3",
49
+ "block-end":
50
+ "order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-3 [.border-t]:pt-3",
51
+ },
52
+ },
53
+ defaultVariants: {
54
+ align: "inline-start",
55
+ },
56
+ }
57
+ );
58
+
59
+ function InputGroupAddon({
60
+ className,
61
+ align = "inline-start",
62
+ ...props
63
+ }: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
64
+ return (
65
+ <div
66
+ className={cn(inputGroupAddonVariants({ align }), className)}
67
+ data-align={align}
68
+ data-slot="input-group-addon"
69
+ onClick={(e) => {
70
+ if ((e.target as HTMLElement).closest("button")) {
71
+ return;
72
+ }
73
+ e.currentTarget.parentElement?.querySelector("input")?.focus();
74
+ }}
75
+ role="group"
76
+ {...props}
77
+ />
78
+ );
79
+ }
80
+
81
+ const inputGroupButtonVariants = cva(
82
+ "flex items-center gap-2 rounded-xl text-sm shadow-none",
83
+ {
84
+ variants: {
85
+ size: {
86
+ xs: "h-6 gap-1 px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
87
+ sm: "",
88
+ "icon-xs": "size-6 p-0 has-[>svg]:p-0",
89
+ "icon-sm": "size-8 p-0 has-[>svg]:p-0",
90
+ },
91
+ },
92
+ defaultVariants: {
93
+ size: "xs",
94
+ },
95
+ }
96
+ );
97
+
98
+ function InputGroupButton({
99
+ className,
100
+ type = "button",
101
+ variant = "ghost",
102
+ size = "xs",
103
+ ...props
104
+ }: Omit<React.ComponentProps<typeof Button>, "size" | "type"> &
105
+ VariantProps<typeof inputGroupButtonVariants> & {
106
+ type?: "button" | "submit" | "reset";
107
+ }) {
108
+ return (
109
+ <Button
110
+ className={cn(inputGroupButtonVariants({ size }), className)}
111
+ data-size={size}
112
+ type={type}
113
+ variant={variant}
114
+ {...props}
115
+ />
116
+ );
117
+ }
118
+
119
+ function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
120
+ return (
121
+ <span
122
+ className={cn(
123
+ "flex items-center gap-2 text-muted-foreground text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
124
+ className
125
+ )}
126
+ {...props}
127
+ />
128
+ );
129
+ }
130
+
131
+ function InputGroupInput({
132
+ className,
133
+ ...props
134
+ }: React.ComponentProps<"input">) {
135
+ return (
136
+ <Input
137
+ className={cn(
138
+ "flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",
139
+ className
140
+ )}
141
+ data-slot="input-group-control"
142
+ {...props}
143
+ />
144
+ );
145
+ }
146
+
147
+ function InputGroupTextarea({
148
+ className,
149
+ ...props
150
+ }: React.ComponentProps<"textarea">) {
151
+ return (
152
+ <Textarea
153
+ className={cn(
154
+ "flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",
155
+ className
156
+ )}
157
+ data-slot="input-group-control"
158
+ {...props}
159
+ />
160
+ );
161
+ }
162
+
163
+ export {
164
+ InputGroup,
165
+ InputGroupAddon,
166
+ InputGroupButton,
167
+ InputGroupText,
168
+ InputGroupInput,
169
+ InputGroupTextarea,
170
+ };
@@ -0,0 +1,84 @@
1
+ import { MinusSignIcon } from "@hugeicons/core-free-icons";
2
+ import { HugeiconsIcon } from "@hugeicons/react";
3
+ import { OTPInput, OTPInputContext } from "input-otp";
4
+ import { type ComponentProps, useContext } from "react";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ function InputOTP({
8
+ className,
9
+ containerClassName,
10
+ ...props
11
+ }: ComponentProps<typeof OTPInput> & {
12
+ containerClassName?: string;
13
+ }) {
14
+ return (
15
+ <OTPInput
16
+ className={cn("disabled:cursor-not-allowed", className)}
17
+ containerClassName={cn(
18
+ "cn-input-otp flex items-center has-disabled:opacity-50",
19
+ containerClassName
20
+ )}
21
+ data-slot="input-otp"
22
+ spellCheck={false}
23
+ {...props}
24
+ />
25
+ );
26
+ }
27
+
28
+ function InputOTPGroup({ className, ...props }: ComponentProps<"div">) {
29
+ return (
30
+ <div
31
+ className={cn(
32
+ "flex items-center rounded-xl has-aria-invalid:border-destructive has-aria-invalid:ring has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40",
33
+ className
34
+ )}
35
+ data-slot="input-otp-group"
36
+ {...props}
37
+ />
38
+ );
39
+ }
40
+
41
+ function InputOTPSlot({
42
+ index,
43
+ className,
44
+ ...props
45
+ }: ComponentProps<"div"> & {
46
+ index: number;
47
+ }) {
48
+ const inputOTPContext = useContext(OTPInputContext);
49
+ const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {};
50
+
51
+ return (
52
+ <div
53
+ className={cn(
54
+ "relative flex size-9 items-center justify-center border-input border-y border-r bg-input/10 text-foreground text-sm outline-none transition-all first:rounded-l-xl first:border-l last:rounded-r-xl aria-invalid:border-destructive data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:border-l data-[active=true]:ring data-[active=true]:ring-ring data-[active=true]:aria-invalid:border-destructive data-[active=true]:aria-invalid:ring-destructive/20 sm:size-10 dark:data-[active=true]:aria-invalid:ring-destructive/40",
55
+ className
56
+ )}
57
+ data-active={isActive}
58
+ data-slot="input-otp-slot"
59
+ {...props}
60
+ >
61
+ {char}
62
+ {hasFakeCaret && (
63
+ <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
64
+ <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
65
+ </div>
66
+ )}
67
+ </div>
68
+ );
69
+ }
70
+
71
+ function InputOTPSeparator({ ...props }: ComponentProps<"div">) {
72
+ return (
73
+ <div
74
+ className="flex items-center [&_svg:not([class*='size-'])]:size-4"
75
+ data-slot="input-otp-separator"
76
+ role="separator"
77
+ {...props}
78
+ >
79
+ <HugeiconsIcon icon={MinusSignIcon} strokeWidth={2} />
80
+ </div>
81
+ );
82
+ }
83
+
84
+ export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };
@@ -0,0 +1,37 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import type * as React from "react";
3
+ import { cn } from "@/lib/utils";
4
+
5
+ const inputVariants = cva(
6
+ "h-12 w-full min-w-0 rounded-xl border border-input px-3 py-1 text-base text-foreground outline-none transition-colors file:inline-flex file:h-7 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground/80 focus-visible:border-ring focus-visible:ring focus-visible:ring-ring disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring aria-invalid:ring-destructive md:text-sm dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: "bg-input/30",
11
+ inverted: "bg-background",
12
+ },
13
+ },
14
+ defaultVariants: {
15
+ variant: "default",
16
+ },
17
+ }
18
+ );
19
+
20
+ function Input({
21
+ className,
22
+ variant = "default",
23
+ type,
24
+ ...props
25
+ }: React.ComponentProps<"input"> & VariantProps<typeof inputVariants>) {
26
+ return (
27
+ <input
28
+ className={cn(inputVariants({ variant, className }))}
29
+ data-slot="input"
30
+ data-variant={variant}
31
+ type={type}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+
37
+ export { Input, inputVariants };
@@ -0,0 +1,196 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import { Slot } from "radix-ui";
3
+ import type * as React from "react";
4
+ import { Separator } from "@/components/ui/separator";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
8
+ return (
9
+ <div
10
+ className={cn(
11
+ "group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2",
12
+ className
13
+ )}
14
+ data-slot="item-group"
15
+ role="list"
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ function ItemSeparator({
22
+ className,
23
+ ...props
24
+ }: React.ComponentProps<typeof Separator>) {
25
+ return (
26
+ <Separator
27
+ className={cn("my-2", className)}
28
+ data-slot="item-separator"
29
+ orientation="horizontal"
30
+ {...props}
31
+ />
32
+ );
33
+ }
34
+
35
+ const itemVariants = cva(
36
+ "group/item flex w-full flex-wrap items-center rounded-xl border text-sm outline-none transition-colors duration-100 focus-visible:border-ring focus-visible:ring focus-visible:ring-ring [a]:transition-colors [a]:hover:bg-muted",
37
+ {
38
+ variants: {
39
+ variant: {
40
+ default: "border-transparent",
41
+ outline: "border-border",
42
+ muted: "border-transparent bg-muted/50",
43
+ },
44
+ size: {
45
+ default: "gap-3.5 px-4 py-3.5",
46
+ lg: "gap-3.5 px-4 py-3.5 md:px-6 md:py-6",
47
+ sm: "gap-2.5 px-3 py-2.5",
48
+ xs: "gap-2 px-2.5 py-2 [[data-slot=dropdown-menu-content]_&]:p-0",
49
+ },
50
+ },
51
+ defaultVariants: {
52
+ variant: "default",
53
+ size: "default",
54
+ },
55
+ }
56
+ );
57
+
58
+ function Item({
59
+ className,
60
+ variant = "default",
61
+ size = "default",
62
+ asChild = false,
63
+ ...props
64
+ }: React.ComponentProps<"div"> &
65
+ VariantProps<typeof itemVariants> & { asChild?: boolean }) {
66
+ const Comp = asChild ? Slot.Root : "div";
67
+ return (
68
+ <Comp
69
+ className={cn(itemVariants({ variant, size, className }))}
70
+ data-size={size}
71
+ data-slot="item"
72
+ data-variant={variant}
73
+ {...props}
74
+ />
75
+ );
76
+ }
77
+
78
+ const itemMediaVariants = cva(
79
+ "flex shrink-0 items-center justify-center gap-2 group-has-[[data-slot=item-description]]/item:translate-y-0.5 group-has-[[data-slot=item-description]]/item:self-start [&_svg]:pointer-events-none",
80
+ {
81
+ variants: {
82
+ variant: {
83
+ default: "bg-transparent",
84
+ icon: "[&_svg:not([class*='size-'])]:size-4",
85
+ image:
86
+ "size-10 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover",
87
+ },
88
+ },
89
+ defaultVariants: {
90
+ variant: "default",
91
+ },
92
+ }
93
+ );
94
+
95
+ function ItemMedia({
96
+ className,
97
+ variant = "default",
98
+ ...props
99
+ }: React.ComponentProps<"div"> & VariantProps<typeof itemMediaVariants>) {
100
+ return (
101
+ <div
102
+ className={cn(itemMediaVariants({ variant, className }))}
103
+ data-slot="item-media"
104
+ data-variant={variant}
105
+ {...props}
106
+ />
107
+ );
108
+ }
109
+
110
+ function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
111
+ return (
112
+ <div
113
+ className={cn(
114
+ "flex flex-1 flex-col gap-1 group-data-[size=xs]/item:gap-0 [&+[data-slot=item-content]]:flex-none",
115
+ className
116
+ )}
117
+ data-slot="item-content"
118
+ {...props}
119
+ />
120
+ );
121
+ }
122
+
123
+ function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
124
+ return (
125
+ <div
126
+ className={cn(
127
+ "line-clamp-1 flex w-fit items-center gap-2 font-medium text-sm leading-snug underline-offset-4",
128
+ className
129
+ )}
130
+ data-slot="item-title"
131
+ {...props}
132
+ />
133
+ );
134
+ }
135
+
136
+ function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
137
+ return (
138
+ <p
139
+ className={cn(
140
+ "line-clamp-2 text-left font-normal text-muted-foreground text-sm leading-normal group-data-[size=xs]/item:text-xs [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
141
+ className
142
+ )}
143
+ data-slot="item-description"
144
+ {...props}
145
+ />
146
+ );
147
+ }
148
+
149
+ function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
150
+ return (
151
+ <div
152
+ className={cn("flex items-center gap-2", className)}
153
+ data-slot="item-actions"
154
+ {...props}
155
+ />
156
+ );
157
+ }
158
+
159
+ function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
160
+ return (
161
+ <div
162
+ className={cn(
163
+ "flex basis-full items-center justify-between gap-2",
164
+ className
165
+ )}
166
+ data-slot="item-header"
167
+ {...props}
168
+ />
169
+ );
170
+ }
171
+
172
+ function ItemFooter({ className, ...props }: React.ComponentProps<"div">) {
173
+ return (
174
+ <div
175
+ className={cn(
176
+ "flex basis-full items-center justify-between gap-2",
177
+ className
178
+ )}
179
+ data-slot="item-footer"
180
+ {...props}
181
+ />
182
+ );
183
+ }
184
+
185
+ export {
186
+ Item,
187
+ ItemMedia,
188
+ ItemContent,
189
+ ItemActions,
190
+ ItemGroup,
191
+ ItemSeparator,
192
+ ItemTitle,
193
+ ItemDescription,
194
+ ItemHeader,
195
+ ItemFooter,
196
+ };
@@ -0,0 +1,19 @@
1
+ import type * as React from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ function Label({ className, ...props }: React.ComponentProps<"label">) {
6
+ return (
7
+ // biome-ignore lint/a11y/noLabelWithoutControl: <shadcn components />
8
+ <label
9
+ className={cn(
10
+ "flex select-none items-center gap-2 font-medium text-muted-foreground text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50",
11
+ className
12
+ )}
13
+ data-slot="label"
14
+ {...props}
15
+ />
16
+ );
17
+ }
18
+
19
+ export { Label };
@@ -0,0 +1,87 @@
1
+ import { Popover as PopoverPrimitive } from "radix-ui";
2
+ import type * as React from "react";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ function Popover({
7
+ ...props
8
+ }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
9
+ return <PopoverPrimitive.Root data-slot="popover" {...props} />;
10
+ }
11
+
12
+ function PopoverTrigger({
13
+ ...props
14
+ }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
15
+ return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
16
+ }
17
+
18
+ function PopoverContent({
19
+ className,
20
+ align = "center",
21
+ sideOffset = 4,
22
+ ...props
23
+ }: React.ComponentProps<typeof PopoverPrimitive.Content>) {
24
+ return (
25
+ <PopoverPrimitive.Portal>
26
+ <PopoverPrimitive.Content
27
+ align={align}
28
+ className={cn(
29
+ "data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 z-50 flex w-72 origin-(--radix-popover-content-transform-origin) flex-col gap-4 rounded-md bg-popover p-4 text-popover-foreground text-sm shadow-md outline-hidden ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in",
30
+ className
31
+ )}
32
+ data-slot="popover-content"
33
+ sideOffset={sideOffset}
34
+ {...props}
35
+ />
36
+ </PopoverPrimitive.Portal>
37
+ );
38
+ }
39
+
40
+ function PopoverAnchor({
41
+ ...props
42
+ }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
43
+ return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
44
+ }
45
+
46
+ function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) {
47
+ return (
48
+ <div
49
+ className={cn("flex flex-col gap-1 text-sm", className)}
50
+ data-slot="popover-header"
51
+ {...props}
52
+ />
53
+ );
54
+ }
55
+
56
+ function PopoverTitle({ className, ...props }: React.ComponentProps<"h2">) {
57
+ return (
58
+ <div
59
+ className={cn("font-medium", className)}
60
+ data-slot="popover-title"
61
+ {...props}
62
+ />
63
+ );
64
+ }
65
+
66
+ function PopoverDescription({
67
+ className,
68
+ ...props
69
+ }: React.ComponentProps<"p">) {
70
+ return (
71
+ <p
72
+ className={cn("text-muted-foreground", className)}
73
+ data-slot="popover-description"
74
+ {...props}
75
+ />
76
+ );
77
+ }
78
+
79
+ export {
80
+ Popover,
81
+ PopoverAnchor,
82
+ PopoverContent,
83
+ PopoverDescription,
84
+ PopoverHeader,
85
+ PopoverTitle,
86
+ PopoverTrigger,
87
+ };
@@ -0,0 +1,47 @@
1
+ import { CircleIcon } from "@hugeicons/core-free-icons";
2
+ import { HugeiconsIcon } from "@hugeicons/react";
3
+ import { RadioGroup as RadioGroupPrimitive } from "radix-ui";
4
+ import type * as React from "react";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ function RadioGroup({
8
+ className,
9
+ ...props
10
+ }: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
11
+ return (
12
+ <RadioGroupPrimitive.Root
13
+ className={cn("grid w-full gap-3", className)}
14
+ data-slot="radio-group"
15
+ {...props}
16
+ />
17
+ );
18
+ }
19
+
20
+ function RadioGroupItem({
21
+ className,
22
+ ...props
23
+ }: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
24
+ return (
25
+ <RadioGroupPrimitive.Item
26
+ className={cn(
27
+ "group/radio-group-item peer relative flex aspect-square size-5 shrink-0 rounded-full border-2 border-input text-primary shadow-xs outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[state=checked]:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
28
+ className
29
+ )}
30
+ data-slot="radio-group-item"
31
+ {...props}
32
+ >
33
+ <RadioGroupPrimitive.Indicator
34
+ className="flex size-5 items-center justify-center text-primary group-aria-invalid/radio-group-item:text-destructive"
35
+ data-slot="radio-group-indicator"
36
+ >
37
+ <HugeiconsIcon
38
+ className="absolute top-1/2 left-1/2 size-2.5 -translate-x-1/2 -translate-y-1/2 fill-current"
39
+ icon={CircleIcon}
40
+ strokeWidth={2}
41
+ />
42
+ </RadioGroupPrimitive.Indicator>
43
+ </RadioGroupPrimitive.Item>
44
+ );
45
+ }
46
+
47
+ export { RadioGroup, RadioGroupItem };