@modlin/ui 0.0.1
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 +1 -0
- package/biome.json +36 -0
- package/components/alert.tsx +79 -0
- package/components/avatar.tsx +26 -0
- package/components/badge.tsx +31 -0
- package/components/button.tsx +135 -0
- package/components/card.tsx +85 -0
- package/components/checkbox.tsx +68 -0
- package/components/content.tsx +10 -0
- package/components/divider.tsx +17 -0
- package/components/footer.tsx +10 -0
- package/components/global.ts +34 -0
- package/components/header.tsx +22 -0
- package/components/heading.tsx +91 -0
- package/components/input.tsx +87 -0
- package/components/label.tsx +36 -0
- package/components/radio.tsx +33 -0
- package/components/select.tsx +127 -0
- package/components/stack.tsx +55 -0
- package/components/switch.tsx +79 -0
- package/components/text.tsx +9 -0
- package/components/textarea.tsx +47 -0
- package/components/toast.tsx +13 -0
- package/components/tooltip.tsx +13 -0
- package/components/typography.tsx +70 -0
- package/lib/utils.ts +7 -0
- package/next.config.ts +8 -0
- package/package.json +32 -0
- package/pages/_app.tsx +6 -0
- package/pages/_document.tsx +13 -0
- package/pages/api/hello.ts +10 -0
- package/pages/index.tsx +3 -0
- package/postcss.config.mjs +7 -0
- package/public/favicon.ico +0 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/styles/globals.css +26 -0
- package/tailwind.config.js +4 -0
- package/tsconfig.json +22 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Suffix UI Library for React
|
package/biome.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.1.2/schema.json",
|
|
3
|
+
"vcs": {
|
|
4
|
+
"enabled": false,
|
|
5
|
+
"clientKind": "git",
|
|
6
|
+
"useIgnoreFile": false
|
|
7
|
+
},
|
|
8
|
+
"files": {
|
|
9
|
+
"ignoreUnknown": false
|
|
10
|
+
},
|
|
11
|
+
"formatter": {
|
|
12
|
+
"enabled": true,
|
|
13
|
+
"indentStyle": "tab"
|
|
14
|
+
},
|
|
15
|
+
"linter": {
|
|
16
|
+
"enabled": true,
|
|
17
|
+
"rules": {
|
|
18
|
+
"recommended": true
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"javascript": {
|
|
22
|
+
"formatter": {
|
|
23
|
+
"quoteStyle": "double",
|
|
24
|
+
"semicolons": "asNeeded",
|
|
25
|
+
"arrowParentheses": "asNeeded"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"assist": {
|
|
29
|
+
"enabled": true,
|
|
30
|
+
"actions": {
|
|
31
|
+
"source": {
|
|
32
|
+
"organizeImports": "off"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils"
|
|
2
|
+
import type { ReactElement, ReactNode } from "react"
|
|
3
|
+
|
|
4
|
+
export interface AlertTitleProps {
|
|
5
|
+
children: ReactNode
|
|
6
|
+
className?: string
|
|
7
|
+
}
|
|
8
|
+
export function AlertTitle(props: Readonly<AlertTitleProps>) {
|
|
9
|
+
return (
|
|
10
|
+
<div
|
|
11
|
+
className={cn(
|
|
12
|
+
"flex flex-1 items-center h-4 font-medium",
|
|
13
|
+
"text-sm overflow-hidden whitespace-nowrap text-ellipsis",
|
|
14
|
+
props.className,
|
|
15
|
+
)}
|
|
16
|
+
>
|
|
17
|
+
{props.children}
|
|
18
|
+
</div>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AlertDescriptionProps {
|
|
23
|
+
children: ReactNode
|
|
24
|
+
className?: string
|
|
25
|
+
}
|
|
26
|
+
export function AlertDescription(props: Readonly<AlertDescriptionProps>) {
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
className={cn(
|
|
30
|
+
"grid flex flex-1 gap-1 col-start-2",
|
|
31
|
+
"text-sm",
|
|
32
|
+
props.className,
|
|
33
|
+
)}
|
|
34
|
+
>
|
|
35
|
+
{props.children}
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface AlertProps {
|
|
41
|
+
dismissible?: boolean
|
|
42
|
+
children:
|
|
43
|
+
| [
|
|
44
|
+
ReactElement,
|
|
45
|
+
ReactElement<AlertTitleProps>,
|
|
46
|
+
ReactElement<AlertDescriptionProps> | undefined,
|
|
47
|
+
]
|
|
48
|
+
| [ReactElement, ReactElement<AlertTitleProps>]
|
|
49
|
+
className?: string
|
|
50
|
+
}
|
|
51
|
+
export function Alert(props: Readonly<AlertProps>) {
|
|
52
|
+
return (
|
|
53
|
+
<div
|
|
54
|
+
className={cn(
|
|
55
|
+
"grid grid-cols-[16px_1fr] auto-rows-auto gap-x-4 gap-y-1 items-center justify-center",
|
|
56
|
+
"p-4 rounded-2xl",
|
|
57
|
+
"inset-ring inset-ring-black/15 dark:inset-ring-white/10",
|
|
58
|
+
props.className,
|
|
59
|
+
)}
|
|
60
|
+
>
|
|
61
|
+
{props.children}
|
|
62
|
+
</div>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
Alert.Destructive = (props: Readonly<AlertProps>) => {
|
|
66
|
+
return (
|
|
67
|
+
<div
|
|
68
|
+
className={cn(
|
|
69
|
+
"grid grid-cols-[16px_1fr] auto-rows-auto gap-x-4 gap-y-1 items-center justify-center",
|
|
70
|
+
"p-4 rounded-2xl",
|
|
71
|
+
"inset-ring inset-ring-black/15 dark:inset-ring-white/10",
|
|
72
|
+
"text-red-600 dark:text-red-400",
|
|
73
|
+
props.className,
|
|
74
|
+
)}
|
|
75
|
+
>
|
|
76
|
+
{props.children}
|
|
77
|
+
</div>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils"
|
|
2
|
+
import Image, { type ImageProps } from "next/image"
|
|
3
|
+
|
|
4
|
+
export interface AvatarProps extends ImageProps {
|
|
5
|
+
fallback: string
|
|
6
|
+
}
|
|
7
|
+
export default function Avatar({ fallback, ...props }: Readonly<AvatarProps>) {
|
|
8
|
+
const show = true
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<span
|
|
12
|
+
className={cn(
|
|
13
|
+
"relative flex shrink-0 size-8 overflow-hidden rounded-full ring ring-(--outline)",
|
|
14
|
+
props.className,
|
|
15
|
+
)}
|
|
16
|
+
>
|
|
17
|
+
{show ? (
|
|
18
|
+
<Image {...props} className="aspect-square size-full" />
|
|
19
|
+
) : (
|
|
20
|
+
<span className="flex items-center justify-center w-full text-sm font-medium leading-none">
|
|
21
|
+
{fallback.slice(0, 3).toUpperCase()}
|
|
22
|
+
</span>
|
|
23
|
+
)}
|
|
24
|
+
</span>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils"
|
|
2
|
+
import type { ReactNode } from "react"
|
|
3
|
+
|
|
4
|
+
const variants = {
|
|
5
|
+
primary: "",
|
|
6
|
+
secondary: "",
|
|
7
|
+
destructive: "",
|
|
8
|
+
outline: "",
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface BadgeProps {
|
|
12
|
+
variant?: keyof typeof variants
|
|
13
|
+
children: ReactNode | number
|
|
14
|
+
className?: string
|
|
15
|
+
}
|
|
16
|
+
export default function Badge(props: Readonly<BadgeProps>) {
|
|
17
|
+
return (
|
|
18
|
+
<span
|
|
19
|
+
className={cn(
|
|
20
|
+
"flex items-center justify-center gap-1",
|
|
21
|
+
"h-5 px-2 rounded-full [&>svg]:size-3",
|
|
22
|
+
"text-xs font-medium leading-none whitespace-nowrap",
|
|
23
|
+
"bg-black dark:bg-white text-white dark:text-black",
|
|
24
|
+
typeof props.children === "number" ? "w-5" : "w-fit",
|
|
25
|
+
props.className,
|
|
26
|
+
)}
|
|
27
|
+
>
|
|
28
|
+
{props.children}
|
|
29
|
+
</span>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils"
|
|
2
|
+
import { IconLoader2 } from "@tabler/icons-react"
|
|
3
|
+
import type { MouseEvent, FocusEvent, ReactNode, ReactHTMLElement } from "react"
|
|
4
|
+
import React from "react"
|
|
5
|
+
import type { Variant, Variants } from "./global"
|
|
6
|
+
|
|
7
|
+
const sizeMap = {
|
|
8
|
+
sm: cn("h-8 px-4 gap-x-1 text-sm rounded-full font-medium"),
|
|
9
|
+
md: cn("h-9 px-4.5 gap-x-1 text-sm rounded-full font-medium"),
|
|
10
|
+
lg: cn(
|
|
11
|
+
"h-11 px-5 gap-x-1 text-base rounded-full font-medium",
|
|
12
|
+
"[&>svg]:size-4",
|
|
13
|
+
),
|
|
14
|
+
xl: cn(
|
|
15
|
+
"h-12 px-6 gap-x-1 text-base rounded-full font-semibold",
|
|
16
|
+
"[&>svg]:size-4",
|
|
17
|
+
),
|
|
18
|
+
sm_: "h-8 px-2.5 rounded-lg text-sm font-medium [&>svg]:size-4",
|
|
19
|
+
md_: cn(
|
|
20
|
+
"h-9 px-3.5 gap-x-1 text-sm rounded-xl font-medium",
|
|
21
|
+
"[&>svg]:size-4",
|
|
22
|
+
),
|
|
23
|
+
lg_: cn(
|
|
24
|
+
"h-11 px-4 gap-x-2 text-base rounded-2xl font-medium",
|
|
25
|
+
"[&>svg]:size-5",
|
|
26
|
+
),
|
|
27
|
+
xl_: cn(
|
|
28
|
+
"h-12 px-4 gap-x-2 text-base rounded-2xl font-medium",
|
|
29
|
+
"[&>svg]:size-4 [&>svg]:scale-125",
|
|
30
|
+
),
|
|
31
|
+
icon: cn("p-2 rounded-full text-sm font-medium"),
|
|
32
|
+
iconr: cn("p-2 rounded-2xl text-sm font-medium"),
|
|
33
|
+
none: cn(),
|
|
34
|
+
}
|
|
35
|
+
const variant: Variants = {
|
|
36
|
+
primary:
|
|
37
|
+
"bg-(--primary) disabled:bg-(--priority) hover:bg-(--primary)/85 text-white dark:text-black",
|
|
38
|
+
secondary: "bg-(--secondary) hover:bg-(--secondary)/75",
|
|
39
|
+
destructive: "bg-(--red) hover:bg-(--red)/85 text-white",
|
|
40
|
+
outline: cn(
|
|
41
|
+
"bg-(--background) inset-ring inset-ring-(--outline)",
|
|
42
|
+
"hover:bg-(--secondary) active:bg-(--secondary) focus-visible:inset-ring-(--description) disabled:bg-(--background) disabled:text-(--description)",
|
|
43
|
+
),
|
|
44
|
+
ghost: "hover:bg-black/5 dark:hover:bg-white/10",
|
|
45
|
+
link: "text-(--primary) hover:underline",
|
|
46
|
+
none: cn(),
|
|
47
|
+
// jnsa: "bg-(--purple) hover:bg-(--purple)/90 text-white",
|
|
48
|
+
// outline_red: "inset-ring inset-ring-(--red)/50 hover:bg-(--red)/5 text-(--red)",
|
|
49
|
+
// shadcn: "rounded-xl bg-(--primary) hover:bg-(--primary)/85 text-white dark:text-black",
|
|
50
|
+
} as const
|
|
51
|
+
const shapes = {
|
|
52
|
+
square: "rounded-none",
|
|
53
|
+
rounded: "",
|
|
54
|
+
pill: "rounded-full",
|
|
55
|
+
}
|
|
56
|
+
const acyrlic = {
|
|
57
|
+
primary: "",
|
|
58
|
+
secondary: "",
|
|
59
|
+
destructive: "",
|
|
60
|
+
outline:
|
|
61
|
+
"inset-ring inset-ring-gray-300 dark:inset-ring-white/25 backdrop-blur-xl hover:bg-black/5 dark:hover:bg-white/10 dark:text-white",
|
|
62
|
+
ghost: "",
|
|
63
|
+
link: "",
|
|
64
|
+
shadcn: "",
|
|
65
|
+
}
|
|
66
|
+
export interface ButtonProps {
|
|
67
|
+
variant?: Variant
|
|
68
|
+
size?: keyof typeof sizeMap
|
|
69
|
+
shape?: "square" | "rounded" | "pill"
|
|
70
|
+
// tone?: "default" | "success" | "error" | "warning"
|
|
71
|
+
// elevation?: "none" | "xs" | "sm" | "md" | "lg" | "xl"
|
|
72
|
+
|
|
73
|
+
disabled?: boolean
|
|
74
|
+
loading?: boolean
|
|
75
|
+
|
|
76
|
+
label?: string
|
|
77
|
+
// haptics?: boolean
|
|
78
|
+
|
|
79
|
+
children: ReactNode
|
|
80
|
+
onPress?: (event: MouseEvent) => void | Promise<void>
|
|
81
|
+
onHover?: (event: MouseEvent) => void | Promise<void> // Web
|
|
82
|
+
onFocus?: (event: FocusEvent) => void | Promise<void> // web
|
|
83
|
+
onBlur?: (event: FocusEvent) => void | Promise<void> // web
|
|
84
|
+
|
|
85
|
+
type?: "button" | "reset" | "submit"
|
|
86
|
+
id?: string
|
|
87
|
+
className?: string
|
|
88
|
+
|
|
89
|
+
asChild?: boolean
|
|
90
|
+
}
|
|
91
|
+
export default function Button(props: Readonly<ButtonProps>) {
|
|
92
|
+
const className = cn(
|
|
93
|
+
"flex items-center justify-center leading-none truncate",
|
|
94
|
+
"select-none hover:cursor-pointer disabled:hover:cursor-not-allowed",
|
|
95
|
+
"transition transition-all transition-duration-250 ease",
|
|
96
|
+
variant[props.variant ?? "primary"],
|
|
97
|
+
sizeMap[props.size ?? "xl"],
|
|
98
|
+
props.className,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if (props.asChild) {
|
|
102
|
+
const children = props.children as ReactHTMLElement<HTMLElement>
|
|
103
|
+
return React.cloneElement(children, {
|
|
104
|
+
type: props.type,
|
|
105
|
+
role: "button",
|
|
106
|
+
disabled: props.disabled,
|
|
107
|
+
"aria-label": props.label,
|
|
108
|
+
onClick: props.onPress,
|
|
109
|
+
onMouseOver: props.onHover,
|
|
110
|
+
onFocus: props.onFocus,
|
|
111
|
+
id: props.id,
|
|
112
|
+
className: cn(className, children.props.className),
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<button
|
|
118
|
+
type={props.type ?? "button"}
|
|
119
|
+
disabled={props.loading ? true : props.disabled}
|
|
120
|
+
aria-label={props.label}
|
|
121
|
+
onClick={props.onPress}
|
|
122
|
+
onMouseOver={props.onHover}
|
|
123
|
+
onFocus={props.onFocus}
|
|
124
|
+
onBlur={props.onBlur}
|
|
125
|
+
id={props.id}
|
|
126
|
+
className={className}
|
|
127
|
+
>
|
|
128
|
+
{props.loading ? (
|
|
129
|
+
<IconLoader2 className="animate-spin" />
|
|
130
|
+
) : (
|
|
131
|
+
props.children
|
|
132
|
+
)}
|
|
133
|
+
</button>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils"
|
|
2
|
+
import type * as React from "react"
|
|
3
|
+
|
|
4
|
+
export interface CardHeaderProps {
|
|
5
|
+
className?: string
|
|
6
|
+
children: React.ReactNode
|
|
7
|
+
}
|
|
8
|
+
export const CardHeader: React.FC<CardHeaderProps> = props => {
|
|
9
|
+
return (
|
|
10
|
+
<header
|
|
11
|
+
data-slot="card-header"
|
|
12
|
+
className={cn("flex flex-col gap-4", props.className)}
|
|
13
|
+
>
|
|
14
|
+
{props.children}
|
|
15
|
+
</header>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CardContentProps {
|
|
20
|
+
className?: string
|
|
21
|
+
children: React.ReactNode
|
|
22
|
+
}
|
|
23
|
+
export const CardContent: React.FC<CardContentProps> = props => {
|
|
24
|
+
return (
|
|
25
|
+
<div data-slot="card-content" className={cn("flex", props.className)}>
|
|
26
|
+
{props.children}
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CardFooterProps {
|
|
32
|
+
className?: string
|
|
33
|
+
children: React.ReactNode
|
|
34
|
+
}
|
|
35
|
+
export const CardFooter: React.FC<CardFooterProps> = props => {
|
|
36
|
+
return (
|
|
37
|
+
<footer
|
|
38
|
+
data-slot="card-footer"
|
|
39
|
+
className={cn("flex [.border-t]:pt-4", props.className)}
|
|
40
|
+
>
|
|
41
|
+
{props.children}
|
|
42
|
+
</footer>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface CardAction {
|
|
47
|
+
children: React.ReactNode
|
|
48
|
+
className?: string
|
|
49
|
+
}
|
|
50
|
+
export function CardAction(props: Readonly<CardAction>) {
|
|
51
|
+
return (
|
|
52
|
+
<div data-slot="card-action" className={cn(props.className)}>
|
|
53
|
+
{props.children}
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const size = {
|
|
59
|
+
md: "p-4 gap-4 sm:rounded-2xl",
|
|
60
|
+
lg: "p-6 gap-6 sm:rounded-2xl",
|
|
61
|
+
xl: "p-8 gap-8 sm:rounded-4xl",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface CardProps {
|
|
65
|
+
className?: string
|
|
66
|
+
size?: "lg" | "xl"
|
|
67
|
+
children: React.ReactNode
|
|
68
|
+
}
|
|
69
|
+
export const Card: React.FC<CardProps> = props => {
|
|
70
|
+
return (
|
|
71
|
+
<div
|
|
72
|
+
className={cn(
|
|
73
|
+
"flex flex-col",
|
|
74
|
+
size[props.size ?? "md"],
|
|
75
|
+
"bg-(--background)",
|
|
76
|
+
"sm:inset-ring inset-ring-(--outline)",
|
|
77
|
+
props.className,
|
|
78
|
+
)}
|
|
79
|
+
>
|
|
80
|
+
{props.children}
|
|
81
|
+
</div>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default Card
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import { useEffect, useState } from "react"
|
|
3
|
+
import Image from "next/image"
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
|
|
6
|
+
export interface CheckboxProps {
|
|
7
|
+
checked?: boolean
|
|
8
|
+
onChange?(checked: boolean): void
|
|
9
|
+
disabled?: boolean
|
|
10
|
+
// loading?: boolean
|
|
11
|
+
label?: string
|
|
12
|
+
|
|
13
|
+
required?: boolean // web
|
|
14
|
+
name?: string // web
|
|
15
|
+
value?: string // web
|
|
16
|
+
id?: string // web
|
|
17
|
+
}
|
|
18
|
+
export default function Checkbox(props: Readonly<CheckboxProps>) {
|
|
19
|
+
const [checked, setChecked] = useState<boolean>(props.checked ?? false)
|
|
20
|
+
|
|
21
|
+
const onChange = props.onChange
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (onChange) onChange(checked)
|
|
24
|
+
}, [checked, onChange])
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
<input
|
|
29
|
+
type="checkbox"
|
|
30
|
+
checked={checked}
|
|
31
|
+
onChange={() => setChecked(v => !v)}
|
|
32
|
+
disabled={props.disabled}
|
|
33
|
+
aria-label={props.label}
|
|
34
|
+
required={props.required}
|
|
35
|
+
name={props.name}
|
|
36
|
+
value={props.value}
|
|
37
|
+
id={props.id}
|
|
38
|
+
className="peer hidden"
|
|
39
|
+
/>
|
|
40
|
+
<button
|
|
41
|
+
type="button"
|
|
42
|
+
role="checkbox"
|
|
43
|
+
data-state={checked ? "checked" : "unchecked"}
|
|
44
|
+
aria-checked={checked}
|
|
45
|
+
onClick={() => setChecked(v => !v)}
|
|
46
|
+
disabled={props.disabled}
|
|
47
|
+
className={cn(
|
|
48
|
+
"w-4 h-4 rounded-sm hover:cursor-pointer",
|
|
49
|
+
"transition transition-all transition-duration-150 ease",
|
|
50
|
+
"data-[state=unchecked]:bg-white data-[state=unchecked]:dark:bg-black",
|
|
51
|
+
"data-[state=checked]:bg-black data-[state=checked]:dark:bg-white",
|
|
52
|
+
"data-[state=checked]:disabled:bg-black/50 dark:data-[state=checked]:disabled:bg-white/50",
|
|
53
|
+
"data-[state=unchecked]:inset-ring",
|
|
54
|
+
"data-[state=unchecked]:inset-ring-black/50 data-[state=unchecked]:dark:inset-ring-white/50",
|
|
55
|
+
"data-[state=unchecked]:disabled:inset-ring-black/25 data-[state=unchecked]:dark:disabled:inset-ring-white/25",
|
|
56
|
+
)}
|
|
57
|
+
>
|
|
58
|
+
<Image
|
|
59
|
+
src={"/check.svg"}
|
|
60
|
+
alt="Check"
|
|
61
|
+
width={16}
|
|
62
|
+
height={16}
|
|
63
|
+
className="dark:invert select-none"
|
|
64
|
+
/>
|
|
65
|
+
</button>
|
|
66
|
+
</>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils"
|
|
2
|
+
|
|
3
|
+
export interface ContentProps {
|
|
4
|
+
children: React.ReactNode
|
|
5
|
+
}
|
|
6
|
+
export default function Content(
|
|
7
|
+
props: Readonly<ContentProps> & React.HTMLAttributes<HTMLElement>,
|
|
8
|
+
) {
|
|
9
|
+
return <div className={cn("flex", props.className)}>{props.children}</div>
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils"
|
|
2
|
+
|
|
3
|
+
export interface DividerProps {
|
|
4
|
+
className?: string
|
|
5
|
+
orientation?: "vertical" | "horizontal"
|
|
6
|
+
}
|
|
7
|
+
export default function Divider(props: DividerProps) {
|
|
8
|
+
return (
|
|
9
|
+
<hr
|
|
10
|
+
className={cn(
|
|
11
|
+
"border-(--outline)",
|
|
12
|
+
props.orientation === "vertical" ? "h-full border-r" : undefined,
|
|
13
|
+
props.className,
|
|
14
|
+
)}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils"
|
|
2
|
+
|
|
3
|
+
export interface FooterProps {
|
|
4
|
+
children: React.ReactNode
|
|
5
|
+
}
|
|
6
|
+
export default function Footer(
|
|
7
|
+
props: Readonly<FooterProps> & React.HTMLAttributes<HTMLElement>,
|
|
8
|
+
) {
|
|
9
|
+
return <div className={cn("flex", props.className)}>{props.children}</div>
|
|
10
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils"
|
|
2
|
+
|
|
3
|
+
export type Variant =
|
|
4
|
+
| "primary"
|
|
5
|
+
| "secondary"
|
|
6
|
+
| "destructive"
|
|
7
|
+
| "outline"
|
|
8
|
+
| "ghost"
|
|
9
|
+
| "link"
|
|
10
|
+
| "none"
|
|
11
|
+
export type Variants = Record<Variant, string>
|
|
12
|
+
|
|
13
|
+
export const input_variant: Variants = {
|
|
14
|
+
primary: cn(
|
|
15
|
+
"bg-(--background)",
|
|
16
|
+
"placeholder:text-(--description) disabled:text-(--disabled)",
|
|
17
|
+
// "inset-ring inset-ring-(--disabled) disabled:inset-ring-(--outline) focus:inset-ring-(--description)",
|
|
18
|
+
"border border-(--disabled) disabled:border-(--outline) focus:border-(--description)",
|
|
19
|
+
"focus:ring-4 focus:ring-(--foreground)/5",
|
|
20
|
+
"invalid:inset-ring-(--red)",
|
|
21
|
+
),
|
|
22
|
+
secondary: cn(),
|
|
23
|
+
destructive: cn(
|
|
24
|
+
"bg-(--background)",
|
|
25
|
+
"placeholder:text-(--description) disabled:text-(--disabled)",
|
|
26
|
+
// "inset-ring inset-ring-(--red)/50 disabled:inset-ring-(--red)/25 focus:inset-ring-(--red)",
|
|
27
|
+
"border border-(--red)/50 disabled:border-(--red)/25 focus:border-(--red)",
|
|
28
|
+
"focus:ring-4 focus:ring-(--red)/5",
|
|
29
|
+
),
|
|
30
|
+
outline: cn(),
|
|
31
|
+
ghost: cn(),
|
|
32
|
+
link: cn(),
|
|
33
|
+
none: cn("text-()"),
|
|
34
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReactElement } from "react"
|
|
2
|
+
import type { HeadingProps } from "./heading"
|
|
3
|
+
import type { TextProps } from "./text"
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
|
|
6
|
+
export interface HeaderProps {
|
|
7
|
+
gap?: number
|
|
8
|
+
children: [ReactElement<HeadingProps>, ReactElement<TextProps>]
|
|
9
|
+
className?: string
|
|
10
|
+
}
|
|
11
|
+
export default function Header(props: Readonly<HeaderProps>) {
|
|
12
|
+
return (
|
|
13
|
+
<header
|
|
14
|
+
style={{
|
|
15
|
+
gap: props.gap ?? 8,
|
|
16
|
+
}}
|
|
17
|
+
className={cn("flex flex-col", props.className)}
|
|
18
|
+
>
|
|
19
|
+
{props.children}
|
|
20
|
+
</header>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils"
|
|
2
|
+
|
|
3
|
+
export interface HeadingProps {
|
|
4
|
+
children: React.ReactNode
|
|
5
|
+
className?: string
|
|
6
|
+
}
|
|
7
|
+
export default function Heading(props: Readonly<HeadingProps>) {
|
|
8
|
+
return (
|
|
9
|
+
<h1
|
|
10
|
+
className={cn(
|
|
11
|
+
"scroll-m-20 text-center text-4xl font-extrabold tracking-tight text-balance",
|
|
12
|
+
props.className,
|
|
13
|
+
)}
|
|
14
|
+
>
|
|
15
|
+
{props.children}
|
|
16
|
+
</h1>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
Heading.H1 = (props: Readonly<HeadingProps>) => {
|
|
21
|
+
return (
|
|
22
|
+
<h1
|
|
23
|
+
className={cn(
|
|
24
|
+
"scroll-m-20 text-center text-4xl font-extrabold tracking-tight text-balance",
|
|
25
|
+
props.className,
|
|
26
|
+
)}
|
|
27
|
+
>
|
|
28
|
+
{props.children}
|
|
29
|
+
</h1>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
Heading.H2 = (props: Readonly<HeadingProps>) => {
|
|
33
|
+
return (
|
|
34
|
+
<h1
|
|
35
|
+
className={cn(
|
|
36
|
+
"scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0",
|
|
37
|
+
props.className,
|
|
38
|
+
)}
|
|
39
|
+
>
|
|
40
|
+
{props.children}
|
|
41
|
+
</h1>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
Heading.H3 = (props: Readonly<HeadingProps>) => {
|
|
45
|
+
return (
|
|
46
|
+
<h1
|
|
47
|
+
className={cn(
|
|
48
|
+
"scroll-m-20 text-2xl font-semibold tracking-tight",
|
|
49
|
+
props.className,
|
|
50
|
+
)}
|
|
51
|
+
>
|
|
52
|
+
{props.children}
|
|
53
|
+
</h1>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
Heading.H4 = (props: Readonly<HeadingProps>) => {
|
|
57
|
+
return (
|
|
58
|
+
<h4
|
|
59
|
+
className={cn(
|
|
60
|
+
"scroll-m-20 text-xl font-semibold tracking-tight",
|
|
61
|
+
props.className,
|
|
62
|
+
)}
|
|
63
|
+
>
|
|
64
|
+
{props.children}
|
|
65
|
+
</h4>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
Heading.H5 = (props: Readonly<HeadingProps>) => {
|
|
69
|
+
return (
|
|
70
|
+
<h4
|
|
71
|
+
className={cn(
|
|
72
|
+
"scroll-m-20 text-lg font-medium",
|
|
73
|
+
props.className,
|
|
74
|
+
)}
|
|
75
|
+
>
|
|
76
|
+
{props.children}
|
|
77
|
+
</h4>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
Heading.H6 = (props: Readonly<HeadingProps>) => {
|
|
81
|
+
return (
|
|
82
|
+
<h6
|
|
83
|
+
className={cn(
|
|
84
|
+
"scroll-m-20 text-base font-medium",
|
|
85
|
+
props.className,
|
|
86
|
+
)}
|
|
87
|
+
>
|
|
88
|
+
{props.children}
|
|
89
|
+
</h6>
|
|
90
|
+
)
|
|
91
|
+
}
|