@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,151 @@
|
|
|
1
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button";
|
|
2
|
+
import { Input as InputPrimitive } from "@base-ui/react/input";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../../lib/utils";
|
|
7
|
+
import { buttonVariants } from "../atoms/button";
|
|
8
|
+
|
|
9
|
+
function InputGroup({
|
|
10
|
+
...props
|
|
11
|
+
}: Omit<React.ComponentProps<"div">, "className">) {
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
data-slot="input-group"
|
|
15
|
+
role="group"
|
|
16
|
+
className="border-input dark:bg-input/30 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-disabled:bg-input/50 dark:has-disabled:bg-input/80 h-8 rounded-lg border transition-colors has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot][aria-invalid=true]]:ring-[3px] has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5 [[data-slot=combobox-content]_&]:focus-within:border-inherit [[data-slot=combobox-content]_&]:focus-within:ring-0 group/input-group relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto"
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const inputGroupAddonVariants = cva(
|
|
23
|
+
"text-muted-foreground h-auto gap-2 py-1.5 text-sm font-medium group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4 flex cursor-text items-center justify-center select-none",
|
|
24
|
+
{
|
|
25
|
+
variants: {
|
|
26
|
+
align: {
|
|
27
|
+
"inline-start":
|
|
28
|
+
"pl-2 has-[>button]:ml-[-0.25rem] has-[>kbd]:ml-[-0.15rem] order-first",
|
|
29
|
+
"inline-end":
|
|
30
|
+
"pr-2 has-[>button]:mr-[-0.25rem] has-[>kbd]:mr-[-0.15rem] order-last",
|
|
31
|
+
"block-start":
|
|
32
|
+
"px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2 order-first w-full justify-start",
|
|
33
|
+
"block-end":
|
|
34
|
+
"px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2 order-last w-full justify-start",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
defaultVariants: {
|
|
38
|
+
align: "inline-start",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
function InputGroupAddon({
|
|
44
|
+
align = "inline-start",
|
|
45
|
+
...props
|
|
46
|
+
}: Omit<React.ComponentProps<"div">, "className"> &
|
|
47
|
+
VariantProps<typeof inputGroupAddonVariants>) {
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
role="group"
|
|
51
|
+
data-slot="input-group-addon"
|
|
52
|
+
data-align={align}
|
|
53
|
+
className={inputGroupAddonVariants({ align })}
|
|
54
|
+
onClick={(e) => {
|
|
55
|
+
if ((e.target as HTMLElement).closest("button")) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
e.currentTarget.parentElement?.querySelector("input")?.focus();
|
|
59
|
+
}}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const inputGroupButtonVariants = cva(
|
|
66
|
+
"gap-2 text-sm shadow-none flex items-center",
|
|
67
|
+
{
|
|
68
|
+
variants: {
|
|
69
|
+
size: {
|
|
70
|
+
xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
|
|
71
|
+
sm: "",
|
|
72
|
+
"icon-xs":
|
|
73
|
+
"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
|
|
74
|
+
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
defaultVariants: {
|
|
78
|
+
size: "xs",
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
function InputGroupButton({
|
|
84
|
+
type = "button",
|
|
85
|
+
variant = "ghost",
|
|
86
|
+
size = "xs",
|
|
87
|
+
...props
|
|
88
|
+
}: Omit<ButtonPrimitive.Props, "className" | "type"> &
|
|
89
|
+
VariantProps<typeof inputGroupButtonVariants> & {
|
|
90
|
+
variant?:
|
|
91
|
+
| "default"
|
|
92
|
+
| "outline"
|
|
93
|
+
| "secondary"
|
|
94
|
+
| "ghost"
|
|
95
|
+
| "destructive"
|
|
96
|
+
| "link";
|
|
97
|
+
type?: "button" | "submit" | "reset";
|
|
98
|
+
}) {
|
|
99
|
+
return (
|
|
100
|
+
<ButtonPrimitive
|
|
101
|
+
type={type}
|
|
102
|
+
data-size={size}
|
|
103
|
+
className={cn(
|
|
104
|
+
buttonVariants({ variant }),
|
|
105
|
+
inputGroupButtonVariants({ size }),
|
|
106
|
+
)}
|
|
107
|
+
{...props}
|
|
108
|
+
/>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function InputGroupText({
|
|
113
|
+
align = "inline-start",
|
|
114
|
+
...props
|
|
115
|
+
}: Omit<React.ComponentProps<"div">, "className"> &
|
|
116
|
+
VariantProps<typeof inputGroupAddonVariants>) {
|
|
117
|
+
return <InputGroupAddon align={align} {...props} />;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function InputGroupInput({
|
|
121
|
+
...props
|
|
122
|
+
}: Omit<React.ComponentProps<"input">, "className">) {
|
|
123
|
+
return (
|
|
124
|
+
<InputPrimitive
|
|
125
|
+
data-slot="input-group-control"
|
|
126
|
+
className="h-8 rounded-none border-0 bg-transparent px-2.5 py-1 text-base shadow-none ring-0 transition-colors focus-visible:ring-0 aria-invalid:ring-0 md:text-sm placeholder:text-muted-foreground w-full min-w-0 outline-none disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 dark:bg-transparent flex-1"
|
|
127
|
+
{...props}
|
|
128
|
+
/>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function InputGroupTextarea({
|
|
133
|
+
...props
|
|
134
|
+
}: Omit<React.ComponentProps<"textarea">, "className">) {
|
|
135
|
+
return (
|
|
136
|
+
<textarea
|
|
137
|
+
data-slot="input-group-control"
|
|
138
|
+
className="rounded-none border-0 bg-transparent px-2.5 py-2 text-base shadow-none ring-0 transition-colors focus-visible:ring-0 aria-invalid:ring-0 md:text-sm placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50 dark:bg-transparent flex-1 resize-none"
|
|
139
|
+
{...props}
|
|
140
|
+
/>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export {
|
|
145
|
+
InputGroup,
|
|
146
|
+
InputGroupAddon,
|
|
147
|
+
InputGroupButton,
|
|
148
|
+
InputGroupInput,
|
|
149
|
+
InputGroupText,
|
|
150
|
+
InputGroupTextarea,
|
|
151
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { OTPInput, OTPInputContext } from "input-otp";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
import { MinusIcon } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
function InputOTP({
|
|
7
|
+
className: _className,
|
|
8
|
+
...props
|
|
9
|
+
}: React.ComponentProps<typeof OTPInput>) {
|
|
10
|
+
return (
|
|
11
|
+
<OTPInput
|
|
12
|
+
data-slot="input-otp"
|
|
13
|
+
containerClassName="cn-input-otp flex items-center has-disabled:opacity-50"
|
|
14
|
+
spellCheck={false}
|
|
15
|
+
className="disabled:cursor-not-allowed"
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function InputOTPGroup({
|
|
22
|
+
...props
|
|
23
|
+
}: Omit<React.ComponentProps<"div">, "className">) {
|
|
24
|
+
return (
|
|
25
|
+
<div
|
|
26
|
+
data-slot="input-otp-group"
|
|
27
|
+
className="has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive rounded-md has-aria-invalid:ring-[3px] flex items-center"
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function InputOTPSlot({
|
|
34
|
+
index,
|
|
35
|
+
...props
|
|
36
|
+
}: Omit<React.ComponentProps<"div">, "className"> & {
|
|
37
|
+
index: number;
|
|
38
|
+
}) {
|
|
39
|
+
const inputOTPContext = React.useContext(OTPInputContext);
|
|
40
|
+
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {};
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
data-slot="input-otp-slot"
|
|
45
|
+
data-active={isActive}
|
|
46
|
+
className="dark:bg-input/30 border-input data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive size-9 border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:ring-[3px] relative flex items-center justify-center data-[active=true]:z-10"
|
|
47
|
+
{...props}
|
|
48
|
+
>
|
|
49
|
+
{char}
|
|
50
|
+
{hasFakeCaret && (
|
|
51
|
+
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
|
52
|
+
<div className="animate-caret-blink bg-foreground h-4 w-px duration-1000 bg-foreground h-4 w-px" />
|
|
53
|
+
</div>
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function InputOTPSeparator({
|
|
60
|
+
...props
|
|
61
|
+
}: Omit<React.ComponentProps<"div">, "className">) {
|
|
62
|
+
return (
|
|
63
|
+
<div
|
|
64
|
+
data-slot="input-otp-separator"
|
|
65
|
+
className="[&_svg:not([class*='size-'])]:size-4 flex items-center"
|
|
66
|
+
role="separator"
|
|
67
|
+
{...props}
|
|
68
|
+
>
|
|
69
|
+
<MinusIcon />
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot };
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
2
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
|
|
5
|
+
import { Separator as SeparatorPrimitive } from "@base-ui/react/separator";
|
|
6
|
+
|
|
7
|
+
function ItemGroup({
|
|
8
|
+
...props
|
|
9
|
+
}: Omit<React.ComponentProps<"div">, "className">) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
role="list"
|
|
13
|
+
data-slot="item-group"
|
|
14
|
+
className="gap-4 has-[[data-size=sm]]:gap-2.5 has-[[data-size=xs]]:gap-2 group/item-group flex w-full flex-col"
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function ItemSeparator({
|
|
21
|
+
...props
|
|
22
|
+
}: Omit<SeparatorPrimitive.Props, "className">) {
|
|
23
|
+
return (
|
|
24
|
+
<SeparatorPrimitive
|
|
25
|
+
data-slot="item-separator"
|
|
26
|
+
orientation="horizontal"
|
|
27
|
+
className="bg-border shrink-0 h-px w-full my-2"
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const itemVariants = cva(
|
|
34
|
+
"[a]:hover:bg-muted rounded-md border text-sm w-full group/item focus-visible:border-ring focus-visible:ring-ring/50 flex items-center flex-wrap outline-none transition-colors duration-100 focus-visible:ring-[3px] [a]:transition-colors",
|
|
35
|
+
{
|
|
36
|
+
variants: {
|
|
37
|
+
variant: {
|
|
38
|
+
default: "border-transparent",
|
|
39
|
+
outline: "border-border",
|
|
40
|
+
muted: "bg-muted/50 border-transparent",
|
|
41
|
+
},
|
|
42
|
+
size: {
|
|
43
|
+
default: "gap-3.5 px-4 py-3.5",
|
|
44
|
+
sm: "gap-2.5 px-3 py-2.5",
|
|
45
|
+
xs: "gap-2 px-2.5 py-2 [[data-slot=dropdown-menu-content]_&]:p-0",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
defaultVariants: {
|
|
49
|
+
variant: "default",
|
|
50
|
+
size: "default",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
function Item({
|
|
56
|
+
variant = "default",
|
|
57
|
+
size = "default",
|
|
58
|
+
render,
|
|
59
|
+
...props
|
|
60
|
+
}: Omit<useRender.ComponentProps<"div">, "className"> &
|
|
61
|
+
VariantProps<typeof itemVariants>) {
|
|
62
|
+
return useRender({
|
|
63
|
+
defaultTagName: "div",
|
|
64
|
+
props: mergeProps<"div">(
|
|
65
|
+
{
|
|
66
|
+
className: itemVariants({ variant, size }),
|
|
67
|
+
},
|
|
68
|
+
props,
|
|
69
|
+
),
|
|
70
|
+
render,
|
|
71
|
+
state: {
|
|
72
|
+
slot: "item",
|
|
73
|
+
variant,
|
|
74
|
+
size,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const itemMediaVariants = cva(
|
|
80
|
+
"gap-2 group-has-[[data-slot=item-description]]/item:translate-y-0.5 group-has-[[data-slot=item-description]]/item:self-start flex shrink-0 items-center justify-center [&_svg]:pointer-events-none",
|
|
81
|
+
{
|
|
82
|
+
variants: {
|
|
83
|
+
variant: {
|
|
84
|
+
default: "bg-transparent",
|
|
85
|
+
icon: "[&_svg:not([class*='size-'])]:size-4",
|
|
86
|
+
image:
|
|
87
|
+
"size-10 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover",
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
defaultVariants: {
|
|
91
|
+
variant: "default",
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
function ItemMedia({
|
|
97
|
+
variant = "default",
|
|
98
|
+
...props
|
|
99
|
+
}: Omit<React.ComponentProps<"div">, "className"> &
|
|
100
|
+
VariantProps<typeof itemMediaVariants>) {
|
|
101
|
+
return (
|
|
102
|
+
<div
|
|
103
|
+
data-slot="item-media"
|
|
104
|
+
data-variant={variant}
|
|
105
|
+
className={itemMediaVariants({ variant })}
|
|
106
|
+
{...props}
|
|
107
|
+
/>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function ItemContent({
|
|
112
|
+
...props
|
|
113
|
+
}: Omit<React.ComponentProps<"div">, "className">) {
|
|
114
|
+
return (
|
|
115
|
+
<div
|
|
116
|
+
data-slot="item-content"
|
|
117
|
+
className="gap-1 group-data-[size=xs]/item:gap-0 flex flex-1 flex-col [&+[data-slot=item-content]]:flex-none"
|
|
118
|
+
{...props}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function ItemTitle({
|
|
124
|
+
...props
|
|
125
|
+
}: Omit<React.ComponentProps<"div">, "className">) {
|
|
126
|
+
return (
|
|
127
|
+
<div
|
|
128
|
+
data-slot="item-title"
|
|
129
|
+
className="gap-2 text-sm leading-snug font-medium underline-offset-4 line-clamp-1 flex w-fit items-center"
|
|
130
|
+
{...props}
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function ItemDescription({
|
|
136
|
+
...props
|
|
137
|
+
}: Omit<React.ComponentProps<"p">, "className">) {
|
|
138
|
+
return (
|
|
139
|
+
<p
|
|
140
|
+
data-slot="item-description"
|
|
141
|
+
className="text-muted-foreground text-left text-sm leading-normal group-data-[size=xs]/item:text-xs [&>a:hover]:text-primary line-clamp-2 font-normal [&>a]:underline [&>a]:underline-offset-4"
|
|
142
|
+
{...props}
|
|
143
|
+
/>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function ItemActions({
|
|
148
|
+
...props
|
|
149
|
+
}: Omit<React.ComponentProps<"div">, "className">) {
|
|
150
|
+
return (
|
|
151
|
+
<div
|
|
152
|
+
data-slot="item-actions"
|
|
153
|
+
className="gap-2 flex items-center"
|
|
154
|
+
{...props}
|
|
155
|
+
/>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function ItemHeader({
|
|
160
|
+
...props
|
|
161
|
+
}: Omit<React.ComponentProps<"div">, "className">) {
|
|
162
|
+
return (
|
|
163
|
+
<div
|
|
164
|
+
data-slot="item-header"
|
|
165
|
+
className="gap-2 flex basis-full items-center justify-between"
|
|
166
|
+
{...props}
|
|
167
|
+
/>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function ItemFooter({
|
|
172
|
+
...props
|
|
173
|
+
}: Omit<React.ComponentProps<"div">, "className">) {
|
|
174
|
+
return (
|
|
175
|
+
<div
|
|
176
|
+
data-slot="item-footer"
|
|
177
|
+
className="gap-2 flex basis-full items-center justify-between"
|
|
178
|
+
{...props}
|
|
179
|
+
/>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export {
|
|
184
|
+
Item,
|
|
185
|
+
ItemActions,
|
|
186
|
+
ItemContent,
|
|
187
|
+
ItemDescription,
|
|
188
|
+
ItemFooter,
|
|
189
|
+
ItemGroup,
|
|
190
|
+
ItemHeader,
|
|
191
|
+
ItemMedia,
|
|
192
|
+
ItemSeparator,
|
|
193
|
+
ItemTitle,
|
|
194
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { Heading } from "../atoms/heading";
|
|
4
|
+
import { Text } from "../atoms/text";
|
|
5
|
+
import { Stack } from "../atoms/stack";
|
|
6
|
+
|
|
7
|
+
interface PageHeaderProps extends Omit<
|
|
8
|
+
React.ComponentProps<"div">,
|
|
9
|
+
"className"
|
|
10
|
+
> {
|
|
11
|
+
title: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
/** Additional descriptive text below description */
|
|
14
|
+
meta?: string;
|
|
15
|
+
actions?: React.ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function PageHeader({
|
|
19
|
+
title,
|
|
20
|
+
description,
|
|
21
|
+
meta,
|
|
22
|
+
actions,
|
|
23
|
+
children,
|
|
24
|
+
...props
|
|
25
|
+
}: PageHeaderProps) {
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
data-slot="page-header"
|
|
29
|
+
className="flex items-center justify-between gap-4"
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
<Stack gap="1">
|
|
33
|
+
<Heading level="1">{title}</Heading>
|
|
34
|
+
{description && (
|
|
35
|
+
<Text size="sm" variant="muted">
|
|
36
|
+
{description}
|
|
37
|
+
</Text>
|
|
38
|
+
)}
|
|
39
|
+
{meta && (
|
|
40
|
+
<Text size="xs" variant="muted">
|
|
41
|
+
{meta}
|
|
42
|
+
</Text>
|
|
43
|
+
)}
|
|
44
|
+
{children}
|
|
45
|
+
</Stack>
|
|
46
|
+
{actions && (
|
|
47
|
+
<div className="flex shrink-0 items-center gap-3">{actions}</div>
|
|
48
|
+
)}
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function PageHeaderTitle({
|
|
54
|
+
...props
|
|
55
|
+
}: Omit<React.ComponentProps<typeof Heading>, "className">) {
|
|
56
|
+
return <Heading data-slot="page-header-title" level="1" {...props} />;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function PageHeaderDescription({
|
|
60
|
+
...props
|
|
61
|
+
}: Omit<React.ComponentProps<typeof Text>, "className">) {
|
|
62
|
+
return (
|
|
63
|
+
<Text
|
|
64
|
+
data-slot="page-header-description"
|
|
65
|
+
size="sm"
|
|
66
|
+
variant="muted"
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function PageHeaderActions({
|
|
73
|
+
...props
|
|
74
|
+
}: Omit<React.ComponentProps<"div">, "className">) {
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
data-slot="page-header-actions"
|
|
78
|
+
className="flex shrink-0 items-center gap-3"
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export {
|
|
85
|
+
PageHeader,
|
|
86
|
+
PageHeaderActions,
|
|
87
|
+
PageHeaderDescription,
|
|
88
|
+
PageHeaderTitle,
|
|
89
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ChevronLeftIcon,
|
|
5
|
+
ChevronRightIcon,
|
|
6
|
+
MoreHorizontalIcon,
|
|
7
|
+
} from "lucide-react";
|
|
8
|
+
import { buttonVariants } from "../atoms/button";
|
|
9
|
+
|
|
10
|
+
function Pagination({
|
|
11
|
+
...props
|
|
12
|
+
}: Omit<React.ComponentProps<"nav">, "className">) {
|
|
13
|
+
return (
|
|
14
|
+
<nav
|
|
15
|
+
role="navigation"
|
|
16
|
+
aria-label="pagination"
|
|
17
|
+
data-slot="pagination"
|
|
18
|
+
className="mx-auto flex w-full justify-center"
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function PaginationContent({
|
|
25
|
+
...props
|
|
26
|
+
}: Omit<React.ComponentProps<"ul">, "className">) {
|
|
27
|
+
return (
|
|
28
|
+
<ul
|
|
29
|
+
data-slot="pagination-content"
|
|
30
|
+
className="gap-1 flex items-center"
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function PaginationItem({
|
|
37
|
+
...props
|
|
38
|
+
}: Omit<React.ComponentProps<"li">, "className">) {
|
|
39
|
+
return <li data-slot="pagination-item" {...props} />;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type PaginationLinkProps = {
|
|
43
|
+
isActive?: boolean;
|
|
44
|
+
size?: "default" | "icon";
|
|
45
|
+
} & Omit<React.ComponentProps<"a">, "className">;
|
|
46
|
+
|
|
47
|
+
function PaginationLink({
|
|
48
|
+
isActive,
|
|
49
|
+
size = "icon",
|
|
50
|
+
...props
|
|
51
|
+
}: PaginationLinkProps) {
|
|
52
|
+
return (
|
|
53
|
+
<ButtonPrimitive
|
|
54
|
+
className={buttonVariants({
|
|
55
|
+
variant: isActive ? "outline" : "ghost",
|
|
56
|
+
size,
|
|
57
|
+
})}
|
|
58
|
+
render={
|
|
59
|
+
<a
|
|
60
|
+
aria-current={isActive ? "page" : undefined}
|
|
61
|
+
data-slot="pagination-link"
|
|
62
|
+
data-active={isActive}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function PaginationPrevious({ ...props }: Omit<PaginationLinkProps, "size">) {
|
|
71
|
+
return (
|
|
72
|
+
<ButtonPrimitive
|
|
73
|
+
className={`${buttonVariants({ variant: "ghost", size: "default" })} pl-2`}
|
|
74
|
+
render={
|
|
75
|
+
<a
|
|
76
|
+
aria-label="Go to previous page"
|
|
77
|
+
data-slot="pagination-link"
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
}
|
|
81
|
+
>
|
|
82
|
+
<ChevronLeftIcon data-icon="inline-start" />
|
|
83
|
+
<span className="hidden sm:block">Previous</span>
|
|
84
|
+
</ButtonPrimitive>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function PaginationNext({ ...props }: Omit<PaginationLinkProps, "size">) {
|
|
89
|
+
return (
|
|
90
|
+
<ButtonPrimitive
|
|
91
|
+
className={`${buttonVariants({ variant: "ghost", size: "default" })} pr-2`}
|
|
92
|
+
render={
|
|
93
|
+
<a
|
|
94
|
+
aria-label="Go to next page"
|
|
95
|
+
data-slot="pagination-link"
|
|
96
|
+
{...props}
|
|
97
|
+
/>
|
|
98
|
+
}
|
|
99
|
+
>
|
|
100
|
+
<span className="hidden sm:block">Next</span>
|
|
101
|
+
<ChevronRightIcon data-icon="inline-end" />
|
|
102
|
+
</ButtonPrimitive>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function PaginationEllipsis({
|
|
107
|
+
...props
|
|
108
|
+
}: Omit<React.ComponentProps<"span">, "className">) {
|
|
109
|
+
return (
|
|
110
|
+
<span
|
|
111
|
+
aria-hidden
|
|
112
|
+
data-slot="pagination-ellipsis"
|
|
113
|
+
className="size-9 items-center justify-center [&_svg:not([class*='size-'])]:size-4 flex items-center justify-center"
|
|
114
|
+
{...props}
|
|
115
|
+
>
|
|
116
|
+
<MoreHorizontalIcon />
|
|
117
|
+
<span className="sr-only">More pages</span>
|
|
118
|
+
</span>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export {
|
|
123
|
+
Pagination,
|
|
124
|
+
PaginationContent,
|
|
125
|
+
PaginationEllipsis,
|
|
126
|
+
PaginationItem,
|
|
127
|
+
PaginationLink,
|
|
128
|
+
PaginationNext,
|
|
129
|
+
PaginationPrevious,
|
|
130
|
+
};
|