cooterlabs 1.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +228 -45
- package/bin/index.js +376 -22
- package/package.json +12 -4
- package/templates/_lib/utils.ts +28 -0
- package/templates/action-sheet/action-sheet.tsx +108 -0
- package/templates/alert/alert.tsx +146 -0
- package/templates/avatar/avatar.tsx +20 -8
- package/templates/badge/badge.tsx +114 -25
- package/templates/breadcrumb/breadcrumb.tsx +108 -0
- package/templates/button/button.tsx +39 -6
- package/templates/button-group/button-group.tsx +88 -0
- package/templates/card/card.tsx +31 -24
- package/templates/checkbox/checkbox.tsx +5 -9
- package/templates/context-menu/context-menu.tsx +172 -0
- package/templates/dialog/dialog.tsx +122 -0
- package/templates/dropdown/dropdown.tsx +175 -0
- package/templates/input/input.tsx +80 -14
- package/templates/label/label.tsx +2 -7
- package/templates/list/list.tsx +91 -0
- package/templates/loader/loader.tsx +33 -0
- package/templates/navbar-bottom/navbar-bottom.tsx +71 -0
- package/templates/navbar-top/navbar-top.tsx +79 -0
- package/templates/page-control/page-control.tsx +50 -0
- package/templates/pagination/pagination.tsx +104 -0
- package/templates/progress/progress.tsx +64 -0
- package/templates/radio/radio.tsx +36 -0
- package/templates/select/select.tsx +113 -0
- package/templates/stepper/stepper.tsx +88 -0
- package/templates/tabs/tabs.tsx +52 -0
- package/templates/textarea/textarea.tsx +2 -7
- package/templates/toggle/toggle.tsx +22 -0
- package/templates/tooltip/tooltip.tsx +32 -0
|
@@ -1,26 +1,92 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
|
-
import {
|
|
3
|
-
import { twMerge } from "tailwind-merge"
|
|
2
|
+
import { cn } from "../../lib/utils"
|
|
4
3
|
|
|
5
|
-
export
|
|
6
|
-
|
|
4
|
+
export const inputVariants = {
|
|
5
|
+
outline: "border-input bg-background hover:border-grey-300",
|
|
6
|
+
filled: "border-transparent bg-secondary hover:bg-muted",
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const inputSizes = {
|
|
10
|
+
large: "h-12 px-4 text-b1 rounded-[12px]",
|
|
11
|
+
medium: "h-10 px-3 text-b3 rounded-[8px]",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const inputStatuses = {
|
|
15
|
+
outline: {
|
|
16
|
+
success: "border-success-500",
|
|
17
|
+
info: "border-info-500",
|
|
18
|
+
warning: "border-warning-500",
|
|
19
|
+
error: "border-error-500",
|
|
20
|
+
},
|
|
21
|
+
filled: {
|
|
22
|
+
success: "bg-success-50",
|
|
23
|
+
info: "bg-info-50",
|
|
24
|
+
warning: "bg-warning-50",
|
|
25
|
+
error: "bg-error-50",
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const statusTextColors = {
|
|
30
|
+
success: "text-success-500",
|
|
31
|
+
info: "text-info-500",
|
|
32
|
+
warning: "text-warning-500",
|
|
33
|
+
error: "text-error-500",
|
|
7
34
|
}
|
|
8
35
|
|
|
9
36
|
export interface InputProps
|
|
10
|
-
extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
37
|
+
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> {
|
|
38
|
+
variant?: keyof typeof inputVariants
|
|
39
|
+
size?: keyof typeof inputSizes
|
|
40
|
+
status?: keyof typeof statusTextColors
|
|
41
|
+
helperText?: string
|
|
42
|
+
icon?: React.ReactNode
|
|
43
|
+
iconPosition?: "left" | "right"
|
|
44
|
+
}
|
|
11
45
|
|
|
12
46
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
13
|
-
(
|
|
47
|
+
(
|
|
48
|
+
{ className, type, variant = "outline", size = "large", status, helperText, icon, iconPosition = "left", ...props },
|
|
49
|
+
ref
|
|
50
|
+
) => {
|
|
14
51
|
return (
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
52
|
+
<div>
|
|
53
|
+
<div className="relative">
|
|
54
|
+
{icon && (
|
|
55
|
+
<span
|
|
56
|
+
className={cn(
|
|
57
|
+
"pointer-events-none absolute top-1/2 flex -translate-y-1/2 items-center text-muted-foreground size-6",
|
|
58
|
+
iconPosition === "right" ? "right-3" : "left-3",
|
|
59
|
+
status && statusTextColors[status]
|
|
60
|
+
)}
|
|
61
|
+
>
|
|
62
|
+
{icon}
|
|
63
|
+
</span>
|
|
64
|
+
)}
|
|
65
|
+
<input
|
|
66
|
+
type={type}
|
|
67
|
+
className={cn(
|
|
68
|
+
"flex w-full border-[1.5px] text-foreground transition-colors file:border-0 file:bg-transparent file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:shadow-focus aria-invalid:border-error-500 aria-invalid:bg-error-50 disabled:pointer-events-none disabled:border-grey-200 disabled:bg-secondary disabled:text-grey-400",
|
|
69
|
+
inputVariants[variant],
|
|
70
|
+
inputSizes[size],
|
|
71
|
+
status && inputStatuses[variant][status],
|
|
72
|
+
icon && (iconPosition === "right" ? "pr-12" : "pl-12"),
|
|
73
|
+
className
|
|
74
|
+
)}
|
|
75
|
+
ref={ref}
|
|
76
|
+
{...props}
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
{helperText && (
|
|
80
|
+
<p
|
|
81
|
+
className={cn(
|
|
82
|
+
"text-b3 mt-2 text-muted-foreground",
|
|
83
|
+
status && statusTextColors[status]
|
|
84
|
+
)}
|
|
85
|
+
>
|
|
86
|
+
{helperText}
|
|
87
|
+
</p>
|
|
20
88
|
)}
|
|
21
|
-
|
|
22
|
-
{...props}
|
|
23
|
-
/>
|
|
89
|
+
</div>
|
|
24
90
|
)
|
|
25
91
|
}
|
|
26
92
|
)
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
2
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
|
3
|
-
import {
|
|
4
|
-
import { twMerge } from "tailwind-merge"
|
|
5
|
-
|
|
6
|
-
function cn(...inputs: ClassValue[]) {
|
|
7
|
-
return twMerge(clsx(inputs))
|
|
8
|
-
}
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
9
4
|
|
|
10
5
|
const Label = React.forwardRef<
|
|
11
6
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
@@ -14,7 +9,7 @@ const Label = React.forwardRef<
|
|
|
14
9
|
<LabelPrimitive.Root
|
|
15
10
|
ref={ref}
|
|
16
11
|
className={cn(
|
|
17
|
-
"text-
|
|
12
|
+
"text-label text-foreground peer-disabled:pointer-events-none peer-disabled:text-grey-400",
|
|
18
13
|
className
|
|
19
14
|
)}
|
|
20
15
|
{...props}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "../../lib/utils"
|
|
3
|
+
|
|
4
|
+
const List = React.forwardRef<
|
|
5
|
+
HTMLDivElement,
|
|
6
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
7
|
+
>(({ className, ...props }, ref) => (
|
|
8
|
+
<div
|
|
9
|
+
ref={ref}
|
|
10
|
+
role="list"
|
|
11
|
+
className={cn("w-full divide-y divide-grey-100", className)}
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
))
|
|
15
|
+
List.displayName = "List"
|
|
16
|
+
|
|
17
|
+
const ListHeader = React.forwardRef<
|
|
18
|
+
HTMLDivElement,
|
|
19
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
20
|
+
>(({ className, ...props }, ref) => (
|
|
21
|
+
<div
|
|
22
|
+
ref={ref}
|
|
23
|
+
className={cn("px-4 py-2 text-label text-muted-foreground", className)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
))
|
|
27
|
+
ListHeader.displayName = "ListHeader"
|
|
28
|
+
|
|
29
|
+
export interface ListItemProps
|
|
30
|
+
extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
|
|
31
|
+
leading?: React.ReactNode
|
|
32
|
+
title?: React.ReactNode
|
|
33
|
+
subtitle?: React.ReactNode
|
|
34
|
+
trailing?: React.ReactNode
|
|
35
|
+
interactive?: boolean
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ListItem = React.forwardRef<HTMLDivElement, ListItemProps>(
|
|
39
|
+
(
|
|
40
|
+
{
|
|
41
|
+
className,
|
|
42
|
+
leading,
|
|
43
|
+
title,
|
|
44
|
+
subtitle,
|
|
45
|
+
trailing,
|
|
46
|
+
interactive = false,
|
|
47
|
+
children,
|
|
48
|
+
...props
|
|
49
|
+
},
|
|
50
|
+
ref
|
|
51
|
+
) => (
|
|
52
|
+
<div
|
|
53
|
+
ref={ref}
|
|
54
|
+
role="listitem"
|
|
55
|
+
className={cn(
|
|
56
|
+
"flex min-h-12 items-center gap-3 px-4 py-2",
|
|
57
|
+
interactive &&
|
|
58
|
+
"cursor-pointer transition-colors hover:bg-secondary active:bg-muted",
|
|
59
|
+
className
|
|
60
|
+
)}
|
|
61
|
+
{...props}
|
|
62
|
+
>
|
|
63
|
+
{leading && (
|
|
64
|
+
<span className="flex shrink-0 items-center justify-center text-muted-foreground [&_svg]:size-5">
|
|
65
|
+
{leading}
|
|
66
|
+
</span>
|
|
67
|
+
)}
|
|
68
|
+
<span className="flex min-w-0 flex-1 flex-col">
|
|
69
|
+
{title && (
|
|
70
|
+
<span className="truncate text-b2 text-foreground">
|
|
71
|
+
{title}
|
|
72
|
+
</span>
|
|
73
|
+
)}
|
|
74
|
+
{subtitle && (
|
|
75
|
+
<span className="truncate text-c1 text-muted-foreground">
|
|
76
|
+
{subtitle}
|
|
77
|
+
</span>
|
|
78
|
+
)}
|
|
79
|
+
{children}
|
|
80
|
+
</span>
|
|
81
|
+
{trailing && (
|
|
82
|
+
<span className="flex shrink-0 items-center gap-2 text-c1 text-muted-foreground [&_svg]:size-5">
|
|
83
|
+
{trailing}
|
|
84
|
+
</span>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
ListItem.displayName = "ListItem"
|
|
90
|
+
|
|
91
|
+
export { List, ListHeader, ListItem }
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "../../lib/utils"
|
|
3
|
+
|
|
4
|
+
const loaderSizes = {
|
|
5
|
+
tiny: "h-6 w-6 border-2",
|
|
6
|
+
small: "h-8 w-8 border-2",
|
|
7
|
+
medium: "h-10 w-10 border-4",
|
|
8
|
+
large: "h-12 w-12 border-4",
|
|
9
|
+
giant: "h-14 w-14 border-4",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface LoaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
13
|
+
size?: keyof typeof loaderSizes
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const Loader = React.forwardRef<HTMLDivElement, LoaderProps>(
|
|
17
|
+
({ className, size = "medium", ...props }, ref) => (
|
|
18
|
+
<div
|
|
19
|
+
ref={ref}
|
|
20
|
+
role="status"
|
|
21
|
+
aria-label="Loading"
|
|
22
|
+
className={cn(
|
|
23
|
+
"inline-block animate-spin rounded-full border-grey-200 border-t-primary",
|
|
24
|
+
loaderSizes[size],
|
|
25
|
+
className
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
Loader.displayName = "Loader"
|
|
32
|
+
|
|
33
|
+
export { Loader }
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "../../lib/utils"
|
|
3
|
+
|
|
4
|
+
const NavbarBottom = React.forwardRef<
|
|
5
|
+
HTMLElement,
|
|
6
|
+
React.HTMLAttributes<HTMLElement>
|
|
7
|
+
>(({ className, ...props }, ref) => (
|
|
8
|
+
<nav
|
|
9
|
+
ref={ref}
|
|
10
|
+
// box-content keeps the tab row a full h-12 above the safe-area padding
|
|
11
|
+
className={cn(
|
|
12
|
+
"box-content flex h-12 items-stretch border-t border-grey-100 bg-background pb-[env(safe-area-inset-bottom)]",
|
|
13
|
+
className
|
|
14
|
+
)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
))
|
|
18
|
+
NavbarBottom.displayName = "NavbarBottom"
|
|
19
|
+
|
|
20
|
+
export interface NavbarBottomItemProps
|
|
21
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
22
|
+
label?: string
|
|
23
|
+
active?: boolean
|
|
24
|
+
badge?: boolean
|
|
25
|
+
// Figma Style="Line Selector + Icon": active top indicator line, no label
|
|
26
|
+
lineSelector?: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const NavbarBottomItem = React.forwardRef<
|
|
30
|
+
HTMLButtonElement,
|
|
31
|
+
NavbarBottomItemProps
|
|
32
|
+
>(
|
|
33
|
+
(
|
|
34
|
+
{
|
|
35
|
+
className,
|
|
36
|
+
label,
|
|
37
|
+
active = false,
|
|
38
|
+
badge = false,
|
|
39
|
+
lineSelector = false,
|
|
40
|
+
children,
|
|
41
|
+
...props
|
|
42
|
+
},
|
|
43
|
+
ref
|
|
44
|
+
) => (
|
|
45
|
+
<button
|
|
46
|
+
ref={ref}
|
|
47
|
+
type="button"
|
|
48
|
+
aria-current={active ? "page" : undefined}
|
|
49
|
+
className={cn(
|
|
50
|
+
"relative flex flex-1 flex-col items-center justify-center gap-1 transition-colors focus-visible:outline-none focus-visible:shadow-focus disabled:pointer-events-none disabled:text-grey-400",
|
|
51
|
+
active ? "text-primary" : "text-grey-400 hover:bg-secondary",
|
|
52
|
+
className
|
|
53
|
+
)}
|
|
54
|
+
{...props}
|
|
55
|
+
>
|
|
56
|
+
{lineSelector && active && (
|
|
57
|
+
<span className="absolute inset-x-3 top-0 h-0.5 rounded-full bg-primary" />
|
|
58
|
+
)}
|
|
59
|
+
<span className="relative [&_svg]:size-6 [&_svg]:shrink-0">
|
|
60
|
+
{children}
|
|
61
|
+
{badge && (
|
|
62
|
+
<span className="absolute -right-1 -top-1 size-2 rounded-full bg-error-500" />
|
|
63
|
+
)}
|
|
64
|
+
</span>
|
|
65
|
+
{label && !lineSelector && <span className="text-c3">{label}</span>}
|
|
66
|
+
</button>
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
NavbarBottomItem.displayName = "NavbarBottomItem"
|
|
70
|
+
|
|
71
|
+
export { NavbarBottom, NavbarBottomItem }
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { ChevronLeft } from "lucide-react"
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
4
|
+
|
|
5
|
+
export interface NavbarTopProps extends React.HTMLAttributes<HTMLElement> {
|
|
6
|
+
bordered?: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const NavbarTop = React.forwardRef<HTMLElement, NavbarTopProps>(
|
|
10
|
+
({ className, bordered = false, ...props }, ref) => (
|
|
11
|
+
<header
|
|
12
|
+
ref={ref}
|
|
13
|
+
className={cn(
|
|
14
|
+
"relative flex h-11 items-center justify-between gap-2 bg-background px-4",
|
|
15
|
+
bordered && "border-b border-grey-100",
|
|
16
|
+
className
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
NavbarTop.displayName = "NavbarTop"
|
|
23
|
+
|
|
24
|
+
const NavbarTopLeft = React.forwardRef<
|
|
25
|
+
HTMLButtonElement,
|
|
26
|
+
React.ButtonHTMLAttributes<HTMLButtonElement>
|
|
27
|
+
>(({ className, children, ...props }, ref) => (
|
|
28
|
+
<button
|
|
29
|
+
ref={ref}
|
|
30
|
+
type="button"
|
|
31
|
+
aria-label="Back"
|
|
32
|
+
className={cn(
|
|
33
|
+
"-ml-2 inline-flex items-center gap-1 rounded-xs p-2 text-b2 text-foreground transition-colors hover:bg-secondary focus-visible:outline-none focus-visible:shadow-focus disabled:pointer-events-none disabled:text-grey-400 [&_svg]:size-6 [&_svg]:shrink-0",
|
|
34
|
+
className
|
|
35
|
+
)}
|
|
36
|
+
{...props}
|
|
37
|
+
>
|
|
38
|
+
{children ?? <ChevronLeft />}
|
|
39
|
+
</button>
|
|
40
|
+
))
|
|
41
|
+
NavbarTopLeft.displayName = "NavbarTopLeft"
|
|
42
|
+
|
|
43
|
+
export interface NavbarTopTitleProps
|
|
44
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
45
|
+
subtitle?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const NavbarTopTitle = React.forwardRef<HTMLDivElement, NavbarTopTitleProps>(
|
|
49
|
+
({ className, subtitle, children, ...props }, ref) => (
|
|
50
|
+
<div
|
|
51
|
+
ref={ref}
|
|
52
|
+
className={cn(
|
|
53
|
+
"pointer-events-none absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-center text-s2 text-foreground",
|
|
54
|
+
className
|
|
55
|
+
)}
|
|
56
|
+
{...props}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
{subtitle && (
|
|
60
|
+
<div className="text-c1 text-muted-foreground">{subtitle}</div>
|
|
61
|
+
)}
|
|
62
|
+
</div>
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
NavbarTopTitle.displayName = "NavbarTopTitle"
|
|
66
|
+
|
|
67
|
+
const NavbarTopRight = React.forwardRef<
|
|
68
|
+
HTMLDivElement,
|
|
69
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
70
|
+
>(({ className, ...props }, ref) => (
|
|
71
|
+
<div
|
|
72
|
+
ref={ref}
|
|
73
|
+
className={cn("ml-auto flex items-center gap-2", className)}
|
|
74
|
+
{...props}
|
|
75
|
+
/>
|
|
76
|
+
))
|
|
77
|
+
NavbarTopRight.displayName = "NavbarTopRight"
|
|
78
|
+
|
|
79
|
+
export { NavbarTop, NavbarTopLeft, NavbarTopTitle, NavbarTopRight }
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "../../lib/utils"
|
|
3
|
+
|
|
4
|
+
export interface PageControlProps
|
|
5
|
+
extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
|
|
6
|
+
count: number
|
|
7
|
+
index: number
|
|
8
|
+
onChange?: (index: number) => void
|
|
9
|
+
orientation?: "horizontal" | "vertical"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const PageControl = React.forwardRef<HTMLDivElement, PageControlProps>(
|
|
13
|
+
(
|
|
14
|
+
{ className, count, index, onChange, orientation = "horizontal", ...props },
|
|
15
|
+
ref
|
|
16
|
+
) => (
|
|
17
|
+
<div
|
|
18
|
+
ref={ref}
|
|
19
|
+
role="tablist"
|
|
20
|
+
aria-label="Page control"
|
|
21
|
+
aria-orientation={orientation}
|
|
22
|
+
className={cn(
|
|
23
|
+
"flex items-center gap-2 p-2",
|
|
24
|
+
orientation === "vertical" && "flex-col",
|
|
25
|
+
className
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
{Array.from({ length: count }, (_, i) => (
|
|
30
|
+
<button
|
|
31
|
+
key={i}
|
|
32
|
+
type="button"
|
|
33
|
+
role="tab"
|
|
34
|
+
aria-label={`Page ${i + 1}`}
|
|
35
|
+
aria-selected={i === index}
|
|
36
|
+
tabIndex={i === index ? 0 : -1}
|
|
37
|
+
disabled={!onChange}
|
|
38
|
+
onClick={() => onChange?.(i)}
|
|
39
|
+
className={cn(
|
|
40
|
+
"size-2 rounded-full transition-colors focus-visible:outline-none focus-visible:shadow-focus disabled:pointer-events-none",
|
|
41
|
+
i === index ? "bg-primary" : "bg-grey-200 hover:bg-grey-300"
|
|
42
|
+
)}
|
|
43
|
+
/>
|
|
44
|
+
))}
|
|
45
|
+
</div>
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
PageControl.displayName = "PageControl"
|
|
49
|
+
|
|
50
|
+
export { PageControl }
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
4
|
+
|
|
5
|
+
const Pagination = ({
|
|
6
|
+
className,
|
|
7
|
+
...props
|
|
8
|
+
}: React.HTMLAttributes<HTMLElement>) => (
|
|
9
|
+
<nav
|
|
10
|
+
role="navigation"
|
|
11
|
+
aria-label="pagination"
|
|
12
|
+
className={cn("mx-auto flex w-full justify-center", className)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
Pagination.displayName = "Pagination"
|
|
17
|
+
|
|
18
|
+
const PaginationContent = React.forwardRef<
|
|
19
|
+
HTMLUListElement,
|
|
20
|
+
React.HTMLAttributes<HTMLUListElement>
|
|
21
|
+
>(({ className, ...props }, ref) => (
|
|
22
|
+
<ul
|
|
23
|
+
ref={ref}
|
|
24
|
+
className={cn("flex flex-row items-center gap-1", className)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
))
|
|
28
|
+
PaginationContent.displayName = "PaginationContent"
|
|
29
|
+
|
|
30
|
+
const PaginationItem = React.forwardRef<
|
|
31
|
+
HTMLLIElement,
|
|
32
|
+
React.LiHTMLAttributes<HTMLLIElement>
|
|
33
|
+
>(({ ...props }, ref) => <li ref={ref} {...props} />)
|
|
34
|
+
PaginationItem.displayName = "PaginationItem"
|
|
35
|
+
|
|
36
|
+
export interface PaginationLinkProps
|
|
37
|
+
extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
38
|
+
isActive?: boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const PaginationLink = React.forwardRef<HTMLAnchorElement, PaginationLinkProps>(
|
|
42
|
+
({ className, isActive = false, ...props }, ref) => (
|
|
43
|
+
<a
|
|
44
|
+
ref={ref}
|
|
45
|
+
aria-current={isActive ? "page" : undefined}
|
|
46
|
+
className={cn(
|
|
47
|
+
"inline-flex size-12 items-center justify-center whitespace-nowrap rounded-xs text-b4 transition-colors focus-visible:outline-none focus-visible:shadow-focus aria-disabled:pointer-events-none aria-disabled:text-grey-400 [&_svg]:size-4",
|
|
48
|
+
isActive
|
|
49
|
+
? "bg-primary text-primary-foreground"
|
|
50
|
+
: "text-foreground hover:bg-secondary",
|
|
51
|
+
className
|
|
52
|
+
)}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
PaginationLink.displayName = "PaginationLink"
|
|
58
|
+
|
|
59
|
+
const PaginationPrevious = React.forwardRef<
|
|
60
|
+
HTMLAnchorElement,
|
|
61
|
+
PaginationLinkProps
|
|
62
|
+
>(({ ...props }, ref) => (
|
|
63
|
+
<PaginationLink ref={ref} aria-label="Go to previous page" {...props}>
|
|
64
|
+
<ChevronLeft />
|
|
65
|
+
</PaginationLink>
|
|
66
|
+
))
|
|
67
|
+
PaginationPrevious.displayName = "PaginationPrevious"
|
|
68
|
+
|
|
69
|
+
const PaginationNext = React.forwardRef<HTMLAnchorElement, PaginationLinkProps>(
|
|
70
|
+
({ ...props }, ref) => (
|
|
71
|
+
<PaginationLink ref={ref} aria-label="Go to next page" {...props}>
|
|
72
|
+
<ChevronRight />
|
|
73
|
+
</PaginationLink>
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
PaginationNext.displayName = "PaginationNext"
|
|
77
|
+
|
|
78
|
+
const PaginationEllipsis = ({
|
|
79
|
+
className,
|
|
80
|
+
...props
|
|
81
|
+
}: React.HTMLAttributes<HTMLSpanElement>) => (
|
|
82
|
+
<span
|
|
83
|
+
aria-hidden="true"
|
|
84
|
+
className={cn(
|
|
85
|
+
"flex size-12 items-center justify-center text-muted-foreground",
|
|
86
|
+
className
|
|
87
|
+
)}
|
|
88
|
+
{...props}
|
|
89
|
+
>
|
|
90
|
+
<MoreHorizontal className="size-4" />
|
|
91
|
+
<span className="sr-only">More pages</span>
|
|
92
|
+
</span>
|
|
93
|
+
)
|
|
94
|
+
PaginationEllipsis.displayName = "PaginationEllipsis"
|
|
95
|
+
|
|
96
|
+
export {
|
|
97
|
+
Pagination,
|
|
98
|
+
PaginationContent,
|
|
99
|
+
PaginationItem,
|
|
100
|
+
PaginationLink,
|
|
101
|
+
PaginationPrevious,
|
|
102
|
+
PaginationNext,
|
|
103
|
+
PaginationEllipsis,
|
|
104
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
4
|
+
|
|
5
|
+
export interface ProgressProps
|
|
6
|
+
extends React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> {
|
|
7
|
+
label?: "left" | "right"
|
|
8
|
+
disabled?: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const Progress = React.forwardRef<
|
|
12
|
+
React.ElementRef<typeof ProgressPrimitive.Root>,
|
|
13
|
+
ProgressProps
|
|
14
|
+
>(({ className, value, label, disabled = false, ...props }, ref) => {
|
|
15
|
+
const percent = value ?? 0
|
|
16
|
+
const bar = (
|
|
17
|
+
<ProgressPrimitive.Root
|
|
18
|
+
ref={ref}
|
|
19
|
+
className={cn(
|
|
20
|
+
"relative h-2 w-full overflow-hidden rounded-full bg-grey-100",
|
|
21
|
+
!label && className
|
|
22
|
+
)}
|
|
23
|
+
value={value}
|
|
24
|
+
{...props}
|
|
25
|
+
>
|
|
26
|
+
<ProgressPrimitive.Indicator
|
|
27
|
+
className={cn(
|
|
28
|
+
"h-full w-full flex-1 rounded-full transition-all",
|
|
29
|
+
disabled ? "bg-grey-200" : "bg-primary"
|
|
30
|
+
)}
|
|
31
|
+
style={{ transform: `translateX(-${100 - percent}%)` }}
|
|
32
|
+
/>
|
|
33
|
+
</ProgressPrimitive.Root>
|
|
34
|
+
)
|
|
35
|
+
if (!label) return bar
|
|
36
|
+
return (
|
|
37
|
+
<div className={cn("flex items-center gap-3", className)}>
|
|
38
|
+
{label === "left" && (
|
|
39
|
+
<span
|
|
40
|
+
className={cn(
|
|
41
|
+
"text-c2 font-medium",
|
|
42
|
+
disabled ? "text-grey-400" : "text-muted-foreground"
|
|
43
|
+
)}
|
|
44
|
+
>
|
|
45
|
+
{percent}%
|
|
46
|
+
</span>
|
|
47
|
+
)}
|
|
48
|
+
{bar}
|
|
49
|
+
{label === "right" && (
|
|
50
|
+
<span
|
|
51
|
+
className={cn(
|
|
52
|
+
"text-c2 font-medium",
|
|
53
|
+
disabled ? "text-grey-400" : "text-muted-foreground"
|
|
54
|
+
)}
|
|
55
|
+
>
|
|
56
|
+
{percent}%
|
|
57
|
+
</span>
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
60
|
+
)
|
|
61
|
+
})
|
|
62
|
+
Progress.displayName = ProgressPrimitive.Root.displayName
|
|
63
|
+
|
|
64
|
+
export { Progress }
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
4
|
+
|
|
5
|
+
const RadioGroup = React.forwardRef<
|
|
6
|
+
React.ElementRef<typeof RadioGroupPrimitive.Root>,
|
|
7
|
+
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
|
|
8
|
+
>(({ className, ...props }, ref) => (
|
|
9
|
+
<RadioGroupPrimitive.Root
|
|
10
|
+
ref={ref}
|
|
11
|
+
className={cn("grid gap-2", className)}
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
))
|
|
15
|
+
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
|
|
16
|
+
|
|
17
|
+
const RadioGroupItem = React.forwardRef<
|
|
18
|
+
React.ElementRef<typeof RadioGroupPrimitive.Item>,
|
|
19
|
+
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
|
|
20
|
+
>(({ className, ...props }, ref) => (
|
|
21
|
+
<RadioGroupPrimitive.Item
|
|
22
|
+
ref={ref}
|
|
23
|
+
className={cn(
|
|
24
|
+
"aspect-square size-5 shrink-0 rounded-full border border-grey-300 bg-background transition-colors hover:border-grey-400 focus-visible:outline-none focus-visible:shadow-focus disabled:pointer-events-none disabled:border-grey-200 disabled:bg-muted data-[state=checked]:border-primary",
|
|
25
|
+
className
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
|
|
30
|
+
<span className="block size-2.5 rounded-full bg-primary" />
|
|
31
|
+
</RadioGroupPrimitive.Indicator>
|
|
32
|
+
</RadioGroupPrimitive.Item>
|
|
33
|
+
))
|
|
34
|
+
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
|
|
35
|
+
|
|
36
|
+
export { RadioGroup, RadioGroupItem }
|