@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.
- package/README.md +115 -0
- package/components.json +21 -0
- package/hooks/use-mobile.tsx +21 -0
- package/lib/utils.ts +6 -0
- package/package.json +104 -0
- package/postcss.config.mjs +8 -0
- package/src/components/atoms/aspect-ratio.tsx +21 -0
- package/src/components/atoms/avatar.tsx +91 -0
- package/src/components/atoms/badge.tsx +47 -0
- package/src/components/atoms/button.tsx +128 -0
- package/src/components/atoms/checkbox.tsx +24 -0
- package/src/components/atoms/container.tsx +42 -0
- package/src/components/atoms/heading.tsx +56 -0
- package/src/components/atoms/index.ts +21 -0
- package/src/components/atoms/input.tsx +18 -0
- package/src/components/atoms/kbd.tsx +23 -0
- package/src/components/atoms/label.tsx +15 -0
- package/src/components/atoms/logo.tsx +52 -0
- package/src/components/atoms/progress.tsx +79 -0
- package/src/components/atoms/separator.tsx +17 -0
- package/src/components/atoms/skeleton.tsx +13 -0
- package/src/components/atoms/slider.tsx +56 -0
- package/src/components/atoms/spinner.tsx +14 -0
- package/src/components/atoms/stack.tsx +126 -0
- package/src/components/atoms/switch.tsx +26 -0
- package/src/components/atoms/text.tsx +69 -0
- package/src/components/atoms/textarea.tsx +19 -0
- package/src/components/atoms/toggle.tsx +40 -0
- package/src/components/molecules/accordion.tsx +72 -0
- package/src/components/molecules/ai-chat.tsx +251 -0
- package/src/components/molecules/alert.tsx +131 -0
- package/src/components/molecules/breadcrumb.tsx +301 -0
- package/src/components/molecules/button-group.tsx +96 -0
- package/src/components/molecules/card.tsx +184 -0
- package/src/components/molecules/collapsible.tsx +21 -0
- package/src/components/molecules/command-search.tsx +148 -0
- package/src/components/molecules/empty.tsx +98 -0
- package/src/components/molecules/field.tsx +217 -0
- package/src/components/molecules/grid.tsx +141 -0
- package/src/components/molecules/hover-card.tsx +45 -0
- package/src/components/molecules/index.ts +29 -0
- package/src/components/molecules/input-group.tsx +151 -0
- package/src/components/molecules/input-otp.tsx +74 -0
- package/src/components/molecules/item.tsx +194 -0
- package/src/components/molecules/page-header.tsx +89 -0
- package/src/components/molecules/pagination.tsx +130 -0
- package/src/components/molecules/popover.tsx +96 -0
- package/src/components/molecules/radio-group.tsx +37 -0
- package/src/components/molecules/resizable.tsx +52 -0
- package/src/components/molecules/scroll-area.tsx +45 -0
- package/src/components/molecules/section.tsx +108 -0
- package/src/components/molecules/select.tsx +201 -0
- package/src/components/molecules/settings.tsx +197 -0
- package/src/components/molecules/table.tsx +111 -0
- package/src/components/molecules/tabs.tsx +74 -0
- package/src/components/molecules/theme-switcher.tsx +187 -0
- package/src/components/molecules/toggle-group.tsx +89 -0
- package/src/components/molecules/tooltip.tsx +66 -0
- package/src/components/organisms/alert-dialog.tsx +152 -0
- package/src/components/organisms/app-shell.tsx +939 -0
- package/src/components/organisms/calendar.tsx +212 -0
- package/src/components/organisms/carousel.tsx +230 -0
- package/src/components/organisms/chart.tsx +333 -0
- package/src/components/organisms/combobox.tsx +274 -0
- package/src/components/organisms/command.tsx +200 -0
- package/src/components/organisms/context-menu.tsx +229 -0
- package/src/components/organisms/dialog.tsx +134 -0
- package/src/components/organisms/drawer.tsx +123 -0
- package/src/components/organisms/dropdown-menu.tsx +256 -0
- package/src/components/organisms/index.ts +17 -0
- package/src/components/organisms/menubar.tsx +203 -0
- package/src/components/organisms/navigation-menu.tsx +143 -0
- package/src/components/organisms/page-layout.tsx +105 -0
- package/src/components/organisms/sheet.tsx +126 -0
- package/src/components/organisms/sidebar.tsx +723 -0
- package/src/components/organisms/sonner.tsx +41 -0
- package/src/components/ui/index.ts +3 -0
- package/src/index.ts +3 -0
- package/src/styles/globals.css +297 -0
- 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";
|