create-bdpamke-react-scaffold 1.0.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 (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -0
  3. package/bin/create-bdpamke-react-scaffold.js +101 -0
  4. package/package.json +39 -0
  5. package/template/.env.example +6 -0
  6. package/template/FUNCTIONS_EXAMPLES.md +480 -0
  7. package/template/HOWTOadd a page.md +166 -0
  8. package/template/REACT_PROPS_USEEFFECT.md +210 -0
  9. package/template/REGISTRATION_FLOW.md +268 -0
  10. package/template/USESTATE_EXAMPLES.md +451 -0
  11. package/template/components.json +20 -0
  12. package/template/index.html +13 -0
  13. package/template/jsconfig.json +19 -0
  14. package/template/package-lock.json +5988 -0
  15. package/template/package.json +73 -0
  16. package/template/postcss.config.cjs +6 -0
  17. package/template/public/images/BDPA_edited.png +0 -0
  18. package/template/server/server.js +86 -0
  19. package/template/server/utils/apiClient.js +59 -0
  20. package/template/server/utils/password.js +60 -0
  21. package/template/src/App.jsx +10 -0
  22. package/template/src/components/layout/Container.jsx +7 -0
  23. package/template/src/components/layout/Section.jsx +7 -0
  24. package/template/src/components/ui/accordion.jsx +41 -0
  25. package/template/src/components/ui/alert-dialog.jsx +99 -0
  26. package/template/src/components/ui/alert.jsx +47 -0
  27. package/template/src/components/ui/aspect-ratio.jsx +5 -0
  28. package/template/src/components/ui/avatar.jsx +35 -0
  29. package/template/src/components/ui/badge.jsx +34 -0
  30. package/template/src/components/ui/button.jsx +47 -0
  31. package/template/src/components/ui/calendar.jsx +173 -0
  32. package/template/src/components/ui/card.jsx +50 -0
  33. package/template/src/components/ui/carousel.jsx +194 -0
  34. package/template/src/components/ui/checkbox.jsx +22 -0
  35. package/template/src/components/ui/collapsible.jsx +11 -0
  36. package/template/src/components/ui/command.jsx +116 -0
  37. package/template/src/components/ui/dialog.jsx +94 -0
  38. package/template/src/components/ui/drawer.jsx +92 -0
  39. package/template/src/components/ui/dropdown-menu.jsx +155 -0
  40. package/template/src/components/ui/form.jsx +138 -0
  41. package/template/src/components/ui/hover-card.jsx +25 -0
  42. package/template/src/components/ui/icons.jsx +81 -0
  43. package/template/src/components/ui/input.jsx +19 -0
  44. package/template/src/components/ui/label.jsx +16 -0
  45. package/template/src/components/ui/menubar.jsx +200 -0
  46. package/template/src/components/ui/navigation-menu.jsx +104 -0
  47. package/template/src/components/ui/popover.jsx +25 -0
  48. package/template/src/components/ui/progress.jsx +20 -0
  49. package/template/src/components/ui/radio-group.jsx +29 -0
  50. package/template/src/components/ui/scroll-area.jsx +40 -0
  51. package/template/src/components/ui/select.jsx +120 -0
  52. package/template/src/components/ui/separator.jsx +25 -0
  53. package/template/src/components/ui/sheet.jsx +108 -0
  54. package/template/src/components/ui/skeleton.jsx +10 -0
  55. package/template/src/components/ui/slider.jsx +23 -0
  56. package/template/src/components/ui/sonner.jsx +42 -0
  57. package/template/src/components/ui/switch.jsx +24 -0
  58. package/template/src/components/ui/table.jsx +83 -0
  59. package/template/src/components/ui/tabs.jsx +41 -0
  60. package/template/src/components/ui/textarea.jsx +18 -0
  61. package/template/src/components/ui/toast.jsx +82 -0
  62. package/template/src/components/ui/toaster.jsx +33 -0
  63. package/template/src/components/ui/toggle.jsx +40 -0
  64. package/template/src/components/ui/tooltip.jsx +24 -0
  65. package/template/src/hooks/use-toast.js +155 -0
  66. package/template/src/index.css +61 -0
  67. package/template/src/index.js +6 -0
  68. package/template/src/lib/utils.js +11 -0
  69. package/template/src/main.jsx +15 -0
  70. package/template/src/pages/Home.jsx +26 -0
  71. package/template/tailwind.config.cjs +76 -0
  72. package/template/vite.config.mts +22 -0
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "bdpa-react-scaffold-app",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "build": "vite build",
8
+ "preview": "vite preview",
9
+ "server": "node server/server.js",
10
+ "dev:server": "nodemon server/server.js",
11
+ "dev:all": "concurrently \"npm run dev\" \"npm run dev:server\""
12
+ },
13
+ "dependencies": {
14
+ "@hookform/resolvers": "^5.2.2",
15
+ "@radix-ui/react-accordion": "^1.2.12",
16
+ "@radix-ui/react-alert-dialog": "^1.1.15",
17
+ "@radix-ui/react-aspect-ratio": "^1.1.8",
18
+ "@radix-ui/react-avatar": "^1.1.11",
19
+ "@radix-ui/react-checkbox": "^1.3.3",
20
+ "@radix-ui/react-collapsible": "^1.1.12",
21
+ "@radix-ui/react-dialog": "^1.1.15",
22
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
23
+ "@radix-ui/react-hover-card": "^1.1.15",
24
+ "@radix-ui/react-label": "^2.1.8",
25
+ "@radix-ui/react-menubar": "^1.1.16",
26
+ "@radix-ui/react-navigation-menu": "^1.2.14",
27
+ "@radix-ui/react-popover": "^1.1.15",
28
+ "@radix-ui/react-progress": "^1.1.8",
29
+ "@radix-ui/react-radio-group": "^1.3.8",
30
+ "@radix-ui/react-scroll-area": "^1.2.10",
31
+ "@radix-ui/react-select": "^2.2.6",
32
+ "@radix-ui/react-separator": "^1.1.8",
33
+ "@radix-ui/react-slider": "^1.3.6",
34
+ "@radix-ui/react-slot": "^1.2.4",
35
+ "@radix-ui/react-switch": "^1.2.6",
36
+ "@radix-ui/react-tabs": "^1.1.13",
37
+ "@radix-ui/react-toast": "^1.2.15",
38
+ "@radix-ui/react-toggle": "^1.1.10",
39
+ "@radix-ui/react-tooltip": "^1.2.8",
40
+ "axios": "^1.6.8",
41
+ "bcryptjs": "^2.4.3",
42
+ "class-variance-authority": "^0.7.0",
43
+ "clsx": "^2.1.0",
44
+ "cmdk": "^1.1.1",
45
+ "cors": "^2.8.5",
46
+ "date-fns": "^4.1.0",
47
+ "embla-carousel-react": "^8.6.0",
48
+ "express": "^4.18.2",
49
+ "lucide-react": "^0.575.0",
50
+ "next-themes": "^0.4.6",
51
+ "react": "^18.2.0",
52
+ "react-day-picker": "^9.14.0",
53
+ "react-dom": "^18.2.0",
54
+ "react-hook-form": "^7.72.1",
55
+ "react-router-dom": "^6.20.0",
56
+ "sonner": "^2.0.7",
57
+ "tailwind-merge": "^2.2.1",
58
+ "vaul": "^1.1.2",
59
+ "zod": "^4.3.6"
60
+ },
61
+ "devDependencies": {
62
+ "@types/react": "^18.2.0",
63
+ "@types/react-dom": "^18.2.0",
64
+ "@vitejs/plugin-react-swc": "^3.5.0",
65
+ "autoprefixer": "^10.4.20",
66
+ "concurrently": "^8.2.2",
67
+ "nodemon": "^3.0.2",
68
+ "postcss": "^8.4.47",
69
+ "tailwindcss": "^3.4.0",
70
+ "tailwindcss-animate": "^1.0.7",
71
+ "vite": "^5.0.0"
72
+ }
73
+ }
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {}
5
+ }
6
+ };
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ const express = require("express");
3
+ const cors = require("cors");
4
+ const bcrypt = require("bcryptjs");
5
+ const { ApiClient } = require("./utils/apiClient");
6
+
7
+ const app = express();
8
+ const PORT = 5000;
9
+
10
+ // ── Middleware ──────────────────────────────────────────────────────────────
11
+ app.use(cors({
12
+ origin: process.env.CLIENT_ORIGIN || "http://localhost:3000",
13
+ credentials: true,
14
+ }));
15
+ app.use(express.json());
16
+
17
+ // ── Password routes ─────────────────────────────────────────────────────────
18
+
19
+ /**
20
+ * POST /api/auth/hash
21
+ * Body: { password: string }
22
+ * Returns: { hash: string }
23
+ */
24
+ app.post("/api/auth/hash", async (req, res) => {
25
+ const { password } = req.body;
26
+ if (!password || typeof password !== "string" || password.trim() === "") {
27
+ return res.status(400).json({ error: "Password must be a non-empty string." });
28
+ }
29
+ try {
30
+ const hash = await bcrypt.hash(password, 10);
31
+ return res.json({ hash });
32
+ } catch (err) {
33
+ return res.status(500).json({ error: "Failed to hash password." });
34
+ }
35
+ });
36
+
37
+ /**
38
+ * POST /api/auth/verify
39
+ * Body: { password: string, hash: string }
40
+ * Returns: { valid: boolean }
41
+ */
42
+ app.post("/api/auth/verify", async (req, res) => {
43
+ const { password, hash } = req.body;
44
+ if (!password || typeof password !== "string") {
45
+ return res.status(400).json({ error: "Password must be a non-empty string." });
46
+ }
47
+ if (!hash || typeof hash !== "string") {
48
+ return res.status(400).json({ error: "Hash must be a valid string." });
49
+ }
50
+ try {
51
+ const valid = await bcrypt.compare(password, hash);
52
+ return res.json({ valid });
53
+ } catch (err) {
54
+ return res.status(500).json({ error: "Failed to verify password." });
55
+ }
56
+ });
57
+
58
+ // ── Generic external-API proxy ───────────────────────────────────────────────
59
+ // Forwards GET /api/proxy?url=<encoded-url> to an external service.
60
+ // Use for fetching third-party data server-side to avoid CORS issues.
61
+ app.get("/api/proxy", async (req, res) => {
62
+ const targetUrl = req.query.url;
63
+ if (!targetUrl) {
64
+ return res.status(400).json({ error: "Missing required query param: url" });
65
+ }
66
+ try {
67
+ const client = new ApiClient("");
68
+ const result = await client.getAll(targetUrl);
69
+ if (!result.success) {
70
+ return res.status(502).json({ error: result.error });
71
+ }
72
+ return res.json(result.data);
73
+ } catch (err) {
74
+ return res.status(500).json({ error: "Proxy request failed." });
75
+ }
76
+ });
77
+
78
+ // ── Health check ─────────────────────────────────────────────────────────────
79
+ app.get("/api/health", (_req, res) => {
80
+ res.json({ status: "ok", timestamp: new Date().toISOString() });
81
+ });
82
+
83
+ // ── Start ─────────────────────────────────────────────────────────────────────
84
+ app.listen(PORT, () => {
85
+ console.log(`🚀 Server running on http://localhost:${PORT}`);
86
+ });
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ const axios = require("axios");
3
+
4
+ /**
5
+ * Server-side Axios helper — mirrors the client ApiClient shape.
6
+ */
7
+ class ApiClient {
8
+ constructor(baseURL = "") {
9
+ this.instance = axios.create({
10
+ baseURL,
11
+ headers: { "Content-Type": "application/json" },
12
+ });
13
+ }
14
+
15
+ setToken(token) {
16
+ if (token) {
17
+ this.instance.defaults.headers.common["Authorization"] = "Bearer " + token;
18
+ } else {
19
+ delete this.instance.defaults.headers.common["Authorization"];
20
+ }
21
+ }
22
+
23
+ async request(config) {
24
+ try {
25
+ const res = await this.instance.request(config);
26
+ return { success: true, data: res.data };
27
+ } catch (error) {
28
+ const message =
29
+ error?.response?.data?.message || error?.message || "Request failed";
30
+ return { success: false, error: message };
31
+ }
32
+ }
33
+
34
+ async getAll(url, config = {}) {
35
+ return this.request({ url, method: "GET", ...config });
36
+ }
37
+
38
+ async getOne(url, config = {}) {
39
+ return this.request({ url, method: "GET", ...config });
40
+ }
41
+
42
+ async create(url, data, config = {}) {
43
+ return this.request({ url, method: "POST", data, ...config });
44
+ }
45
+
46
+ async update(url, data, config = {}) {
47
+ return this.request({ url, method: "PUT", data, ...config });
48
+ }
49
+
50
+ async patch(url, data, config = {}) {
51
+ return this.request({ url, method: "PATCH", data, ...config });
52
+ }
53
+
54
+ async delete(url, config = {}) {
55
+ return this.request({ url, method: "DELETE", ...config });
56
+ }
57
+ }
58
+
59
+ module.exports = { ApiClient };
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ const bcrypt = require("bcryptjs");
3
+
4
+ const SALT_ROUNDS = 10;
5
+
6
+ /**
7
+ * Hash a plain-text password.
8
+ * @param {string} password
9
+ * @returns {Promise<string>} bcrypt hash
10
+ */
11
+ async function hashPassword(password) {
12
+ if (!password || typeof password !== "string" || password.trim().length === 0) {
13
+ throw new Error("Password must be a non-empty string.");
14
+ }
15
+ return bcrypt.hash(password, SALT_ROUNDS);
16
+ }
17
+
18
+ /**
19
+ * Compare a plain-text password against a bcrypt hash.
20
+ * @param {string} password
21
+ * @param {string} hash
22
+ * @returns {Promise<boolean>}
23
+ */
24
+ async function verifyPassword(password, hash) {
25
+ if (!password || typeof password !== "string") {
26
+ throw new Error("Password must be a non-empty string.");
27
+ }
28
+ if (!hash || typeof hash !== "string") {
29
+ throw new Error("Hash must be a valid string.");
30
+ }
31
+ return bcrypt.compare(password, hash);
32
+ }
33
+
34
+ /**
35
+ * Return a numeric strength score 0-4 for the given password.
36
+ * @param {string} password
37
+ * @returns {number}
38
+ */
39
+ function getPasswordStrength(password) {
40
+ if (!password) return 0;
41
+ let score = 0;
42
+ if (password.length >= 8) score++;
43
+ if (password.length >= 12) score++;
44
+ if (/[a-z]/.test(password) && /[A-Z]/.test(password)) score++;
45
+ if (/d/.test(password)) score++;
46
+ if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) score++;
47
+ return Math.min(score, 4);
48
+ }
49
+
50
+ /**
51
+ * Return a human-readable label for the password strength.
52
+ * @param {string} password
53
+ * @returns {string}
54
+ */
55
+ function getPasswordStrengthLabel(password) {
56
+ const labels = ["Weak", "Fair", "Good", "Strong", "Very Strong"];
57
+ return labels[getPasswordStrength(password)];
58
+ }
59
+
60
+ module.exports = { hashPassword, verifyPassword, getPasswordStrength, getPasswordStrengthLabel };
@@ -0,0 +1,10 @@
1
+ import { Route, Routes } from "react-router-dom";
2
+ import Home from "./pages/Home.jsx";
3
+
4
+ export default function App() {
5
+ return (
6
+ <Routes>
7
+ <Route path="/" element={<Home />} />
8
+ </Routes>
9
+ );
10
+ }
@@ -0,0 +1,7 @@
1
+ export default function Container({ children, className = "" }) {
2
+ return (
3
+ <div className={`max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 ${className}`}>
4
+ {children}
5
+ </div>
6
+ );
7
+ }
@@ -0,0 +1,7 @@
1
+ export default function Section({ children, className = "" }) {
2
+ return (
3
+ <section className={`py-12 ${className}`}>
4
+ {children}
5
+ </section>
6
+ );
7
+ }
@@ -0,0 +1,41 @@
1
+ import * as React from "react"
2
+ import * as AccordionPrimitive from "@radix-ui/react-accordion"
3
+ import { ChevronDown } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Accordion = AccordionPrimitive.Root
8
+
9
+ const AccordionItem = React.forwardRef(({ className, ...props }, ref) => (
10
+ <AccordionPrimitive.Item ref={ref} className={cn("border-b", className)} {...props} />
11
+ ))
12
+ AccordionItem.displayName = "AccordionItem"
13
+
14
+ const AccordionTrigger = React.forwardRef(({ className, children, ...props }, ref) => (
15
+ <AccordionPrimitive.Header className="flex">
16
+ <AccordionPrimitive.Trigger
17
+ ref={ref}
18
+ className={cn(
19
+ "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
20
+ className
21
+ )}
22
+ {...props}>
23
+ {children}
24
+ <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
25
+ </AccordionPrimitive.Trigger>
26
+ </AccordionPrimitive.Header>
27
+ ))
28
+ AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
29
+
30
+ const AccordionContent = React.forwardRef(({ className, children, ...props }, ref) => (
31
+ <AccordionPrimitive.Content
32
+ ref={ref}
33
+ className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
34
+ {...props}>
35
+ <div className={cn("pb-4 pt-0", className)}>{children}</div>
36
+ </AccordionPrimitive.Content>
37
+ ))
38
+
39
+ AccordionContent.displayName = AccordionPrimitive.Content.displayName
40
+
41
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
@@ -0,0 +1,99 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
5
+
6
+ import { cn } from "@/lib/utils"
7
+ import { buttonVariants } from "@/components/ui/button"
8
+
9
+ const AlertDialog = AlertDialogPrimitive.Root
10
+
11
+ const AlertDialogTrigger = AlertDialogPrimitive.Trigger
12
+
13
+ const AlertDialogPortal = AlertDialogPrimitive.Portal
14
+
15
+ const AlertDialogOverlay = React.forwardRef(({ className, ...props }, ref) => (
16
+ <AlertDialogPrimitive.Overlay
17
+ className={cn(
18
+ "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",
19
+ className
20
+ )}
21
+ {...props}
22
+ ref={ref} />
23
+ ))
24
+ AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
25
+
26
+ const AlertDialogContent = React.forwardRef(({ className, ...props }, ref) => (
27
+ <AlertDialogPortal>
28
+ <AlertDialogOverlay />
29
+ <AlertDialogPrimitive.Content
30
+ ref={ref}
31
+ className={cn(
32
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
33
+ className
34
+ )}
35
+ {...props} />
36
+ </AlertDialogPortal>
37
+ ))
38
+ AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
39
+
40
+ const AlertDialogHeader = ({
41
+ className,
42
+ ...props
43
+ }) => (
44
+ <div
45
+ className={cn("flex flex-col space-y-2 text-center sm:text-left", className)}
46
+ {...props} />
47
+ )
48
+ AlertDialogHeader.displayName = "AlertDialogHeader"
49
+
50
+ const AlertDialogFooter = ({
51
+ className,
52
+ ...props
53
+ }) => (
54
+ <div
55
+ className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
56
+ {...props} />
57
+ )
58
+ AlertDialogFooter.displayName = "AlertDialogFooter"
59
+
60
+ const AlertDialogTitle = React.forwardRef(({ className, ...props }, ref) => (
61
+ <AlertDialogPrimitive.Title ref={ref} className={cn("text-lg font-semibold", className)} {...props} />
62
+ ))
63
+ AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
64
+
65
+ const AlertDialogDescription = React.forwardRef(({ className, ...props }, ref) => (
66
+ <AlertDialogPrimitive.Description
67
+ ref={ref}
68
+ className={cn("text-sm text-muted-foreground", className)}
69
+ {...props} />
70
+ ))
71
+ AlertDialogDescription.displayName =
72
+ AlertDialogPrimitive.Description.displayName
73
+
74
+ const AlertDialogAction = React.forwardRef(({ className, ...props }, ref) => (
75
+ <AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
76
+ ))
77
+ AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
78
+
79
+ const AlertDialogCancel = React.forwardRef(({ className, ...props }, ref) => (
80
+ <AlertDialogPrimitive.Cancel
81
+ ref={ref}
82
+ className={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
83
+ {...props} />
84
+ ))
85
+ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
86
+
87
+ export {
88
+ AlertDialog,
89
+ AlertDialogPortal,
90
+ AlertDialogOverlay,
91
+ AlertDialogTrigger,
92
+ AlertDialogContent,
93
+ AlertDialogHeader,
94
+ AlertDialogFooter,
95
+ AlertDialogTitle,
96
+ AlertDialogDescription,
97
+ AlertDialogAction,
98
+ AlertDialogCancel,
99
+ }
@@ -0,0 +1,47 @@
1
+ import * as React from "react"
2
+ import { cva } from "class-variance-authority";
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const alertVariants = cva(
7
+ "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-background text-foreground",
12
+ destructive:
13
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "default",
18
+ },
19
+ }
20
+ )
21
+
22
+ const Alert = React.forwardRef(({ className, variant, ...props }, ref) => (
23
+ <div
24
+ ref={ref}
25
+ role="alert"
26
+ className={cn(alertVariants({ variant }), className)}
27
+ {...props} />
28
+ ))
29
+ Alert.displayName = "Alert"
30
+
31
+ const AlertTitle = React.forwardRef(({ className, ...props }, ref) => (
32
+ <h5
33
+ ref={ref}
34
+ className={cn("mb-1 font-medium leading-none tracking-tight", className)}
35
+ {...props} />
36
+ ))
37
+ AlertTitle.displayName = "AlertTitle"
38
+
39
+ const AlertDescription = React.forwardRef(({ className, ...props }, ref) => (
40
+ <div
41
+ ref={ref}
42
+ className={cn("text-sm [&_p]:leading-relaxed", className)}
43
+ {...props} />
44
+ ))
45
+ AlertDescription.displayName = "AlertDescription"
46
+
47
+ export { Alert, AlertTitle, AlertDescription }
@@ -0,0 +1,5 @@
1
+ import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
2
+
3
+ const AspectRatio = AspectRatioPrimitive.Root
4
+
5
+ export { AspectRatio }
@@ -0,0 +1,35 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Avatar = React.forwardRef(({ className, ...props }, ref) => (
9
+ <AvatarPrimitive.Root
10
+ ref={ref}
11
+ className={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)}
12
+ {...props} />
13
+ ))
14
+ Avatar.displayName = AvatarPrimitive.Root.displayName
15
+
16
+ const AvatarImage = React.forwardRef(({ className, ...props }, ref) => (
17
+ <AvatarPrimitive.Image
18
+ ref={ref}
19
+ className={cn("aspect-square h-full w-full", className)}
20
+ {...props} />
21
+ ))
22
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName
23
+
24
+ const AvatarFallback = React.forwardRef(({ className, ...props }, ref) => (
25
+ <AvatarPrimitive.Fallback
26
+ ref={ref}
27
+ className={cn(
28
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
29
+ className
30
+ )}
31
+ {...props} />
32
+ ))
33
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
34
+
35
+ export { Avatar, AvatarImage, AvatarFallback }
@@ -0,0 +1,34 @@
1
+ import * as React from "react"
2
+ import { cva } from "class-variance-authority";
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const badgeVariants = cva(
7
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13
+ secondary:
14
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15
+ destructive:
16
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17
+ outline: "text-foreground",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ },
23
+ }
24
+ )
25
+
26
+ function Badge({
27
+ className,
28
+ variant,
29
+ ...props
30
+ }) {
31
+ return (<div className={cn(badgeVariants({ variant }), className)} {...props} />);
32
+ }
33
+
34
+ export { Badge, badgeVariants }
@@ -0,0 +1,47 @@
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva } from "class-variance-authority";
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
+ destructive:
14
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15
+ outline:
16
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17
+ secondary:
18
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ ghost: "hover:bg-accent hover:text-accent-foreground",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ },
22
+ size: {
23
+ default: "h-10 px-4 py-2",
24
+ sm: "h-9 rounded-md px-3",
25
+ lg: "h-11 rounded-md px-8",
26
+ icon: "h-10 w-10",
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: "default",
31
+ size: "default",
32
+ },
33
+ }
34
+ )
35
+
36
+ const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
37
+ const Comp = asChild ? Slot : "button"
38
+ return (
39
+ <Comp
40
+ className={cn(buttonVariants({ variant, size, className }))}
41
+ ref={ref}
42
+ {...props} />
43
+ );
44
+ })
45
+ Button.displayName = "Button"
46
+
47
+ export { Button, buttonVariants }