@oppulence/design-system 1.0.2

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 (80) hide show
  1. package/README.md +115 -0
  2. package/components.json +21 -0
  3. package/hooks/use-mobile.tsx +21 -0
  4. package/lib/utils.ts +6 -0
  5. package/package.json +104 -0
  6. package/postcss.config.mjs +8 -0
  7. package/src/components/atoms/aspect-ratio.tsx +21 -0
  8. package/src/components/atoms/avatar.tsx +91 -0
  9. package/src/components/atoms/badge.tsx +47 -0
  10. package/src/components/atoms/button.tsx +128 -0
  11. package/src/components/atoms/checkbox.tsx +24 -0
  12. package/src/components/atoms/container.tsx +42 -0
  13. package/src/components/atoms/heading.tsx +56 -0
  14. package/src/components/atoms/index.ts +21 -0
  15. package/src/components/atoms/input.tsx +18 -0
  16. package/src/components/atoms/kbd.tsx +23 -0
  17. package/src/components/atoms/label.tsx +15 -0
  18. package/src/components/atoms/logo.tsx +52 -0
  19. package/src/components/atoms/progress.tsx +79 -0
  20. package/src/components/atoms/separator.tsx +17 -0
  21. package/src/components/atoms/skeleton.tsx +13 -0
  22. package/src/components/atoms/slider.tsx +56 -0
  23. package/src/components/atoms/spinner.tsx +14 -0
  24. package/src/components/atoms/stack.tsx +126 -0
  25. package/src/components/atoms/switch.tsx +26 -0
  26. package/src/components/atoms/text.tsx +69 -0
  27. package/src/components/atoms/textarea.tsx +19 -0
  28. package/src/components/atoms/toggle.tsx +40 -0
  29. package/src/components/molecules/accordion.tsx +72 -0
  30. package/src/components/molecules/ai-chat.tsx +251 -0
  31. package/src/components/molecules/alert.tsx +131 -0
  32. package/src/components/molecules/breadcrumb.tsx +301 -0
  33. package/src/components/molecules/button-group.tsx +96 -0
  34. package/src/components/molecules/card.tsx +184 -0
  35. package/src/components/molecules/collapsible.tsx +21 -0
  36. package/src/components/molecules/command-search.tsx +148 -0
  37. package/src/components/molecules/empty.tsx +98 -0
  38. package/src/components/molecules/field.tsx +217 -0
  39. package/src/components/molecules/grid.tsx +141 -0
  40. package/src/components/molecules/hover-card.tsx +45 -0
  41. package/src/components/molecules/index.ts +29 -0
  42. package/src/components/molecules/input-group.tsx +151 -0
  43. package/src/components/molecules/input-otp.tsx +74 -0
  44. package/src/components/molecules/item.tsx +194 -0
  45. package/src/components/molecules/page-header.tsx +89 -0
  46. package/src/components/molecules/pagination.tsx +130 -0
  47. package/src/components/molecules/popover.tsx +96 -0
  48. package/src/components/molecules/radio-group.tsx +37 -0
  49. package/src/components/molecules/resizable.tsx +52 -0
  50. package/src/components/molecules/scroll-area.tsx +45 -0
  51. package/src/components/molecules/section.tsx +108 -0
  52. package/src/components/molecules/select.tsx +201 -0
  53. package/src/components/molecules/settings.tsx +197 -0
  54. package/src/components/molecules/table.tsx +111 -0
  55. package/src/components/molecules/tabs.tsx +74 -0
  56. package/src/components/molecules/theme-switcher.tsx +187 -0
  57. package/src/components/molecules/toggle-group.tsx +89 -0
  58. package/src/components/molecules/tooltip.tsx +66 -0
  59. package/src/components/organisms/alert-dialog.tsx +152 -0
  60. package/src/components/organisms/app-shell.tsx +939 -0
  61. package/src/components/organisms/calendar.tsx +212 -0
  62. package/src/components/organisms/carousel.tsx +230 -0
  63. package/src/components/organisms/chart.tsx +333 -0
  64. package/src/components/organisms/combobox.tsx +274 -0
  65. package/src/components/organisms/command.tsx +200 -0
  66. package/src/components/organisms/context-menu.tsx +229 -0
  67. package/src/components/organisms/dialog.tsx +134 -0
  68. package/src/components/organisms/drawer.tsx +123 -0
  69. package/src/components/organisms/dropdown-menu.tsx +256 -0
  70. package/src/components/organisms/index.ts +17 -0
  71. package/src/components/organisms/menubar.tsx +203 -0
  72. package/src/components/organisms/navigation-menu.tsx +143 -0
  73. package/src/components/organisms/page-layout.tsx +105 -0
  74. package/src/components/organisms/sheet.tsx +126 -0
  75. package/src/components/organisms/sidebar.tsx +723 -0
  76. package/src/components/organisms/sonner.tsx +41 -0
  77. package/src/components/ui/index.ts +3 -0
  78. package/src/index.ts +3 -0
  79. package/src/styles/globals.css +297 -0
  80. package/tailwind.config.ts +77 -0
@@ -0,0 +1,148 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { SearchIcon } from "lucide-react";
5
+ import {
6
+ Command,
7
+ CommandDialog,
8
+ CommandEmpty,
9
+ CommandGroup,
10
+ CommandInput,
11
+ CommandItem,
12
+ CommandList,
13
+ CommandShortcut,
14
+ } from "../organisms/command";
15
+ import { Kbd } from "../atoms/kbd";
16
+
17
+ // ============ TYPES ============
18
+
19
+ export interface CommandSearchItem {
20
+ id: string;
21
+ label: string;
22
+ icon?: React.ReactNode;
23
+ shortcut?: string;
24
+ onSelect?: () => void;
25
+ keywords?: string[];
26
+ }
27
+
28
+ export interface CommandSearchGroup {
29
+ id: string;
30
+ label: string;
31
+ items: CommandSearchItem[];
32
+ }
33
+
34
+ export interface CommandSearchProps {
35
+ /** Placeholder text for the search input */
36
+ placeholder?: string;
37
+ /** Text shown when no results are found */
38
+ emptyText?: string;
39
+ /** Groups of searchable items */
40
+ groups?: CommandSearchGroup[];
41
+ /** Flat list of items (alternative to groups) */
42
+ items?: CommandSearchItem[];
43
+ /** Callback when an item is selected */
44
+ onSelect?: (item: CommandSearchItem) => void;
45
+ /** Controlled open state */
46
+ open?: boolean;
47
+ /** Callback when open state changes */
48
+ onOpenChange?: (open: boolean) => void;
49
+ /** Whether to show the trigger input */
50
+ showTrigger?: boolean;
51
+ /** Width of the trigger input */
52
+ triggerWidth?: "sm" | "md" | "lg" | "full";
53
+ }
54
+
55
+ // ============ COMPONENT ============
56
+
57
+ function CommandSearch({
58
+ placeholder = "Search...",
59
+ emptyText = "No results found.",
60
+ groups = [],
61
+ items = [],
62
+ onSelect,
63
+ open: openProp,
64
+ onOpenChange,
65
+ showTrigger = true,
66
+ triggerWidth = "md",
67
+ }: CommandSearchProps) {
68
+ const [_open, _setOpen] = React.useState(false);
69
+ const open = openProp ?? _open;
70
+ const setOpen = onOpenChange ?? _setOpen;
71
+
72
+ // Listen for Cmd+K / Ctrl+K
73
+ React.useEffect(() => {
74
+ const handleKeyDown = (e: KeyboardEvent) => {
75
+ if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
76
+ e.preventDefault();
77
+ setOpen(!open);
78
+ }
79
+ };
80
+
81
+ document.addEventListener("keydown", handleKeyDown);
82
+ return () => document.removeEventListener("keydown", handleKeyDown);
83
+ }, [open, setOpen]);
84
+
85
+ const handleSelect = (item: CommandSearchItem) => {
86
+ setOpen(false);
87
+ item.onSelect?.();
88
+ onSelect?.(item);
89
+ };
90
+
91
+ const widthClasses = {
92
+ sm: "w-48 md:w-56",
93
+ md: "w-56 md:w-72",
94
+ lg: "w-72 md:w-96",
95
+ full: "w-full max-w-md",
96
+ };
97
+
98
+ // Combine flat items into a default group if provided
99
+ const allGroups =
100
+ items.length > 0
101
+ ? [{ id: "default", label: "", items }, ...groups]
102
+ : groups;
103
+
104
+ return (
105
+ <>
106
+ {showTrigger && (
107
+ <button
108
+ type="button"
109
+ onClick={() => setOpen(true)}
110
+ className={`${widthClasses[triggerWidth]} inline-flex items-center gap-2 rounded-lg border border-input/50 bg-background/50 px-3 py-1.5 text-sm text-muted-foreground transition-colors hover:bg-background hover:border-input focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring`}
111
+ >
112
+ <SearchIcon className="size-4" />
113
+ <span className="flex-1 text-left">{placeholder}</span>
114
+ <Kbd>⌘K</Kbd>
115
+ </button>
116
+ )}
117
+
118
+ <CommandDialog open={open} onOpenChange={setOpen}>
119
+ <Command>
120
+ <CommandInput placeholder={placeholder} />
121
+ <CommandList>
122
+ <CommandEmpty>{emptyText}</CommandEmpty>
123
+ {allGroups.map((group) => (
124
+ <CommandGroup key={group.id} heading={group.label || undefined}>
125
+ {group.items.map((item) => (
126
+ <CommandItem
127
+ key={item.id}
128
+ value={item.label}
129
+ keywords={item.keywords}
130
+ onSelect={() => handleSelect(item)}
131
+ >
132
+ {item.icon}
133
+ <span>{item.label}</span>
134
+ {item.shortcut && (
135
+ <CommandShortcut>{item.shortcut}</CommandShortcut>
136
+ )}
137
+ </CommandItem>
138
+ ))}
139
+ </CommandGroup>
140
+ ))}
141
+ </CommandList>
142
+ </Command>
143
+ </CommandDialog>
144
+ </>
145
+ );
146
+ }
147
+
148
+ export { CommandSearch };
@@ -0,0 +1,98 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+
3
+ function Empty({ ...props }: Omit<React.ComponentProps<"div">, "className">) {
4
+ return (
5
+ <div
6
+ data-slot="empty"
7
+ className="gap-4 rounded-lg border-dashed p-12 flex w-full min-w-0 flex-1 flex-col items-center justify-center text-center text-balance"
8
+ {...props}
9
+ />
10
+ );
11
+ }
12
+
13
+ function EmptyHeader({
14
+ ...props
15
+ }: Omit<React.ComponentProps<"div">, "className">) {
16
+ return (
17
+ <div
18
+ data-slot="empty-header"
19
+ className="gap-2 flex max-w-sm flex-col items-center"
20
+ {...props}
21
+ />
22
+ );
23
+ }
24
+
25
+ const emptyMediaVariants = cva(
26
+ "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
27
+ {
28
+ variants: {
29
+ variant: {
30
+ default: "bg-transparent",
31
+ icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6",
32
+ },
33
+ },
34
+ defaultVariants: {
35
+ variant: "default",
36
+ },
37
+ },
38
+ );
39
+
40
+ function EmptyMedia({
41
+ variant = "default",
42
+ ...props
43
+ }: Omit<React.ComponentProps<"div">, "className"> &
44
+ VariantProps<typeof emptyMediaVariants>) {
45
+ return (
46
+ <div
47
+ data-slot="empty-icon"
48
+ data-variant={variant}
49
+ className={emptyMediaVariants({ variant })}
50
+ {...props}
51
+ />
52
+ );
53
+ }
54
+
55
+ function EmptyTitle({
56
+ ...props
57
+ }: Omit<React.ComponentProps<"div">, "className">) {
58
+ return (
59
+ <div
60
+ data-slot="empty-title"
61
+ className="text-lg font-medium tracking-tight"
62
+ {...props}
63
+ />
64
+ );
65
+ }
66
+
67
+ function EmptyDescription({
68
+ ...props
69
+ }: Omit<React.ComponentProps<"p">, "className">) {
70
+ return (
71
+ <div
72
+ data-slot="empty-description"
73
+ className="text-sm/relaxed text-muted-foreground [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4"
74
+ {...props}
75
+ />
76
+ );
77
+ }
78
+
79
+ function EmptyContent({
80
+ ...props
81
+ }: Omit<React.ComponentProps<"div">, "className">) {
82
+ return (
83
+ <div
84
+ data-slot="empty-content"
85
+ className="gap-4 text-sm flex w-full max-w-sm min-w-0 flex-col items-center text-balance"
86
+ {...props}
87
+ />
88
+ );
89
+ }
90
+
91
+ export {
92
+ Empty,
93
+ EmptyContent,
94
+ EmptyDescription,
95
+ EmptyHeader,
96
+ EmptyMedia,
97
+ EmptyTitle,
98
+ };
@@ -0,0 +1,217 @@
1
+ "use client";
2
+
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+ import { useMemo } from "react";
5
+
6
+ import { Separator as SeparatorPrimitive } from "@base-ui/react/separator";
7
+
8
+ function FieldSet({
9
+ ...props
10
+ }: Omit<React.ComponentProps<"fieldset">, "className">) {
11
+ return (
12
+ <fieldset
13
+ data-slot="field-set"
14
+ className="gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3 flex flex-col"
15
+ {...props}
16
+ />
17
+ );
18
+ }
19
+
20
+ function FieldLegend({
21
+ variant = "legend",
22
+ ...props
23
+ }: Omit<React.ComponentProps<"legend">, "className"> & {
24
+ variant?: "legend" | "label";
25
+ }) {
26
+ return (
27
+ <legend
28
+ data-slot="field-legend"
29
+ data-variant={variant}
30
+ className="mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base"
31
+ {...props}
32
+ />
33
+ );
34
+ }
35
+
36
+ function FieldGroup({
37
+ ...props
38
+ }: Omit<React.ComponentProps<"div">, "className">) {
39
+ return (
40
+ <div
41
+ data-slot="field-group"
42
+ className="gap-5 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4 group/field-group @container/field-group flex w-full flex-col"
43
+ {...props}
44
+ />
45
+ );
46
+ }
47
+
48
+ const fieldVariants = cva(
49
+ "data-[invalid=true]:text-destructive gap-2 group/field flex w-full",
50
+ {
51
+ variants: {
52
+ orientation: {
53
+ vertical: "flex-col [&>*]:w-full [&>.sr-only]:w-auto",
54
+ horizontal:
55
+ "flex-row items-center [&>[data-slot=field-label]]:flex-auto has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
56
+ responsive:
57
+ "flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto @md/field-group:[&>[data-slot=field-label]]:flex-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
58
+ },
59
+ },
60
+ defaultVariants: {
61
+ orientation: "vertical",
62
+ },
63
+ },
64
+ );
65
+
66
+ function Field({
67
+ orientation = "vertical",
68
+ ...props
69
+ }: Omit<React.ComponentProps<"div">, "className"> &
70
+ VariantProps<typeof fieldVariants>) {
71
+ return (
72
+ <div
73
+ role="group"
74
+ data-slot="field"
75
+ data-orientation={orientation}
76
+ className={fieldVariants({ orientation })}
77
+ {...props}
78
+ />
79
+ );
80
+ }
81
+
82
+ function FieldContent({
83
+ ...props
84
+ }: Omit<React.ComponentProps<"div">, "className">) {
85
+ return (
86
+ <div
87
+ data-slot="field-content"
88
+ className="gap-0.5 group/field-content flex flex-1 flex-col leading-snug"
89
+ {...props}
90
+ />
91
+ );
92
+ }
93
+
94
+ function FieldLabel({
95
+ ...props
96
+ }: Omit<React.ComponentProps<"label">, "className">) {
97
+ return (
98
+ <label
99
+ data-slot="field-label"
100
+ className="gap-2 text-sm leading-none font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed has-data-checked:bg-primary/5 has-data-checked:border-primary dark:has-data-checked:bg-primary/10 group-data-[disabled=true]/field:opacity-50 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-2.5 group/field-label peer/field-label w-fit leading-snug has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col"
101
+ {...props}
102
+ />
103
+ );
104
+ }
105
+
106
+ function FieldTitle({
107
+ ...props
108
+ }: Omit<React.ComponentProps<"div">, "className">) {
109
+ return (
110
+ <div
111
+ data-slot="field-label"
112
+ className="gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50 flex w-fit items-center leading-snug"
113
+ {...props}
114
+ />
115
+ );
116
+ }
117
+
118
+ function FieldDescription({
119
+ ...props
120
+ }: Omit<React.ComponentProps<"p">, "className">) {
121
+ return (
122
+ <p
123
+ data-slot="field-description"
124
+ className="text-muted-foreground text-left text-sm [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance last:mt-0 nth-last-2:-mt-1 [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4"
125
+ {...props}
126
+ />
127
+ );
128
+ }
129
+
130
+ function FieldSeparator({
131
+ children,
132
+ ...props
133
+ }: Omit<React.ComponentProps<"div">, "className"> & {
134
+ children?: React.ReactNode;
135
+ }) {
136
+ return (
137
+ <div
138
+ data-slot="field-separator"
139
+ data-content={!!children}
140
+ className="-my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2 relative"
141
+ {...props}
142
+ >
143
+ <SeparatorPrimitive className="bg-border shrink-0 h-px w-full absolute inset-0 top-1/2" />
144
+ {children && (
145
+ <span
146
+ className="text-muted-foreground px-2 bg-background relative mx-auto block w-fit"
147
+ data-slot="field-separator-content"
148
+ >
149
+ {children}
150
+ </span>
151
+ )}
152
+ </div>
153
+ );
154
+ }
155
+
156
+ function FieldError({
157
+ children,
158
+ errors,
159
+ ...props
160
+ }: Omit<React.ComponentProps<"div">, "className"> & {
161
+ errors?: Array<{ message?: string } | undefined>;
162
+ }) {
163
+ const content = useMemo(() => {
164
+ if (children) {
165
+ return children;
166
+ }
167
+
168
+ if (!errors?.length) {
169
+ return null;
170
+ }
171
+
172
+ const uniqueErrors = [
173
+ ...new Map(errors.map((error) => [error?.message, error])).values(),
174
+ ];
175
+
176
+ if (uniqueErrors?.length == 1) {
177
+ return uniqueErrors[0]?.message;
178
+ }
179
+
180
+ return (
181
+ <ul className="ml-4 flex list-disc flex-col gap-1">
182
+ {uniqueErrors.map(
183
+ (error, index) =>
184
+ error?.message && <li key={index}>{error.message}</li>,
185
+ )}
186
+ </ul>
187
+ );
188
+ }, [children, errors]);
189
+
190
+ if (!content) {
191
+ return null;
192
+ }
193
+
194
+ return (
195
+ <div
196
+ role="alert"
197
+ data-slot="field-error"
198
+ className="text-destructive text-sm font-normal"
199
+ {...props}
200
+ >
201
+ {content}
202
+ </div>
203
+ );
204
+ }
205
+
206
+ export {
207
+ Field,
208
+ FieldContent,
209
+ FieldDescription,
210
+ FieldError,
211
+ FieldGroup,
212
+ FieldLabel,
213
+ FieldLegend,
214
+ FieldSeparator,
215
+ FieldSet,
216
+ FieldTitle,
217
+ };
@@ -0,0 +1,141 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import * as React from "react";
3
+
4
+ import { cn } from "../../../lib/utils";
5
+
6
+ const gridVariants = cva("grid", {
7
+ variants: {
8
+ gap: {
9
+ "0": "gap-0",
10
+ "1": "gap-1",
11
+ "2": "gap-2",
12
+ "3": "gap-3",
13
+ "4": "gap-4",
14
+ "5": "gap-5",
15
+ "6": "gap-6",
16
+ "8": "gap-8",
17
+ "10": "gap-10",
18
+ "12": "gap-12",
19
+ },
20
+ align: {
21
+ start: "items-start",
22
+ center: "items-center",
23
+ end: "items-end",
24
+ stretch: "items-stretch",
25
+ },
26
+ },
27
+ defaultVariants: {
28
+ gap: "4",
29
+ align: "stretch",
30
+ },
31
+ });
32
+
33
+ const gridColsVariants = cva("", {
34
+ variants: {
35
+ cols: {
36
+ "1": "grid-cols-1",
37
+ "2": "grid-cols-2",
38
+ "3": "grid-cols-3",
39
+ "4": "grid-cols-4",
40
+ "5": "grid-cols-5",
41
+ "6": "grid-cols-6",
42
+ "12": "grid-cols-12",
43
+ },
44
+ },
45
+ defaultVariants: {
46
+ cols: "1",
47
+ },
48
+ });
49
+
50
+ type ResponsiveCols = {
51
+ base?: "1" | "2" | "3" | "4" | "5" | "6" | "12";
52
+ sm?: "1" | "2" | "3" | "4" | "5" | "6" | "12";
53
+ md?: "1" | "2" | "3" | "4" | "5" | "6" | "12";
54
+ lg?: "1" | "2" | "3" | "4" | "5" | "6" | "12";
55
+ xl?: "1" | "2" | "3" | "4" | "5" | "6" | "12";
56
+ };
57
+
58
+ const responsiveColsMap: Record<string, Record<string, string>> = {
59
+ base: {
60
+ "1": "grid-cols-1",
61
+ "2": "grid-cols-2",
62
+ "3": "grid-cols-3",
63
+ "4": "grid-cols-4",
64
+ "5": "grid-cols-5",
65
+ "6": "grid-cols-6",
66
+ "12": "grid-cols-12",
67
+ },
68
+ sm: {
69
+ "1": "sm:grid-cols-1",
70
+ "2": "sm:grid-cols-2",
71
+ "3": "sm:grid-cols-3",
72
+ "4": "sm:grid-cols-4",
73
+ "5": "sm:grid-cols-5",
74
+ "6": "sm:grid-cols-6",
75
+ "12": "sm:grid-cols-12",
76
+ },
77
+ md: {
78
+ "1": "md:grid-cols-1",
79
+ "2": "md:grid-cols-2",
80
+ "3": "md:grid-cols-3",
81
+ "4": "md:grid-cols-4",
82
+ "5": "md:grid-cols-5",
83
+ "6": "md:grid-cols-6",
84
+ "12": "md:grid-cols-12",
85
+ },
86
+ lg: {
87
+ "1": "lg:grid-cols-1",
88
+ "2": "lg:grid-cols-2",
89
+ "3": "lg:grid-cols-3",
90
+ "4": "lg:grid-cols-4",
91
+ "5": "lg:grid-cols-5",
92
+ "6": "lg:grid-cols-6",
93
+ "12": "lg:grid-cols-12",
94
+ },
95
+ xl: {
96
+ "1": "xl:grid-cols-1",
97
+ "2": "xl:grid-cols-2",
98
+ "3": "xl:grid-cols-3",
99
+ "4": "xl:grid-cols-4",
100
+ "5": "xl:grid-cols-5",
101
+ "6": "xl:grid-cols-6",
102
+ "12": "xl:grid-cols-12",
103
+ },
104
+ };
105
+
106
+ const getResponsiveClasses = (cols: ResponsiveCols): string => {
107
+ const classes: string[] = [];
108
+ const withBase: ResponsiveCols = cols.base ? cols : { base: "1", ...cols };
109
+
110
+ for (const [breakpoint, value] of Object.entries(withBase)) {
111
+ if (value && responsiveColsMap[breakpoint]?.[value]) {
112
+ classes.push(responsiveColsMap[breakpoint][value]);
113
+ }
114
+ }
115
+
116
+ return classes.join(" ");
117
+ };
118
+
119
+ interface GridProps
120
+ extends
121
+ Omit<React.ComponentProps<"div">, "cols" | "className">,
122
+ VariantProps<typeof gridVariants> {
123
+ cols?: "1" | "2" | "3" | "4" | "5" | "6" | "12" | ResponsiveCols;
124
+ }
125
+
126
+ function Grid({ cols = "1", gap, align, ...props }: GridProps) {
127
+ const colsClasses =
128
+ typeof cols === "object"
129
+ ? getResponsiveClasses(cols)
130
+ : gridColsVariants({ cols });
131
+
132
+ return (
133
+ <div
134
+ data-slot="grid"
135
+ className={cn(gridVariants({ gap, align }), colsClasses)}
136
+ {...props}
137
+ />
138
+ );
139
+ }
140
+
141
+ export { Grid, gridVariants };
@@ -0,0 +1,45 @@
1
+ "use client";
2
+
3
+ import { PreviewCard as PreviewCardPrimitive } from "@base-ui/react/preview-card";
4
+
5
+ function HoverCard({ ...props }: PreviewCardPrimitive.Root.Props) {
6
+ return <PreviewCardPrimitive.Root data-slot="hover-card" {...props} />;
7
+ }
8
+
9
+ function HoverCardTrigger({ ...props }: PreviewCardPrimitive.Trigger.Props) {
10
+ return (
11
+ <PreviewCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
12
+ );
13
+ }
14
+
15
+ function HoverCardContent({
16
+ side = "bottom",
17
+ sideOffset = 4,
18
+ align = "center",
19
+ alignOffset = 4,
20
+ ...props
21
+ }: Omit<PreviewCardPrimitive.Popup.Props, "className"> &
22
+ Pick<
23
+ PreviewCardPrimitive.Positioner.Props,
24
+ "align" | "alignOffset" | "side" | "sideOffset"
25
+ >) {
26
+ return (
27
+ <PreviewCardPrimitive.Portal data-slot="hover-card-portal">
28
+ <PreviewCardPrimitive.Positioner
29
+ align={align}
30
+ alignOffset={alignOffset}
31
+ side={side}
32
+ sideOffset={sideOffset}
33
+ className="isolate z-50"
34
+ >
35
+ <PreviewCardPrimitive.Popup
36
+ data-slot="hover-card-content"
37
+ className="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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground w-64 rounded-lg p-4 text-sm shadow-md ring-1 duration-100 z-50 origin-(--transform-origin) outline-hidden"
38
+ {...props}
39
+ />
40
+ </PreviewCardPrimitive.Positioner>
41
+ </PreviewCardPrimitive.Portal>
42
+ );
43
+ }
44
+
45
+ export { HoverCard, HoverCardContent, HoverCardTrigger };
@@ -0,0 +1,29 @@
1
+ export * from "./accordion";
2
+ export * from "./ai-chat";
3
+ export * from "./alert";
4
+ export * from "./breadcrumb";
5
+ export * from "./button-group";
6
+ export * from "./card";
7
+ export * from "./collapsible";
8
+ export * from "./command-search";
9
+ export * from "./empty";
10
+ export * from "./field";
11
+ export * from "./grid";
12
+ export * from "./hover-card";
13
+ export * from "./input-group";
14
+ export * from "./input-otp";
15
+ export * from "./item";
16
+ export * from "./page-header";
17
+ export * from "./pagination";
18
+ export * from "./popover";
19
+ export * from "./radio-group";
20
+ export * from "./resizable";
21
+ export * from "./scroll-area";
22
+ export * from "./section";
23
+ export * from "./select";
24
+ export * from "./settings";
25
+ export * from "./table";
26
+ export * from "./tabs";
27
+ export * from "./theme-switcher";
28
+ export * from "./toggle-group";
29
+ export * from "./tooltip";