@salesforce/webapp-template-app-react-sample-b2e-experimental 1.49.0 → 1.50.1

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 (58) hide show
  1. package/dist/CHANGELOG.md +16 -0
  2. package/dist/force-app/main/default/data/Property__c.json +2 -2
  3. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/dashboard.ts +170 -0
  4. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/maintenance.ts +221 -0
  5. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/properties.ts +157 -0
  6. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/utils.ts +4 -0
  7. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/appLayout.tsx +20 -8
  8. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/appliances.svg +13 -0
  9. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/dashboard.svg +4 -0
  10. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/downgraph.svg +3 -0
  11. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/electrical.svg +41 -0
  12. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/files.svg +7 -0
  13. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/hvac.svg +79 -0
  14. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/maintenance.svg +4 -0
  15. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/pest.svg +5 -0
  16. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/plumbing.svg +7 -0
  17. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/properties.svg +14 -0
  18. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/support.svg +6 -0
  19. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/upgraph.svg +3 -0
  20. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/users.svg +8 -0
  21. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/zen-logo.svg +5 -0
  22. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/AnalyticsTile.tsx +29 -0
  23. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ApplicationCard.tsx +43 -0
  24. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/IssuesDonutChart.tsx +66 -0
  25. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/MaintenanceRequestCard.tsx +71 -0
  26. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/MaintenanceTable.tsx +110 -0
  27. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/PriorityBadge.tsx +29 -0
  28. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/PropertyCard.tsx +61 -0
  29. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/StatCard.tsx +52 -0
  30. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/StatusBadge.tsx +37 -0
  31. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/TopBar.tsx +72 -0
  32. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/UserAvatar.tsx +35 -0
  33. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/VerticalNav.tsx +54 -0
  34. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/alert.tsx +69 -0
  35. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/button.tsx +67 -0
  36. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/card.tsx +92 -0
  37. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/dialog.tsx +143 -0
  38. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/field.tsx +222 -0
  39. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/index.ts +72 -0
  40. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/input.tsx +19 -0
  41. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/label.tsx +19 -0
  42. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/pagination.tsx +112 -0
  43. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/select.tsx +183 -0
  44. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/separator.tsx +26 -0
  45. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/skeleton.tsx +14 -0
  46. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/spinner.tsx +15 -0
  47. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/table.tsx +87 -0
  48. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/tabs.tsx +78 -0
  49. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components.json +18 -0
  50. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/types.ts +57 -0
  51. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/utils.ts +6 -0
  52. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Home.tsx +163 -10
  53. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Maintenance.tsx +176 -0
  54. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Properties.tsx +94 -0
  55. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/routes.tsx +19 -7
  56. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/styles/global.css +160 -0
  57. package/dist/package.json +1 -1
  58. package/package.json +2 -2
@@ -0,0 +1,52 @@
1
+ import React from "react";
2
+ import { Card } from "@/components/ui/card";
3
+ import upgraphIcon from "../assets/icons/upgraph.svg";
4
+ import downgraphIcon from "../assets/icons/downgraph.svg";
5
+ import { MoreVertical } from "lucide-react";
6
+
7
+ interface StatCardProps {
8
+ title: string;
9
+ value: number | string;
10
+ trend?: {
11
+ value: number;
12
+ isPositive: boolean;
13
+ };
14
+ subtitle?: string;
15
+ onClick?: () => void;
16
+ }
17
+
18
+ export const StatCard: React.FC<StatCardProps> = ({ title, value, trend, subtitle, onClick }) => {
19
+ return (
20
+ <Card
21
+ className={`p-4 border-gray-200 shadow-sm relative ${onClick ? "cursor-pointer hover:shadow-lg transition-shadow" : ""}`}
22
+ onClick={onClick}
23
+ >
24
+ {/* Three-dot menu */}
25
+ <button className="absolute top-4 right-4 p-1 hover:bg-gray-100 rounded-md transition-colors">
26
+ <MoreVertical className="w-4 h-4 text-gray-400" />
27
+ </button>
28
+
29
+ <div className="space-y-1">
30
+ <p className="text-sm font-medium text-gray-600 uppercase tracking-wide">{title}</p>
31
+ <div className="flex items-baseline gap-3">
32
+ <p className="text-4xl font-bold text-primary-purple">{value}</p>
33
+ {trend && (
34
+ <span
35
+ className={`inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-sm font-medium ${
36
+ trend.isPositive ? "bg-cyan-100 text-cyan-800" : "bg-pink-100 text-pink-800"
37
+ }`}
38
+ >
39
+ <img
40
+ src={trend.isPositive ? upgraphIcon : downgraphIcon}
41
+ alt={trend.isPositive ? "Up" : "Down"}
42
+ className="w-4 h-4"
43
+ />
44
+ {Math.abs(trend.value)}%
45
+ </span>
46
+ )}
47
+ </div>
48
+ {subtitle && <p className="text-sm text-gray-500 mt-1">{subtitle}</p>}
49
+ </div>
50
+ </Card>
51
+ );
52
+ };
@@ -0,0 +1,37 @@
1
+ import React from "react";
2
+ import { Check } from "lucide-react";
3
+
4
+ interface StatusBadgeProps {
5
+ status: "new" | "assigned" | "scheduled" | "in_progress" | "completed";
6
+ }
7
+
8
+ export const StatusBadge: React.FC<StatusBadgeProps> = ({ status }) => {
9
+ const styles = {
10
+ new: "bg-pink-100 text-pink-700",
11
+ assigned: "bg-purple-100 text-purple-700",
12
+ scheduled: "bg-blue-100 text-blue-700",
13
+ in_progress: "bg-yellow-100 text-yellow-700",
14
+ completed: "bg-green-100 text-green-700",
15
+ };
16
+
17
+ const labels = {
18
+ new: "Needs Action",
19
+ assigned: "Assigned",
20
+ scheduled: "Scheduled",
21
+ in_progress: "In Progress",
22
+ completed: "Completed",
23
+ };
24
+
25
+ const showCheckmark = status === "completed";
26
+ const showDot = status === "new" || status === "in_progress";
27
+
28
+ return (
29
+ <span
30
+ className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-sm font-medium ${styles[status]}`}
31
+ >
32
+ {showCheckmark && <Check className="w-4 h-4" />}
33
+ {showDot && <span className="w-2 h-2 rounded-full bg-current" />}
34
+ {labels[status]}
35
+ </span>
36
+ );
37
+ };
@@ -0,0 +1,72 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { Search, Bell, ChevronDown, Menu } from "lucide-react";
3
+ import zenLogo from "../assets/icons/zen-logo.svg";
4
+ import { getUserInfo } from "../api/dashboard.js";
5
+
6
+ interface TopBarProps {
7
+ onMenuClick?: () => void;
8
+ }
9
+
10
+ export const TopBar: React.FC<TopBarProps> = ({ onMenuClick }) => {
11
+ const [userName, setUserName] = useState<string>("User");
12
+
13
+ useEffect(() => {
14
+ const loadUserInfo = async () => {
15
+ const userInfo = await getUserInfo();
16
+ if (userInfo) {
17
+ setUserName(userInfo.name);
18
+ }
19
+ };
20
+ loadUserInfo();
21
+ }, []);
22
+ return (
23
+ <div className="bg-[#372949] text-white h-16 flex items-center justify-between px-6">
24
+ {/* Left section - Logo and Menu */}
25
+ <div className="flex items-center gap-4">
26
+ <button
27
+ onClick={onMenuClick}
28
+ className="p-2 hover:bg-purple-700 rounded-md transition-colors md:hidden"
29
+ aria-label="Toggle menu"
30
+ >
31
+ <Menu className="w-6 h-6" />
32
+ </button>
33
+ <div className="flex items-center gap-2">
34
+ <img src={zenLogo} alt="Zenlease Logo" className="w-8 h-8" />
35
+ <span className="text-xl tracking-wide">
36
+ <span className="font-light">ZEN</span>
37
+ <span className="font-semibold">LEASE</span>
38
+ </span>
39
+ </div>
40
+ </div>
41
+
42
+ {/* Right section - Search, Notifications, Profile */}
43
+ <div className="flex items-center gap-4">
44
+ {/* Search Icon (Mobile) */}
45
+ <button
46
+ className="p-2 hover:bg-purple-700 rounded-md transition-colors md:hidden"
47
+ aria-label="Search"
48
+ >
49
+ <Search className="w-5 h-5" />
50
+ </button>
51
+
52
+ {/* Notifications */}
53
+ <button
54
+ className="p-2 hover:bg-purple-700 rounded-md transition-colors relative"
55
+ aria-label="Notifications"
56
+ >
57
+ <Bell className="w-5 h-5" />
58
+ <span className="absolute top-1 right-1 w-2 h-2 bg-pink-500 rounded-full"></span>
59
+ </button>
60
+
61
+ {/* User Profile */}
62
+ <button className="flex items-center gap-2 px-3 py-2 hover:bg-purple-700 rounded-md transition-colors">
63
+ <div className="w-8 h-8 bg-purple-300 rounded-full flex items-center justify-center text-purple-900 font-semibold">
64
+ {userName.charAt(0).toUpperCase()}
65
+ </div>
66
+ <span className="hidden md:inline font-medium">{userName.toUpperCase()}</span>
67
+ <ChevronDown className="w-4 h-4 hidden md:inline" />
68
+ </button>
69
+ </div>
70
+ </div>
71
+ );
72
+ };
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+
3
+ interface UserAvatarProps {
4
+ name: string;
5
+ imageUrl?: string;
6
+ size?: "sm" | "md" | "lg";
7
+ }
8
+
9
+ export const UserAvatar: React.FC<UserAvatarProps> = ({ name, imageUrl, size = "md" }) => {
10
+ const sizeClasses = {
11
+ sm: "w-8 h-8 text-xs",
12
+ md: "w-10 h-10 text-sm",
13
+ lg: "w-12 h-12 text-base",
14
+ };
15
+
16
+ const getInitials = (name: string) => {
17
+ const parts = name.trim().split(" ");
18
+ if (parts.length >= 2) {
19
+ return `${parts[0][0]}${parts[1][0]}`.toUpperCase();
20
+ }
21
+ return name.substring(0, 2).toUpperCase();
22
+ };
23
+
24
+ return (
25
+ <div
26
+ className={`${sizeClasses[size]} rounded-full bg-gray-200 flex items-center justify-center flex-shrink-0 overflow-hidden`}
27
+ >
28
+ {imageUrl ? (
29
+ <img src={imageUrl} alt={name} className="w-full h-full object-cover" />
30
+ ) : (
31
+ <span className="font-medium text-gray-700">{getInitials(name)}</span>
32
+ )}
33
+ </div>
34
+ );
35
+ };
@@ -0,0 +1,54 @@
1
+ import React from "react";
2
+ import { Link, useLocation } from "react-router";
3
+ import dashboardIcon from "../assets/icons/dashboard.svg";
4
+ import filesIcon from "../assets/icons/files.svg";
5
+ import propertiesIcon from "../assets/icons/properties.svg";
6
+ import maintenanceIcon from "../assets/icons/maintenance.svg";
7
+ import usersIcon from "../assets/icons/users.svg";
8
+ import supportIcon from "../assets/icons/support.svg";
9
+
10
+ interface NavItem {
11
+ path: string;
12
+ icon: string;
13
+ label: string;
14
+ }
15
+
16
+ const navItems: NavItem[] = [
17
+ { path: "/", icon: dashboardIcon, label: "Dashboard" },
18
+ { path: "/files", icon: filesIcon, label: "Files" },
19
+ { path: "/properties", icon: propertiesIcon, label: "Properties" },
20
+ { path: "/maintenance", icon: maintenanceIcon, label: "Maintenance" },
21
+ { path: "/users", icon: usersIcon, label: "Users" },
22
+ { path: "/support", icon: supportIcon, label: "Support" },
23
+ ];
24
+
25
+ export const VerticalNav: React.FC = () => {
26
+ const location = useLocation();
27
+
28
+ const isActive = (path: string) => {
29
+ if (path === "/") {
30
+ return location.pathname === "/";
31
+ }
32
+ return location.pathname.startsWith(path);
33
+ };
34
+
35
+ return (
36
+ <div className="flex flex-col w-24 bg-white border-r border-gray-200 py-8 space-y-4">
37
+ {navItems.map((item) => (
38
+ <Link
39
+ key={item.path}
40
+ to={item.path}
41
+ className={`flex flex-col items-center justify-center gap-2 py-4 px-2 transition-colors ${
42
+ isActive(item.path)
43
+ ? "bg-purple-100 text-purple-700 border-l-4 border-purple-700"
44
+ : "text-gray-600 hover:bg-gray-100 hover:text-gray-900"
45
+ }`}
46
+ title={item.label}
47
+ >
48
+ <img src={item.icon} alt={item.label} className="w-6 h-6" />
49
+ <span className="text-xs font-medium">{item.label}</span>
50
+ </Link>
51
+ ))}
52
+ </div>
53
+ );
54
+ };
@@ -0,0 +1,69 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+
4
+ import { cn } from "../../lib/utils";
5
+
6
+ const alertVariants = cva(
7
+ "grid gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 w-full relative group/alert",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-card text-card-foreground",
12
+ destructive:
13
+ "text-destructive bg-card *: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
+ data-slot="alert"
30
+ role="alert"
31
+ className={cn(alertVariants({ variant }), className)}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+
37
+ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
38
+ return (
39
+ <div
40
+ data-slot="alert-title"
41
+ className={cn(
42
+ "font-medium group-has-[>svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
43
+ className,
44
+ )}
45
+ {...props}
46
+ />
47
+ );
48
+ }
49
+
50
+ function AlertDescription({ className, ...props }: React.ComponentProps<"div">) {
51
+ return (
52
+ <div
53
+ data-slot="alert-description"
54
+ className={cn(
55
+ "text-muted-foreground text-sm text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
56
+ className,
57
+ )}
58
+ {...props}
59
+ />
60
+ );
61
+ }
62
+
63
+ function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
64
+ return (
65
+ <div data-slot="alert-action" className={cn("absolute top-2 right-2", className)} {...props} />
66
+ );
67
+ }
68
+
69
+ export { Alert, AlertTitle, AlertDescription, AlertAction };
@@ -0,0 +1,67 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { Slot } from "radix-ui";
4
+
5
+ import { cn } from "../../lib/utils";
6
+
7
+ const buttonVariants = cva(
8
+ "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-lg border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
13
+ outline:
14
+ "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground",
15
+ secondary:
16
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
17
+ ghost:
18
+ "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
19
+ destructive:
20
+ "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ size: {
24
+ default:
25
+ "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
26
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
27
+ sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
28
+ lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
29
+ icon: "size-8",
30
+ "icon-xs":
31
+ "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
32
+ "icon-sm":
33
+ "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
34
+ "icon-lg": "size-9",
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ variant: "default",
39
+ size: "default",
40
+ },
41
+ },
42
+ );
43
+
44
+ function Button({
45
+ className,
46
+ variant = "default",
47
+ size = "default",
48
+ asChild = false,
49
+ ...props
50
+ }: React.ComponentProps<"button"> &
51
+ VariantProps<typeof buttonVariants> & {
52
+ asChild?: boolean;
53
+ }) {
54
+ const Comp = asChild ? Slot.Root : "button";
55
+
56
+ return (
57
+ <Comp
58
+ data-slot="button"
59
+ data-variant={variant}
60
+ data-size={size}
61
+ className={cn(buttonVariants({ variant, size, className }))}
62
+ {...(props as any)}
63
+ />
64
+ );
65
+ }
66
+
67
+ export { Button, buttonVariants };
@@ -0,0 +1,92 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "../../lib/utils";
4
+
5
+ function Card({
6
+ className,
7
+ size = "default",
8
+ ...props
9
+ }: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
10
+ return (
11
+ <div
12
+ data-slot="card"
13
+ data-size={size}
14
+ className={cn(
15
+ "ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-xl py-4 text-sm ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col",
16
+ className,
17
+ )}
18
+ {...props}
19
+ />
20
+ );
21
+ }
22
+
23
+ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
24
+ return (
25
+ <div
26
+ data-slot="card-header"
27
+ className={cn(
28
+ "gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
29
+ className,
30
+ )}
31
+ {...props}
32
+ />
33
+ );
34
+ }
35
+
36
+ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
37
+ return (
38
+ <div
39
+ data-slot="card-title"
40
+ className={cn(
41
+ "text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
42
+ className,
43
+ )}
44
+ {...props}
45
+ />
46
+ );
47
+ }
48
+
49
+ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
50
+ return (
51
+ <div
52
+ data-slot="card-description"
53
+ className={cn("text-muted-foreground text-sm", className)}
54
+ {...props}
55
+ />
56
+ );
57
+ }
58
+
59
+ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
60
+ return (
61
+ <div
62
+ data-slot="card-action"
63
+ className={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
64
+ {...props}
65
+ />
66
+ );
67
+ }
68
+
69
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
70
+ return (
71
+ <div
72
+ data-slot="card-content"
73
+ className={cn("px-4 group-data-[size=sm]/card:px-3", className)}
74
+ {...props}
75
+ />
76
+ );
77
+ }
78
+
79
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
80
+ return (
81
+ <div
82
+ data-slot="card-footer"
83
+ className={cn(
84
+ "bg-muted/50 rounded-b-xl border-t p-4 group-data-[size=sm]/card:p-3 flex items-center",
85
+ className,
86
+ )}
87
+ {...props}
88
+ />
89
+ );
90
+ }
91
+
92
+ export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent };
@@ -0,0 +1,143 @@
1
+ import * as React from "react";
2
+ import { Dialog as DialogPrimitive } from "radix-ui";
3
+
4
+ import { cn } from "../../lib/utils";
5
+ import { Button } from "./button";
6
+ import { XIcon } from "lucide-react";
7
+
8
+ function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
9
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />;
10
+ }
11
+
12
+ function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
13
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
14
+ }
15
+
16
+ function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
17
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
18
+ }
19
+
20
+ function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
21
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
22
+ }
23
+
24
+ function DialogOverlay({
25
+ className,
26
+ ...props
27
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
28
+ return (
29
+ <DialogPrimitive.Overlay
30
+ data-slot="dialog-overlay"
31
+ className={cn(
32
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50",
33
+ className,
34
+ )}
35
+ {...props}
36
+ />
37
+ );
38
+ }
39
+
40
+ function DialogContent({
41
+ className,
42
+ children,
43
+ showCloseButton = true,
44
+ ...props
45
+ }: React.ComponentProps<typeof DialogPrimitive.Content> & {
46
+ showCloseButton?: boolean;
47
+ }) {
48
+ return (
49
+ <DialogPortal>
50
+ <DialogOverlay />
51
+ <DialogPrimitive.Content
52
+ data-slot="dialog-content"
53
+ className={cn(
54
+ "bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-4 rounded-xl p-4 text-sm ring-1 duration-100 sm:max-w-sm fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none",
55
+ className,
56
+ )}
57
+ {...props}
58
+ >
59
+ {children}
60
+ {showCloseButton && (
61
+ <DialogPrimitive.Close data-slot="dialog-close" asChild>
62
+ <Button variant="ghost" className="absolute top-2 right-2" size="icon-sm">
63
+ <XIcon />
64
+ <span className="sr-only">Close</span>
65
+ </Button>
66
+ </DialogPrimitive.Close>
67
+ )}
68
+ </DialogPrimitive.Content>
69
+ </DialogPortal>
70
+ );
71
+ }
72
+
73
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
74
+ return (
75
+ <div data-slot="dialog-header" className={cn("gap-2 flex flex-col", className)} {...props} />
76
+ );
77
+ }
78
+
79
+ function DialogFooter({
80
+ className,
81
+ showCloseButton = false,
82
+ children,
83
+ ...props
84
+ }: React.ComponentProps<"div"> & {
85
+ showCloseButton?: boolean;
86
+ }) {
87
+ return (
88
+ <div
89
+ data-slot="dialog-footer"
90
+ className={cn(
91
+ "bg-muted/50 -mx-4 -mb-4 rounded-b-xl border-t p-4 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
92
+ className,
93
+ )}
94
+ {...props}
95
+ >
96
+ {children}
97
+ {showCloseButton && (
98
+ <DialogPrimitive.Close asChild>
99
+ <Button variant="outline">Close</Button>
100
+ </DialogPrimitive.Close>
101
+ )}
102
+ </div>
103
+ );
104
+ }
105
+
106
+ function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
107
+ return (
108
+ <DialogPrimitive.Title
109
+ data-slot="dialog-title"
110
+ className={cn("text-base leading-none font-medium", className)}
111
+ {...props}
112
+ />
113
+ );
114
+ }
115
+
116
+ function DialogDescription({
117
+ className,
118
+ ...props
119
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
120
+ return (
121
+ <DialogPrimitive.Description
122
+ data-slot="dialog-description"
123
+ className={cn(
124
+ "text-muted-foreground *:[a]:hover:text-foreground text-sm *:[a]:underline *:[a]:underline-offset-3",
125
+ className,
126
+ )}
127
+ {...props}
128
+ />
129
+ );
130
+ }
131
+
132
+ export {
133
+ Dialog,
134
+ DialogClose,
135
+ DialogContent,
136
+ DialogDescription,
137
+ DialogFooter,
138
+ DialogHeader,
139
+ DialogOverlay,
140
+ DialogPortal,
141
+ DialogTitle,
142
+ DialogTrigger,
143
+ };