@hobenakicoffee/libraries 1.11.0 → 1.12.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 +649 -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
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @hobenakicoffee/libraries
2
2
 
3
- Framework-agnostic shared constants and utilities for “হবে নাকি Coffee?” projects.
3
+ Framework-agnostic shared constants and utilities for "হবে নাকি Coffee?" projects.
4
4
 
5
5
  ## Installation
6
6
 
@@ -47,9 +47,9 @@ import {
47
47
 
48
48
  ### Utilities
49
49
 
50
- | Entrypoint | Function exports |
51
- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
52
- | `@hobenakicoffee/libraries/utils` | `formatAmount`, `formatSignedAmount`, `formatDate`, `formatToPlainText`, `formatMetadataKey`, `getSocialUrl`, `getUserPageLink`, `openInNewWindow`, `shareToFacebook`, `shareToInstagram`, `shareToLinkedIn`, `shareToX`, `printQrSvg` |
50
+ | Entrypoint | Function exports |
51
+ | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
52
+ | `@hobenakicoffee/libraries/utils` | `checkModeration`, `formatAmount`, `formatSignedAmount`, `formatDate`, `formatNumber`, `formatToPlainText`, `getSocialHandle`, `getSocialUrl`, `getUserNameInitials`, `getUserPageLink`, `openInNewWindow`, `shareToFacebook`, `shareToInstagram`, `shareToLinkedIn`, `shareToX`, `printQrSvg`, `toHumanReadable`, `validatePhoneNumber` |
53
53
 
54
54
  ## Local development
55
55
 
@@ -97,10 +97,14 @@ src/
97
97
  services.ts
98
98
  index.ts
99
99
  utils/
100
+ check-moderation.ts
100
101
  format-amount.ts
101
102
  format-date.ts
103
+ format-number.ts
102
104
  format-plain-text.ts
103
105
  get-social-handle.ts
106
+ get-social-link.ts
107
+ get-user-name-initials.ts
104
108
  get-user-page-link.ts
105
109
  open-to-new-window.ts
106
110
  post-to-facebook.ts
@@ -108,7 +112,24 @@ src/
108
112
  post-to-linkedin.ts
109
113
  post-to-x.ts
110
114
  qr-svg-utils.ts
115
+ to-human-readable.ts
116
+ validate-phone-number.ts
111
117
  index.ts
118
+ moderation/
119
+ normalizer.ts
120
+ profanity-service.ts
121
+ index.ts
122
+ types/
123
+ supabase.ts
124
+ index.ts
125
+ lib/
126
+ utils.ts
127
+ providers/
128
+ theme-provider.tsx
129
+ components/
130
+ ui/
131
+ ... (Radix UI based components)
132
+ turnstile-captcha.tsx
112
133
  ```
113
134
 
114
135
  ## Release & publish
package/package.json CHANGED
@@ -1,14 +1,54 @@
1
1
  {
2
2
  "name": "@hobenakicoffee/libraries",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "type": "module",
5
5
  "types": "src/index.ts",
6
6
  "exports": {
7
7
  ".": "./src/index.ts",
8
+ "./components/turnstile-captcha": "./src/components/turnstile-captcha.tsx",
9
+ "./components/ui/alert": "./src/components/ui/alert.tsx",
10
+ "./components/ui/alert-dialog": "./src/components/ui/alert-dialog.tsx",
11
+ "./components/ui/avatar": "./src/components/ui/avatar.tsx",
12
+ "./components/ui/badge": "./src/components/ui/badge.tsx",
13
+ "./components/ui/breadcrumb": "./src/components/ui/breadcrumb.tsx",
14
+ "./components/ui/button": "./src/components/ui/button.tsx",
15
+ "./components/ui/button-group": "./src/components/ui/button-group.tsx",
16
+ "./components/ui/calendar": "./src/components/ui/calendar.tsx",
17
+ "./components/ui/card": "./src/components/ui/card.tsx",
18
+ "./components/ui/chart": "./src/components/ui/chart.tsx",
19
+ "./components/ui/checkbox": "./src/components/ui/checkbox.tsx",
20
+ "./components/ui/dialog": "./src/components/ui/dialog.tsx",
21
+ "./components/ui/drawer": "./src/components/ui/drawer.tsx",
22
+ "./components/ui/dropdown-menu": "./src/components/ui/dropdown-menu.tsx",
23
+ "./components/ui/empty": "./src/components/ui/empty.tsx",
24
+ "./components/ui/empty-minimal": "./src/components/ui/empty-minimal.tsx",
25
+ "./components/ui/field": "./src/components/ui/field.tsx",
26
+ "./components/ui/input": "./src/components/ui/input.tsx",
27
+ "./components/ui/input-group": "./src/components/ui/input-group.tsx",
28
+ "./components/ui/input-otp": "./src/components/ui/input-otp.tsx",
29
+ "./components/ui/item": "./src/components/ui/item.tsx",
30
+ "./components/ui/label": "./src/components/ui/label.tsx",
31
+ "./components/ui/popover": "./src/components/ui/popover.tsx",
32
+ "./components/ui/radio-group": "./src/components/ui/radio-group.tsx",
33
+ "./components/ui/select": "./src/components/ui/select.tsx",
34
+ "./components/ui/separator": "./src/components/ui/separator.tsx",
35
+ "./components/ui/sheet": "./src/components/ui/sheet.tsx",
36
+ "./components/ui/sidebar": "./src/components/ui/sidebar.tsx",
37
+ "./components/ui/skeleton": "./src/components/ui/skeleton.tsx",
38
+ "./components/ui/sonner": "./src/components/ui/sonner.tsx",
39
+ "./components/ui/spinner": "./src/components/ui/spinner.tsx",
40
+ "./components/ui/table": "./src/components/ui/table.tsx",
41
+ "./components/ui/tabs": "./src/components/ui/tabs.tsx",
42
+ "./components/ui/textarea": "./src/components/ui/textarea.tsx",
43
+ "./components/ui/toggle": "./src/components/ui/toggle.tsx",
44
+ "./components/ui/toggle-group": "./src/components/ui/toggle-group.tsx",
45
+ "./components/ui/tooltip": "./src/components/ui/tooltip.tsx",
8
46
  "./constants": "./src/constants/index.ts",
47
+ "./lib/utils": "./src/lib/utils.ts",
9
48
  "./moderation": "./src/moderation/index.ts",
10
- "./utils": "./src/utils/index.ts",
11
- "./types": "./src/types/index.ts"
49
+ "./providers/theme-provider": "./src/providers/theme-provider.tsx",
50
+ "./types": "./src/types/index.ts",
51
+ "./utils": "./src/utils/index.ts"
12
52
  },
13
53
  "files": [
14
54
  "src",
@@ -16,27 +56,62 @@
16
56
  "package.json"
17
57
  ],
18
58
  "scripts": {
19
- "build": "bun build src/index.ts --outdir dist --target node",
20
- "dev": "bun --watch src/index.ts",
59
+ "dev": "vite",
60
+ "build": "tsc -b && vite build",
61
+ "preview": "vite preview",
21
62
  "test": "bun test",
22
63
  "test:watch": "bun test --watch",
23
64
  "typecheck": "tsc --noEmit",
24
65
  "lint": "bun run typecheck",
66
+ "format": "ultracite fix",
67
+ "format:check": "ultracite check",
68
+ "format:doctor": "ultracite doctor",
25
69
  "clean": "rm -rf dist",
26
- "prepublishOnly": "bun run test"
70
+ "prepublishOnly": "bun run test",
71
+ "check": "ultracite check",
72
+ "fix": "ultracite fix",
73
+ "prepare": "lefthook install"
27
74
  },
28
75
  "devDependencies": {
29
- "@types/bun": "latest"
76
+ "@biomejs/biome": "2.4.0",
77
+ "@types/bun": "latest",
78
+ "@types/react": "^19.2.14",
79
+ "@types/react-dom": "^19.2.3",
80
+ "@vitejs/plugin-react": "^5.1.4",
81
+ "lefthook": "^2.1.1",
82
+ "ultracite": "7.2.3",
83
+ "vite": "^7.3.1",
84
+ "vite-tsconfig-paths": "^6.1.1"
30
85
  },
31
86
  "peerDependencies": {
32
- "typescript": "^5"
87
+ "typescript": "^5.9.3"
33
88
  },
34
89
  "publishConfig": {
35
90
  "access": "public"
36
91
  },
37
92
  "dependencies": {
93
+ "@fontsource-variable/noto-sans-bengali": "^5.2.11",
94
+ "@hugeicons/core-free-icons": "^3.1.1",
95
+ "@hugeicons/react": "^1.1.5",
96
+ "@marsidev/react-turnstile": "^1.4.2",
97
+ "@tailwindcss/vite": "^4.2.1",
98
+ "class-variance-authority": "^0.7.1",
99
+ "clsx": "^2.1.1",
38
100
  "glin-profanity": "^3.3.0",
101
+ "input-otp": "^1.4.2",
102
+ "install": "^0.13.0",
103
+ "next-themes": "^0.4.6",
39
104
  "openai": "^6.22.0",
40
- "sonner": "^2.0.7"
105
+ "radix-ui": "^1.4.3",
106
+ "react": "^19.2.4",
107
+ "react-day-picker": "^9.13.2",
108
+ "react-dom": "^19.2.4",
109
+ "recharts": "^2.15.4",
110
+ "shadcn": "^3.8.5",
111
+ "sonner": "^2.0.7",
112
+ "tailwind-merge": "^3.5.0",
113
+ "tailwindcss": "^4.2.1",
114
+ "tw-animate-css": "^1.4.0",
115
+ "vaul": "^1.1.2"
41
116
  }
42
117
  }
package/src/App.tsx ADDED
@@ -0,0 +1,28 @@
1
+ import { useState } from "react";
2
+ import { Button } from "./components/ui/button";
3
+ import { Calendar } from "./components/ui/calendar";
4
+
5
+ const App = () => {
6
+ const [date, setDate] = useState<Date | undefined>(new Date());
7
+
8
+ return (
9
+ <div className="flex min-h-dvh flex-col gap-y-6 p-5 md:p-8">
10
+ <h1 className="font-bold text-lg">Welcome to library playground!</h1>
11
+ <p>This is the core library package for "হবে নাকি Coffee?" projects.</p>
12
+
13
+ <div>
14
+ <Button>Click me</Button>
15
+
16
+ <Calendar
17
+ captionLayout="dropdown"
18
+ className="rounded-lg border"
19
+ mode="single"
20
+ onSelect={setDate}
21
+ selected={date}
22
+ />
23
+ </div>
24
+ </div>
25
+ );
26
+ };
27
+
28
+ export default App;
@@ -0,0 +1,47 @@
1
+ import { Turnstile, type TurnstileInstance } from "@marsidev/react-turnstile";
2
+ import { useRef } from "react";
3
+ import { useTheme } from "@/providers/theme-provider";
4
+
5
+ interface TurnstileCaptchaProps {
6
+ onError?: (error: string) => void;
7
+ onTokenChange: (token: string) => void;
8
+ }
9
+
10
+ export function TurnstileCaptcha({
11
+ onTokenChange,
12
+ onError,
13
+ }: TurnstileCaptchaProps) {
14
+ const { theme } = useTheme();
15
+ const turnstileRef = useRef<TurnstileInstance | null>(null);
16
+
17
+ function handleSuccess(token: string) {
18
+ onTokenChange(token);
19
+ }
20
+
21
+ function handleExpire() {
22
+ onTokenChange("");
23
+ onError?.("CAPTCHA has expired. Please verify again.");
24
+ }
25
+
26
+ function handleError() {
27
+ onTokenChange("");
28
+ onError?.("CAPTCHA verification failed. Please try again.");
29
+ }
30
+
31
+ return (
32
+ <div className="rounded-xl border border-input bg-input">
33
+ <Turnstile
34
+ className="overflow-hidden rounded-[calc(var(--radius)+5px)]"
35
+ onError={handleError}
36
+ onExpire={handleExpire}
37
+ onSuccess={handleSuccess}
38
+ options={{
39
+ theme: theme === "system" ? "auto" : theme,
40
+ size: "flexible",
41
+ }}
42
+ ref={turnstileRef}
43
+ siteKey={import.meta.env.VITE_TURNSTILE_SITE_KEY || ""}
44
+ />
45
+ </div>
46
+ );
47
+ }
@@ -0,0 +1,196 @@
1
+ import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
2
+ import type * as React from "react";
3
+ import { Button } from "@/components/ui/button";
4
+ import { cn } from "@/lib/utils";
5
+
6
+ function AlertDialog({
7
+ ...props
8
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
9
+ return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
10
+ }
11
+
12
+ function AlertDialogTrigger({
13
+ ...props
14
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
15
+ return (
16
+ <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
17
+ );
18
+ }
19
+
20
+ function AlertDialogPortal({
21
+ ...props
22
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
23
+ return (
24
+ <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
25
+ );
26
+ }
27
+
28
+ function AlertDialogOverlay({
29
+ className,
30
+ ...props
31
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
32
+ return (
33
+ <AlertDialogPrimitive.Overlay
34
+ className={cn(
35
+ "data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs",
36
+ className
37
+ )}
38
+ data-slot="alert-dialog-overlay"
39
+ {...props}
40
+ />
41
+ );
42
+ }
43
+
44
+ function AlertDialogContent({
45
+ className,
46
+ size = "default",
47
+ ...props
48
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Content> & {
49
+ size?: "default" | "sm";
50
+ }) {
51
+ return (
52
+ <AlertDialogPortal>
53
+ <AlertDialogOverlay />
54
+ <AlertDialogPrimitive.Content
55
+ className={cn(
56
+ "data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-6 rounded-xl bg-background p-6 outline-none ring-1 ring-foreground/10 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-closed:animate-out data-open:animate-in data-[size=default]:sm:max-w-lg",
57
+ className
58
+ )}
59
+ data-size={size}
60
+ data-slot="alert-dialog-content"
61
+ {...props}
62
+ />
63
+ </AlertDialogPortal>
64
+ );
65
+ }
66
+
67
+ function AlertDialogHeader({
68
+ className,
69
+ ...props
70
+ }: React.ComponentProps<"div">) {
71
+ return (
72
+ <div
73
+ className={cn(
74
+ "grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-6 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
75
+ className
76
+ )}
77
+ data-slot="alert-dialog-header"
78
+ {...props}
79
+ />
80
+ );
81
+ }
82
+
83
+ function AlertDialogFooter({
84
+ className,
85
+ ...props
86
+ }: React.ComponentProps<"div">) {
87
+ return (
88
+ <div
89
+ className={cn(
90
+ "flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
91
+ className
92
+ )}
93
+ data-slot="alert-dialog-footer"
94
+ {...props}
95
+ />
96
+ );
97
+ }
98
+
99
+ function AlertDialogMedia({
100
+ className,
101
+ ...props
102
+ }: React.ComponentProps<"div">) {
103
+ return (
104
+ <div
105
+ className={cn(
106
+ "mb-2 inline-flex size-16 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-8",
107
+ className
108
+ )}
109
+ data-slot="alert-dialog-media"
110
+ {...props}
111
+ />
112
+ );
113
+ }
114
+
115
+ function AlertDialogTitle({
116
+ className,
117
+ ...props
118
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
119
+ return (
120
+ <AlertDialogPrimitive.Title
121
+ className={cn(
122
+ "font-medium text-lg sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
123
+ className
124
+ )}
125
+ data-slot="alert-dialog-title"
126
+ {...props}
127
+ />
128
+ );
129
+ }
130
+
131
+ function AlertDialogDescription({
132
+ className,
133
+ ...props
134
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
135
+ return (
136
+ <AlertDialogPrimitive.Description
137
+ className={cn(
138
+ "text-balance text-muted-foreground text-sm md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
139
+ className
140
+ )}
141
+ data-slot="alert-dialog-description"
142
+ {...props}
143
+ />
144
+ );
145
+ }
146
+
147
+ function AlertDialogAction({
148
+ className,
149
+ variant = "default",
150
+ size = "default",
151
+ ...props
152
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Action> &
153
+ Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
154
+ return (
155
+ <Button asChild size={size} variant={variant}>
156
+ <AlertDialogPrimitive.Action
157
+ className={cn(className)}
158
+ data-slot="alert-dialog-action"
159
+ {...props}
160
+ />
161
+ </Button>
162
+ );
163
+ }
164
+
165
+ function AlertDialogCancel({
166
+ className,
167
+ variant = "outline",
168
+ size = "default",
169
+ ...props
170
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Cancel> &
171
+ Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
172
+ return (
173
+ <Button asChild size={size} variant={variant}>
174
+ <AlertDialogPrimitive.Cancel
175
+ className={cn(className)}
176
+ data-slot="alert-dialog-cancel"
177
+ {...props}
178
+ />
179
+ </Button>
180
+ );
181
+ }
182
+
183
+ export {
184
+ AlertDialog,
185
+ AlertDialogAction,
186
+ AlertDialogCancel,
187
+ AlertDialogContent,
188
+ AlertDialogDescription,
189
+ AlertDialogFooter,
190
+ AlertDialogHeader,
191
+ AlertDialogMedia,
192
+ AlertDialogOverlay,
193
+ AlertDialogPortal,
194
+ AlertDialogTitle,
195
+ AlertDialogTrigger,
196
+ };
@@ -0,0 +1,76 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import type * as React from "react";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ const alertVariants = cva(
7
+ "group/alert relative grid w-full gap-0.5 rounded-lg border px-3 py-3 text-left text-xs has-data-[slot=alert-action]:relative has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 has-data-[slot=alert-action]:pr-18 *:[svg:not([class*='size-'])]:size-4 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-card text-card-foreground",
12
+ destructive:
13
+ "border-destructive/15 bg-destructive/5 text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "default",
18
+ },
19
+ }
20
+ );
21
+
22
+ function Alert({
23
+ className,
24
+ variant,
25
+ ...props
26
+ }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
27
+ return (
28
+ <div
29
+ className={cn(alertVariants({ variant }), className)}
30
+ data-slot="alert"
31
+ role="alert"
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+
37
+ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
38
+ return (
39
+ <div
40
+ className={cn(
41
+ "font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
42
+ className
43
+ )}
44
+ data-slot="alert-title"
45
+ {...props}
46
+ />
47
+ );
48
+ }
49
+
50
+ function AlertDescription({
51
+ className,
52
+ ...props
53
+ }: React.ComponentProps<"div">) {
54
+ return (
55
+ <div
56
+ className={cn(
57
+ "text-balance text-muted-foreground text-sm md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
58
+ className
59
+ )}
60
+ data-slot="alert-description"
61
+ {...props}
62
+ />
63
+ );
64
+ }
65
+
66
+ function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
67
+ return (
68
+ <div
69
+ className={cn("absolute top-2.5 right-3", className)}
70
+ data-slot="alert-action"
71
+ {...props}
72
+ />
73
+ );
74
+ }
75
+
76
+ export { Alert, AlertTitle, AlertDescription, AlertAction };
@@ -0,0 +1,110 @@
1
+ import { Avatar as AvatarPrimitive } from "radix-ui";
2
+ import type * as React from "react";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ function Avatar({
7
+ className,
8
+ size = "default",
9
+ ...props
10
+ }: React.ComponentProps<typeof AvatarPrimitive.Root> & {
11
+ size?: "default" | "sm" | "lg";
12
+ }) {
13
+ return (
14
+ <AvatarPrimitive.Root
15
+ className={cn(
16
+ "group/avatar relative flex size-8 shrink-0 select-none rounded-full after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten",
17
+ className
18
+ )}
19
+ data-size={size}
20
+ data-slot="avatar"
21
+ {...props}
22
+ />
23
+ );
24
+ }
25
+
26
+ function AvatarImage({
27
+ className,
28
+ ...props
29
+ }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
30
+ return (
31
+ <AvatarPrimitive.Image
32
+ className={cn(
33
+ "aspect-square size-full rounded-full object-cover",
34
+ className
35
+ )}
36
+ data-slot="avatar-image"
37
+ {...props}
38
+ />
39
+ );
40
+ }
41
+
42
+ function AvatarFallback({
43
+ className,
44
+ ...props
45
+ }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
46
+ return (
47
+ <AvatarPrimitive.Fallback
48
+ className={cn(
49
+ "flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground text-sm group-data-[size=sm]/avatar:text-xs",
50
+ className
51
+ )}
52
+ data-slot="avatar-fallback"
53
+ {...props}
54
+ />
55
+ );
56
+ }
57
+
58
+ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
59
+ return (
60
+ <span
61
+ className={cn(
62
+ "absolute right-0 bottom-0 z-10 inline-flex select-none items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background",
63
+ "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
64
+ "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
65
+ "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
66
+ className
67
+ )}
68
+ data-slot="avatar-badge"
69
+ {...props}
70
+ />
71
+ );
72
+ }
73
+
74
+ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
75
+ return (
76
+ <div
77
+ className={cn(
78
+ "group/avatar-group flex -gap-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
79
+ className
80
+ )}
81
+ data-slot="avatar-group"
82
+ {...props}
83
+ />
84
+ );
85
+ }
86
+
87
+ function AvatarGroupCount({
88
+ className,
89
+ ...props
90
+ }: React.ComponentProps<"div">) {
91
+ return (
92
+ <div
93
+ className={cn(
94
+ "relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground text-sm ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
95
+ className
96
+ )}
97
+ data-slot="avatar-group-count"
98
+ {...props}
99
+ />
100
+ );
101
+ }
102
+
103
+ export {
104
+ Avatar,
105
+ AvatarImage,
106
+ AvatarFallback,
107
+ AvatarGroup,
108
+ AvatarGroupCount,
109
+ AvatarBadge,
110
+ };