@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,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
+ };