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