modula-ui 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 (66) hide show
  1. package/README.md +36 -0
  2. package/bin/run.js +86 -0
  3. package/package.json +71 -0
  4. package/public/avatars/avatar1.png +0 -0
  5. package/public/avatars/avatar2.png +0 -0
  6. package/public/avatars/avatar3.png +0 -0
  7. package/public/avatars/avatar4.png +0 -0
  8. package/public/avatars/sources.md +34 -0
  9. package/public/file.svg +1 -0
  10. package/public/globe.svg +1 -0
  11. package/public/next.svg +1 -0
  12. package/public/vercel.svg +1 -0
  13. package/public/window.svg +1 -0
  14. package/registry.json +12 -0
  15. package/src/app/favicon.ico +0 -0
  16. package/src/app/globals.css +126 -0
  17. package/src/app/layout.js +29 -0
  18. package/src/app/page.js +50 -0
  19. package/src/app/patterns/page.js +50 -0
  20. package/src/components/CodeCard.jsx +16 -0
  21. package/src/components/CopyButton.jsx +31 -0
  22. package/src/components/Header.jsx +24 -0
  23. package/src/components/Logo.jsx +64 -0
  24. package/src/components/MobileOverlay.jsx +10 -0
  25. package/src/components/PreviewCard.jsx +98 -0
  26. package/src/components/Sidebar.jsx +47 -0
  27. package/src/components/ui/avatar.jsx +47 -0
  28. package/src/components/ui/badge.jsx +44 -0
  29. package/src/components/ui/button.jsx +56 -0
  30. package/src/components/ui/calendar.jsx +178 -0
  31. package/src/components/ui/card.jsx +101 -0
  32. package/src/components/ui/chart.jsx +314 -0
  33. package/src/components/ui/checkbox.jsx +30 -0
  34. package/src/components/ui/dropdown-menu.jsx +223 -0
  35. package/src/components/ui/input.jsx +24 -0
  36. package/src/components/ui/navigation-menu.jsx +152 -0
  37. package/src/components/ui/popover.jsx +47 -0
  38. package/src/components/ui/progress.jsx +29 -0
  39. package/src/components/ui/scroll-area.jsx +51 -0
  40. package/src/components/ui/select.jsx +168 -0
  41. package/src/components/ui/separator.jsx +27 -0
  42. package/src/components/ui/sheet.jsx +140 -0
  43. package/src/components/ui/sidebar.jsx +682 -0
  44. package/src/components/ui/skeleton.jsx +15 -0
  45. package/src/components/ui/slider.jsx +56 -0
  46. package/src/components/ui/tooltip.jsx +55 -0
  47. package/src/data/componentData.js +12 -0
  48. package/src/hooks/use-mobile.js +19 -0
  49. package/src/lib/utils.js +6 -0
  50. package/src/library/components/Alert.jsx +27 -0
  51. package/src/library/components/Badge.jsx +19 -0
  52. package/src/library/components/Button.jsx +31 -0
  53. package/src/library/components/Card.jsx +25 -0
  54. package/src/library/components/Input.jsx +35 -0
  55. package/src/library/components/Modal.jsx +26 -0
  56. package/src/library/components/Textarea.jsx +15 -0
  57. package/src/library/components/Toggle.jsx +16 -0
  58. package/src/library/pages/FitnessPage/FitnessPage.jsx +519 -0
  59. package/src/library/pages/FitnessPage/index.jsx +12 -0
  60. package/src/library/pages/GroupChat/GroupChat.jsx +275 -0
  61. package/src/library/pages/GroupChat/data.js +203 -0
  62. package/src/library/pages/GroupChat/index.jsx +12 -0
  63. package/src/library/pages/ReservationsOverview/ReservationsOverviewPage.jsx +225 -0
  64. package/src/library/pages/ReservationsOverview/index.jsx +12 -0
  65. package/src/library/pages/VideoConference/VideoConferencePage.jsx +334 -0
  66. package/src/library/pages/VideoConference/index.jsx +12 -0
@@ -0,0 +1,10 @@
1
+ export default function MobileOverlay({ open, onClose }) {
2
+ if (!open) return null;
3
+
4
+ return (
5
+ <div
6
+ className="fixed inset-0 bg-black/35 z-40 "
7
+ onClick={onClose}
8
+ />
9
+ );
10
+ }
@@ -0,0 +1,98 @@
1
+ import { useState, useEffect, useRef } from "react";
2
+ import { createPortal } from "react-dom";
3
+
4
+ export default function PreviewCard({ children, title = "Preview" }) {
5
+ const [externalWindow, setExternalWindow] = useState(null);
6
+ const [containerElement, setContainerElement] = useState(null);
7
+
8
+ const openFullPreview = () => {
9
+ if (externalWindow) {
10
+ externalWindow.focus();
11
+ return;
12
+ }
13
+
14
+ const newWindow = window.open("", "_blank", "width=1200,height=800,left=200,top=200");
15
+ if (!newWindow) return;
16
+
17
+ const fullPage = `
18
+ <!DOCTYPE html>
19
+ <html lang="en">
20
+ <head>
21
+ <meta charset="utf-8">
22
+ <meta name="viewport" content="width=device-width, initial-scale=1">
23
+ <title>${title}</title>
24
+ <script src="https://cdn.tailwindcss.com"></script>
25
+ <style>
26
+ body { margin: 0; background: #f9fafb; }
27
+ @media (prefers-color-scheme: dark) { body { background: #111827; } }
28
+ </style>
29
+ </head>
30
+ <body class="min-h-screen flex items-center justify-center">
31
+ <div id="root" class="w-full mx-auto"></div>
32
+ </body>
33
+ </html>
34
+ `;
35
+
36
+ newWindow.document.write(fullPage);
37
+ newWindow.document.close();
38
+
39
+ // Wait for the window to load before trying to access the DOM
40
+ newWindow.onload = () => {
41
+ const container = newWindow.document.getElementById("root");
42
+ setExternalWindow(newWindow);
43
+ setContainerElement(container);
44
+ };
45
+
46
+ // In case onload fired synchronously or we missed it (rare with open, but good safety)
47
+ if (newWindow.document.readyState === 'complete') {
48
+ const container = newWindow.document.getElementById("root");
49
+ setExternalWindow(newWindow);
50
+ setContainerElement(container);
51
+ }
52
+
53
+ // Handle window close
54
+ newWindow.onbeforeunload = () => {
55
+ setExternalWindow(null);
56
+ setContainerElement(null);
57
+ };
58
+ };
59
+
60
+ // Close external window when component unmounts
61
+ useEffect(() => {
62
+ return () => {
63
+ if (externalWindow) {
64
+ externalWindow.close();
65
+ }
66
+ };
67
+ }, [externalWindow]);
68
+
69
+ return (
70
+ <div className="w-full bg-white rounded-lg shadow-sm border border-gray-200 p-3">
71
+ <div className="flex items-center justify-between mb-4">
72
+ <h2 className="text-xl font-semibold text-gray-900">{title}</h2>
73
+
74
+ <button
75
+ onClick={openFullPreview}
76
+ className={`flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium rounded-md transition
77
+ text-gray-700 bg-gray-50 hover:bg-gray-100 hover:text-gray-900`}
78
+ >
79
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
80
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
81
+ </svg>
82
+ Open in full screen
83
+ </button>
84
+ </div>
85
+
86
+ {/* The preview area in the main window */}
87
+ <div
88
+ id="preview-content"
89
+ className="bg-gray-50 rounded-lg p-4 border border-dashed border-gray-300 min-h-96 w-full"
90
+ >
91
+ {children}
92
+ </div>
93
+
94
+ {/* Render content into the new window if it exists */}
95
+ {containerElement && createPortal(children, containerElement)}
96
+ </div>
97
+ );
98
+ }
@@ -0,0 +1,47 @@
1
+ 'use client';
2
+
3
+ import { X } from 'lucide-react';
4
+ import { components } from '@/data/componentData';
5
+
6
+ export default function Sidebar({ selected, onSelect, open, onClose }) {
7
+ return (
8
+ <aside
9
+ className={`
10
+ fixed inset-y-0 left-0 z-50 w-64 bg-white rounded-lg shadow-sm border border-gray-200 p-4
11
+ transform transition-transform duration-300 ease-in-out
12
+ lg:static lg:inset-auto lg:transform-none
13
+ ${open ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'}
14
+ `}
15
+ style={{ top: '1.5rem' }} // matches Header padding
16
+ >
17
+ <div className="flex items-center justify-between mb-4 lg:mb-3">
18
+ <h2 className="font-semibold text-gray-700">Patterns</h2>
19
+ <button
20
+ onClick={onClose}
21
+ className="lg:hidden p-1 hover:bg-gray-100 rounded"
22
+ >
23
+ <X size={20} />
24
+ </button>
25
+ </div>
26
+
27
+ <nav className="space-y-1">
28
+ {Object.entries(components).map(([key, comp]) => (
29
+ <button
30
+ key={key}
31
+ onClick={() => {
32
+ onSelect(key);
33
+ onClose();
34
+ }}
35
+ className={`w-full text-left px-3 py-2 rounded-lg transition-colors text-sm font-medium
36
+ ${selected === key
37
+ ? 'bg-blue-100 text-blue-700 ring-2 ring-blue-200'
38
+ : 'text-gray-700 hover:bg-gray-100'
39
+ }`}
40
+ >
41
+ {comp.name}
42
+ </button>
43
+ ))}
44
+ </nav>
45
+ </aside>
46
+ );
47
+ }
@@ -0,0 +1,47 @@
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
+ function Avatar({
9
+ className,
10
+ ...props
11
+ }) {
12
+ return (
13
+ <AvatarPrimitive.Root
14
+ data-slot="avatar"
15
+ className={cn("relative flex size-8 shrink-0 overflow-hidden rounded-full", className)}
16
+ {...props} />
17
+ );
18
+ }
19
+
20
+ function AvatarImage({
21
+ className,
22
+ ...props
23
+ }) {
24
+ return (
25
+ <AvatarPrimitive.Image
26
+ data-slot="avatar-image"
27
+ className={cn("aspect-square size-full", className)}
28
+ {...props} />
29
+ );
30
+ }
31
+
32
+ function AvatarFallback({
33
+ className,
34
+ ...props
35
+ }) {
36
+ return (
37
+ <AvatarPrimitive.Fallback
38
+ data-slot="avatar-fallback"
39
+ className={cn(
40
+ "bg-muted flex size-full items-center justify-center rounded-full",
41
+ className
42
+ )}
43
+ {...props} />
44
+ );
45
+ }
46
+
47
+ export { Avatar, AvatarImage, AvatarFallback }
@@ -0,0 +1,44 @@
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 badgeVariants = cva(
8
+ "inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
14
+ secondary:
15
+ "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
16
+ destructive:
17
+ "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
18
+ outline:
19
+ "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
20
+ },
21
+ },
22
+ defaultVariants: {
23
+ variant: "default",
24
+ },
25
+ }
26
+ )
27
+
28
+ function Badge({
29
+ className,
30
+ variant,
31
+ asChild = false,
32
+ ...props
33
+ }) {
34
+ const Comp = asChild ? Slot : "span"
35
+
36
+ return (
37
+ <Comp
38
+ data-slot="badge"
39
+ className={cn(badgeVariants({ variant }), className)}
40
+ {...props} />
41
+ );
42
+ }
43
+
44
+ export { Badge, badgeVariants }
@@ -0,0 +1,56 @@
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 transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
+ destructive:
14
+ "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
15
+ outline:
16
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
17
+ secondary:
18
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ ghost:
20
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ size: {
24
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
25
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
26
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
27
+ icon: "size-9",
28
+ "icon-sm": "size-8",
29
+ "icon-lg": "size-10",
30
+ },
31
+ },
32
+ defaultVariants: {
33
+ variant: "default",
34
+ size: "default",
35
+ },
36
+ }
37
+ )
38
+
39
+ function Button({
40
+ className,
41
+ variant,
42
+ size,
43
+ asChild = false,
44
+ ...props
45
+ }) {
46
+ const Comp = asChild ? Slot : "button"
47
+
48
+ return (
49
+ <Comp
50
+ data-slot="button"
51
+ className={cn(buttonVariants({ variant, size, className }))}
52
+ {...props} />
53
+ );
54
+ }
55
+
56
+ export { Button, buttonVariants }
@@ -0,0 +1,178 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import {
5
+ ChevronDownIcon,
6
+ ChevronLeftIcon,
7
+ ChevronRightIcon,
8
+ } from "lucide-react"
9
+ import { DayPicker, getDefaultClassNames } from "react-day-picker";
10
+
11
+ import { cn } from "@/lib/utils"
12
+ import { Button, buttonVariants } from "@/components/ui/button"
13
+
14
+ function Calendar({
15
+ className,
16
+ classNames,
17
+ showOutsideDays = true,
18
+ captionLayout = "label",
19
+ buttonVariant = "ghost",
20
+ formatters,
21
+ components,
22
+ ...props
23
+ }) {
24
+ const defaultClassNames = getDefaultClassNames()
25
+
26
+ return (
27
+ <DayPicker
28
+ showOutsideDays={showOutsideDays}
29
+ className={cn(
30
+ "bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
31
+ String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
32
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
33
+ className
34
+ )}
35
+ captionLayout={captionLayout}
36
+ formatters={{
37
+ formatMonthDropdown: (date) =>
38
+ date.toLocaleString("default", { month: "short" }),
39
+ ...formatters,
40
+ }}
41
+ classNames={{
42
+ root: cn("w-fit", defaultClassNames.root),
43
+ months: cn("flex gap-4 flex-col md:flex-row relative", defaultClassNames.months),
44
+ month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
45
+ nav: cn(
46
+ "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
47
+ defaultClassNames.nav
48
+ ),
49
+ button_previous: cn(
50
+ buttonVariants({ variant: buttonVariant }),
51
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
52
+ defaultClassNames.button_previous
53
+ ),
54
+ button_next: cn(
55
+ buttonVariants({ variant: buttonVariant }),
56
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
57
+ defaultClassNames.button_next
58
+ ),
59
+ month_caption: cn(
60
+ "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
61
+ defaultClassNames.month_caption
62
+ ),
63
+ dropdowns: cn(
64
+ "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
65
+ defaultClassNames.dropdowns
66
+ ),
67
+ dropdown_root: cn(
68
+ "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
69
+ defaultClassNames.dropdown_root
70
+ ),
71
+ dropdown: cn("absolute bg-popover inset-0 opacity-0", defaultClassNames.dropdown),
72
+ caption_label: cn("select-none font-medium", captionLayout === "label"
73
+ ? "text-sm"
74
+ : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5", defaultClassNames.caption_label),
75
+ table: "w-full border-collapse",
76
+ weekdays: cn("flex", defaultClassNames.weekdays),
77
+ weekday: cn(
78
+ "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
79
+ defaultClassNames.weekday
80
+ ),
81
+ week: cn("flex w-full mt-2", defaultClassNames.week),
82
+ week_number_header: cn("select-none w-(--cell-size)", defaultClassNames.week_number_header),
83
+ week_number: cn(
84
+ "text-[0.8rem] select-none text-muted-foreground",
85
+ defaultClassNames.week_number
86
+ ),
87
+ day: cn(
88
+ "relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
89
+ props.showWeekNumber
90
+ ? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md"
91
+ : "[&:first-child[data-selected=true]_button]:rounded-l-md",
92
+ defaultClassNames.day
93
+ ),
94
+ range_start: cn("rounded-l-md bg-accent", defaultClassNames.range_start),
95
+ range_middle: cn("rounded-none", defaultClassNames.range_middle),
96
+ range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
97
+ today: cn(
98
+ "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
99
+ defaultClassNames.today
100
+ ),
101
+ outside: cn(
102
+ "text-muted-foreground aria-selected:text-muted-foreground",
103
+ defaultClassNames.outside
104
+ ),
105
+ disabled: cn("text-muted-foreground opacity-50", defaultClassNames.disabled),
106
+ hidden: cn("invisible", defaultClassNames.hidden),
107
+ ...classNames,
108
+ }}
109
+ components={{
110
+ Root: ({ className, rootRef, ...props }) => {
111
+ return (<div data-slot="calendar" ref={rootRef} className={cn(className)} {...props} />);
112
+ },
113
+ Chevron: ({ className, orientation, ...props }) => {
114
+ if (orientation === "left") {
115
+ return (<ChevronLeftIcon className={cn("size-4", className)} {...props} />);
116
+ }
117
+
118
+ if (orientation === "right") {
119
+ return (<ChevronRightIcon className={cn("size-4", className)} {...props} />);
120
+ }
121
+
122
+ return (<ChevronDownIcon className={cn("size-4", className)} {...props} />);
123
+ },
124
+ DayButton: CalendarDayButton,
125
+ WeekNumber: ({ children, ...props }) => {
126
+ return (
127
+ <td {...props}>
128
+ <div
129
+ className="flex size-(--cell-size) items-center justify-center text-center">
130
+ {children}
131
+ </div>
132
+ </td>
133
+ );
134
+ },
135
+ ...components,
136
+ }}
137
+ {...props} />
138
+ );
139
+ }
140
+
141
+ function CalendarDayButton({
142
+ className,
143
+ day,
144
+ modifiers,
145
+ ...props
146
+ }) {
147
+ const defaultClassNames = getDefaultClassNames()
148
+
149
+ const ref = React.useRef(null)
150
+ React.useEffect(() => {
151
+ if (modifiers.focused) ref.current?.focus()
152
+ }, [modifiers.focused])
153
+
154
+ return (
155
+ <Button
156
+ ref={ref}
157
+ variant="ghost"
158
+ size="icon"
159
+ data-day={day.date.toLocaleDateString()}
160
+ data-selected-single={
161
+ modifiers.selected &&
162
+ !modifiers.range_start &&
163
+ !modifiers.range_end &&
164
+ !modifiers.range_middle
165
+ }
166
+ data-range-start={modifiers.range_start}
167
+ data-range-end={modifiers.range_end}
168
+ data-range-middle={modifiers.range_middle}
169
+ className={cn(
170
+ "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70",
171
+ defaultClassNames.day,
172
+ className
173
+ )}
174
+ {...props} />
175
+ );
176
+ }
177
+
178
+ export { Calendar, CalendarDayButton }
@@ -0,0 +1,101 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Card({
6
+ className,
7
+ ...props
8
+ }) {
9
+ return (
10
+ <div
11
+ data-slot="card"
12
+ className={cn(
13
+ "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
14
+ className
15
+ )}
16
+ {...props} />
17
+ );
18
+ }
19
+
20
+ function CardHeader({
21
+ className,
22
+ ...props
23
+ }) {
24
+ return (
25
+ <div
26
+ data-slot="card-header"
27
+ className={cn(
28
+ "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
29
+ className
30
+ )}
31
+ {...props} />
32
+ );
33
+ }
34
+
35
+ function CardTitle({
36
+ className,
37
+ ...props
38
+ }) {
39
+ return (
40
+ <div
41
+ data-slot="card-title"
42
+ className={cn("leading-none font-semibold", className)}
43
+ {...props} />
44
+ );
45
+ }
46
+
47
+ function CardDescription({
48
+ className,
49
+ ...props
50
+ }) {
51
+ return (
52
+ <div
53
+ data-slot="card-description"
54
+ className={cn("text-muted-foreground text-sm", className)}
55
+ {...props} />
56
+ );
57
+ }
58
+
59
+ function CardAction({
60
+ className,
61
+ ...props
62
+ }) {
63
+ return (
64
+ <div
65
+ data-slot="card-action"
66
+ className={cn(
67
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
68
+ className
69
+ )}
70
+ {...props} />
71
+ );
72
+ }
73
+
74
+ function CardContent({
75
+ className,
76
+ ...props
77
+ }) {
78
+ return (<div data-slot="card-content" className={cn("px-6", className)} {...props} />);
79
+ }
80
+
81
+ function CardFooter({
82
+ className,
83
+ ...props
84
+ }) {
85
+ return (
86
+ <div
87
+ data-slot="card-footer"
88
+ className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
89
+ {...props} />
90
+ );
91
+ }
92
+
93
+ export {
94
+ Card,
95
+ CardHeader,
96
+ CardFooter,
97
+ CardTitle,
98
+ CardAction,
99
+ CardDescription,
100
+ CardContent,
101
+ }