@modlin/ui 0.0.241 → 0.0.243
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/package.json +1 -1
- package/src/avatar.tsx +1 -1
- package/src/button.tsx +82 -102
- package/src/card.tsx +1 -1
- package/src/checkbox.tsx +39 -22
- package/src/divider.tsx +3 -3
- package/src/global.ts +7 -5
- package/src/heading.tsx +6 -6
- package/src/input.tsx +42 -58
- package/src/label.tsx +1 -6
- package/src/select.tsx +140 -141
- package/src/toast.tsx +1 -1
- package/tsconfig.json +12 -9
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@modlin/ui",
|
|
4
4
|
"description": "A lightweight UI library built on the Suffix design system for consistent, fast, and elegant interfaces.",
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.243",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"exports": {
|
package/src/avatar.tsx
CHANGED
package/src/button.tsx
CHANGED
|
@@ -5,121 +5,101 @@ import React from "react"
|
|
|
5
5
|
import type { Variant, Variants } from "./global"
|
|
6
6
|
|
|
7
7
|
const sizeMap = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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("overflow-visible"),
|
|
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("h-11 px-5 gap-x-1 text-base rounded-full font-medium", "[&>svg]:size-4"),
|
|
11
|
+
xl: cn("h-12 px-6 gap-x-1 text-base rounded-full font-semibold", "[&>svg]:size-4"),
|
|
12
|
+
sm_: "h-8 px-2.5 rounded-lg text-sm font-medium [&>svg]:size-4",
|
|
13
|
+
md_: cn("h-9 px-3.5 gap-x-1 text-sm rounded-xl font-medium", "[&>svg]:size-4"),
|
|
14
|
+
lg_: cn("h-11 px-4 gap-x-2 text-base rounded-2xl font-medium", "[&>svg]:size-5"),
|
|
15
|
+
xl_: cn("h-12 px-4 gap-x-2 text-base rounded-2xl font-medium", "[&>svg]:size-4 [&>svg]:scale-125"),
|
|
16
|
+
icon: cn("p-2 rounded-full text-sm font-medium"),
|
|
17
|
+
iconr: cn("p-2 rounded-2xl text-sm font-medium"),
|
|
18
|
+
none: cn("overflow-visible"),
|
|
34
19
|
}
|
|
35
20
|
const variant: Variants = {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// shadcn: "rounded-xl bg-(--primary) hover:bg-(--primary)/85 text-white dark:text-black",
|
|
21
|
+
primary: "bg-primary disabled:bg-primary/60 hover:bg-primary/85 active:bg-primary/85 text-background",
|
|
22
|
+
secondary: "bg-secondary hover:bg-secondary/75",
|
|
23
|
+
destructive: "bg-red hover:bg-red/85 text-white",
|
|
24
|
+
outline: cn(
|
|
25
|
+
"bg-background inset-ring inset-ring-border",
|
|
26
|
+
"hover:bg-secondary active:bg-secondary focus-visible:inset-ring-muted-foreground disabled:bg-background disabled:text-muted-foreground",
|
|
27
|
+
),
|
|
28
|
+
ghost: "hover:bg-secondary",
|
|
29
|
+
link: "text-primary hover:underline",
|
|
30
|
+
none: cn(),
|
|
31
|
+
// jnsa: "bg-(--purple) hover:bg-(--purple)/90 text-white",
|
|
32
|
+
// outline_red: "inset-ring inset-ring-(--red)/50 hover:bg-(--red)/5 text-(--red)",
|
|
33
|
+
// shadcn: "rounded-xl bg-(--primary) hover:bg-(--primary)/85 text-white dark:text-black",
|
|
50
34
|
} as const
|
|
51
35
|
const shapes = {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
36
|
+
square: "rounded-none",
|
|
37
|
+
rounded: "",
|
|
38
|
+
pill: "rounded-full",
|
|
55
39
|
}
|
|
56
40
|
export interface ButtonProps {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
41
|
+
variant?: Variant
|
|
42
|
+
size?: keyof typeof sizeMap
|
|
43
|
+
shape?: "square" | "rounded" | "pill"
|
|
44
|
+
// tone?: "default" | "success" | "error" | "warning"
|
|
45
|
+
// elevation?: "none" | "xs" | "sm" | "md" | "lg" | "xl"
|
|
62
46
|
|
|
63
|
-
|
|
64
|
-
|
|
47
|
+
disabled?: boolean
|
|
48
|
+
loading?: boolean
|
|
65
49
|
|
|
66
|
-
|
|
67
|
-
|
|
50
|
+
label?: string
|
|
51
|
+
// haptics?: boolean
|
|
68
52
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
53
|
+
children?: ReactNode
|
|
54
|
+
onPress?: (event: MouseEvent) => void | Promise<void>
|
|
55
|
+
onHover?: (event: MouseEvent) => void | Promise<void> // Web
|
|
56
|
+
onFocus?: (event: FocusEvent) => void | Promise<void> // web
|
|
57
|
+
onBlur?: (event: FocusEvent) => void | Promise<void> // web
|
|
74
58
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
59
|
+
type?: "button" | "reset" | "submit"
|
|
60
|
+
id?: string
|
|
61
|
+
className?: string
|
|
78
62
|
|
|
79
|
-
|
|
63
|
+
asChild?: boolean
|
|
80
64
|
}
|
|
81
65
|
export default function Button(props: Readonly<ButtonProps>) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
66
|
+
const className = cn(
|
|
67
|
+
"line-clamp-1 flex items-center justify-center leading-none text-center",
|
|
68
|
+
"select-none hover:cursor-pointer disabled:hover:cursor-not-allowed",
|
|
69
|
+
"transition-[background-color] transition-duration-250 ease",
|
|
70
|
+
variant[props.variant ?? "primary"],
|
|
71
|
+
sizeMap[props.size ?? "xl"],
|
|
72
|
+
props.className,
|
|
73
|
+
)
|
|
90
74
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
75
|
+
if (props.asChild) {
|
|
76
|
+
const children = props.children as ReactHTMLElement<HTMLElement>
|
|
77
|
+
return React.cloneElement(children, {
|
|
78
|
+
type: props.type,
|
|
79
|
+
role: "button",
|
|
80
|
+
disabled: props.disabled,
|
|
81
|
+
"aria-label": props.label,
|
|
82
|
+
onClick: props.onPress,
|
|
83
|
+
onMouseOver: props.onHover,
|
|
84
|
+
onFocus: props.onFocus,
|
|
85
|
+
id: props.id,
|
|
86
|
+
className: cn(className, children.props.className),
|
|
87
|
+
})
|
|
88
|
+
}
|
|
105
89
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
props.children
|
|
122
|
-
)}
|
|
123
|
-
</button>
|
|
124
|
-
)
|
|
90
|
+
return (
|
|
91
|
+
<button
|
|
92
|
+
type={props.type ?? "button"}
|
|
93
|
+
disabled={props.loading ? true : props.disabled}
|
|
94
|
+
aria-label={props.label}
|
|
95
|
+
onClick={props.onPress}
|
|
96
|
+
onMouseOver={props.onHover}
|
|
97
|
+
onFocus={props.onFocus}
|
|
98
|
+
onBlur={props.onBlur}
|
|
99
|
+
id={props.id}
|
|
100
|
+
className={className}
|
|
101
|
+
>
|
|
102
|
+
{props.loading ? <IconLoader2 className="animate-spin" /> : props.children}
|
|
103
|
+
</button>
|
|
104
|
+
)
|
|
125
105
|
}
|
package/src/card.tsx
CHANGED
package/src/checkbox.tsx
CHANGED
|
@@ -1,58 +1,75 @@
|
|
|
1
1
|
"use client"
|
|
2
|
-
import { useEffect, useState } from "react"
|
|
3
|
-
import Image from "next/image"
|
|
2
|
+
import { useEffect, useRef, useState, type ChangeEventHandler, type ReactElement, type ReactNode } from "react"
|
|
4
3
|
import { cn } from "./utils"
|
|
4
|
+
import { setConfig } from "next/config"
|
|
5
|
+
|
|
6
|
+
export interface ViewProps {
|
|
7
|
+
children?: ReactNode
|
|
8
|
+
className?: string
|
|
9
|
+
}
|
|
5
10
|
|
|
6
11
|
export interface CheckboxProps {
|
|
7
|
-
checked?: boolean
|
|
8
|
-
onChange?(checked: boolean): void
|
|
9
12
|
disabled?: boolean
|
|
13
|
+
onChange?: ChangeEventHandler<HTMLInputElement> // web
|
|
14
|
+
onValueChange?(checked: boolean): void | Promise<void> // web
|
|
15
|
+
checked?: boolean
|
|
16
|
+
defaultChecked?: boolean
|
|
10
17
|
// loading?: boolean
|
|
11
18
|
label?: string
|
|
12
19
|
|
|
13
20
|
required?: boolean // web
|
|
14
21
|
name?: string // web
|
|
15
|
-
value?: string // web
|
|
16
22
|
id?: string // web
|
|
23
|
+
value?: string
|
|
17
24
|
}
|
|
18
|
-
export default function Checkbox(props: Readonly<CheckboxProps>) {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
if (onChange) onChange(checked)
|
|
24
|
-
}, [checked, onChange])
|
|
25
|
+
export default function Checkbox(props: Readonly<CheckboxProps>): ReactElement<CheckboxProps> {
|
|
26
|
+
const input = useRef<HTMLInputElement>(null)
|
|
27
|
+
const button = useRef<HTMLInputElement>(null)
|
|
28
|
+
let checked = props.checked || props.defaultChecked || false
|
|
25
29
|
|
|
26
30
|
return (
|
|
27
31
|
<>
|
|
28
32
|
<input
|
|
33
|
+
ref={input}
|
|
29
34
|
type="checkbox"
|
|
30
|
-
checked={checked}
|
|
31
|
-
onChange={() => setChecked(v => !v)}
|
|
32
35
|
disabled={props.disabled}
|
|
33
|
-
|
|
36
|
+
onChange={e => {
|
|
37
|
+
checked = !checked
|
|
38
|
+
e.target.checked = checked
|
|
39
|
+
if (button.current) {
|
|
40
|
+
button.current.ariaChecked = checked ? "true" : "false"
|
|
41
|
+
button.current.setAttribute("data-state", checked ? "checked" : "unchecked")
|
|
42
|
+
}
|
|
43
|
+
}}
|
|
44
|
+
checked={checked}
|
|
45
|
+
defaultChecked={props.defaultChecked}
|
|
34
46
|
required={props.required}
|
|
47
|
+
aria-label={props.label}
|
|
35
48
|
name={props.name}
|
|
36
49
|
value={props.value}
|
|
37
50
|
id={props.id}
|
|
38
51
|
className="peer hidden"
|
|
39
52
|
/>
|
|
40
53
|
<button
|
|
54
|
+
ref={button}
|
|
41
55
|
type="button"
|
|
42
56
|
role="checkbox"
|
|
43
57
|
data-state={checked ? "checked" : "unchecked"}
|
|
44
58
|
aria-checked={checked}
|
|
45
|
-
onClick={
|
|
59
|
+
onClick={async e => {
|
|
60
|
+
checked = !checked
|
|
61
|
+
if (input.current) input.current.checked = checked
|
|
62
|
+
e.currentTarget.ariaChecked = checked ? "true" : "false"
|
|
63
|
+
e.currentTarget.setAttribute("data-state", checked ? "checked" : "unchecked")
|
|
64
|
+
}}
|
|
46
65
|
disabled={props.disabled}
|
|
47
66
|
className={cn(
|
|
48
67
|
"w-4 h-4 rounded-sm hover:cursor-pointer text-background",
|
|
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
68
|
"data-[state=unchecked]:inset-ring",
|
|
54
|
-
"data-[state=unchecked]:
|
|
55
|
-
"data-[state=unchecked]:disabled:inset-ring-
|
|
69
|
+
"data-[state=unchecked]:bg-background data-[state=unchecked]:inset-ring-muted-foreground",
|
|
70
|
+
"data-[state=unchecked]:disabled:inset-ring-border",
|
|
71
|
+
"data-[state=checked]:bg-primary data-[state=checked]:disabled:bg-primary/50",
|
|
72
|
+
"transition transition-all transition-duration-150 ease",
|
|
56
73
|
)}
|
|
57
74
|
>
|
|
58
75
|
<svg width={16} height={16} viewBox="0 0 16 16" fill="none">
|
package/src/divider.tsx
CHANGED
|
@@ -6,10 +6,10 @@ export interface DividerProps {
|
|
|
6
6
|
}
|
|
7
7
|
export default function Divider(props: DividerProps) {
|
|
8
8
|
return (
|
|
9
|
-
<
|
|
9
|
+
<div
|
|
10
|
+
data-orientation={props.orientation ?? "horizontal"}
|
|
10
11
|
className={cn(
|
|
11
|
-
"border-
|
|
12
|
-
props.orientation === "vertical" ? "h-full border-r" : undefined,
|
|
12
|
+
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
|
13
13
|
props.className,
|
|
14
14
|
)}
|
|
15
15
|
/>
|
package/src/global.ts
CHANGED
|
@@ -14,19 +14,21 @@ export const input_variant: Variants = {
|
|
|
14
14
|
primary: cn(
|
|
15
15
|
"bg-background",
|
|
16
16
|
"placeholder:text-description disabled:text-disabled",
|
|
17
|
-
|
|
18
|
-
"border border-
|
|
17
|
+
"inset-ring inset-ring-border disabled:inset-ring-disabled focus:inset-ring-primary/75",
|
|
18
|
+
// "border border-border disabled:border-disabled focus:border-primary/75",
|
|
19
19
|
"focus:ring-4 focus:ring-primary/10",
|
|
20
20
|
"invalid:inset-ring-red",
|
|
21
|
-
"data-[invalid=true]:border data-[invalid=true]:border-red/50 data-[invalid=true]:focus:border-red",
|
|
21
|
+
// "data-[invalid=true]:border data-[invalid=true]:border-red/50 data-[invalid=true]:focus:border-red",
|
|
22
|
+
"data-[invalid=true]:inset-ring-red/50 data-[invalid=true]:focus:inset-ring-red/75",
|
|
22
23
|
"data-[invalid=true]:focus:ring-4 data-[invalid=true]:focus:ring-red/10",
|
|
23
24
|
),
|
|
24
25
|
secondary: cn(),
|
|
25
26
|
destructive: cn(
|
|
26
27
|
"bg-background",
|
|
27
28
|
"placeholder:text-description disabled:text-disabled",
|
|
28
|
-
|
|
29
|
-
"
|
|
29
|
+
"inset-ring inset-ring-red/50 disabled:inset-ring-red/25 focus:inset-ring-red/75",
|
|
30
|
+
"focus:ring-4 focus:ring-red/10",
|
|
31
|
+
// "border border-red/50 disabled:border-red/25 focus:border-red",
|
|
30
32
|
"focus:ring-4 focus:ring-red/5",
|
|
31
33
|
),
|
|
32
34
|
outline: cn(),
|
package/src/heading.tsx
CHANGED
|
@@ -31,26 +31,26 @@ Heading.H1 = (props: Readonly<HeadingProps>) => {
|
|
|
31
31
|
}
|
|
32
32
|
Heading.H2 = (props: Readonly<HeadingProps>) => {
|
|
33
33
|
return (
|
|
34
|
-
<
|
|
34
|
+
<h2
|
|
35
35
|
className={cn(
|
|
36
36
|
"scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0",
|
|
37
37
|
props.className,
|
|
38
38
|
)}
|
|
39
39
|
>
|
|
40
40
|
{props.children}
|
|
41
|
-
</
|
|
41
|
+
</h2>
|
|
42
42
|
)
|
|
43
43
|
}
|
|
44
44
|
Heading.H3 = (props: Readonly<HeadingProps>) => {
|
|
45
45
|
return (
|
|
46
|
-
<
|
|
46
|
+
<h3
|
|
47
47
|
className={cn(
|
|
48
48
|
"scroll-m-20 text-2xl font-semibold tracking-tight",
|
|
49
49
|
props.className,
|
|
50
50
|
)}
|
|
51
51
|
>
|
|
52
52
|
{props.children}
|
|
53
|
-
</
|
|
53
|
+
</h3>
|
|
54
54
|
)
|
|
55
55
|
}
|
|
56
56
|
Heading.H4 = (props: Readonly<HeadingProps>) => {
|
|
@@ -67,14 +67,14 @@ Heading.H4 = (props: Readonly<HeadingProps>) => {
|
|
|
67
67
|
}
|
|
68
68
|
Heading.H5 = (props: Readonly<HeadingProps>) => {
|
|
69
69
|
return (
|
|
70
|
-
<
|
|
70
|
+
<h5
|
|
71
71
|
className={cn(
|
|
72
72
|
"scroll-m-20 text-lg font-medium",
|
|
73
73
|
props.className,
|
|
74
74
|
)}
|
|
75
75
|
>
|
|
76
76
|
{props.children}
|
|
77
|
-
</
|
|
77
|
+
</h5>
|
|
78
78
|
)
|
|
79
79
|
}
|
|
80
80
|
Heading.H6 = (props: Readonly<HeadingProps>) => {
|
package/src/input.tsx
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type ChangeEventHandler,
|
|
3
|
-
type FocusEventHandler,
|
|
4
|
-
type HTMLInputAutoCompleteAttribute,
|
|
5
|
-
type InputEventHandler,
|
|
6
|
-
forwardRef,
|
|
7
|
-
} from "react"
|
|
1
|
+
import { type ChangeEvent, type FocusEvent, type InputEvent, type HTMLInputAutoCompleteAttribute, forwardRef } from "react"
|
|
8
2
|
import { input_variant, type Variant } from "./global"
|
|
9
3
|
import { cn } from "./utils"
|
|
10
4
|
|
|
@@ -12,15 +6,7 @@ export interface InputProps {
|
|
|
12
6
|
variant?: Variant
|
|
13
7
|
|
|
14
8
|
type?: "text" | "password" | "email" | "number" | "tel" | "url" | "file"
|
|
15
|
-
inputMode?:
|
|
16
|
-
| "search"
|
|
17
|
-
| "text"
|
|
18
|
-
| "email"
|
|
19
|
-
| "tel"
|
|
20
|
-
| "url"
|
|
21
|
-
| "none"
|
|
22
|
-
| "numeric"
|
|
23
|
-
| "decimal"
|
|
9
|
+
inputMode?: "search" | "text" | "email" | "tel" | "url" | "none" | "numeric" | "decimal"
|
|
24
10
|
placeholder?: string
|
|
25
11
|
defaultValue?: string
|
|
26
12
|
|
|
@@ -36,54 +22,52 @@ export interface InputProps {
|
|
|
36
22
|
disabled?: boolean
|
|
37
23
|
|
|
38
24
|
value?: string
|
|
39
|
-
onChange?:
|
|
40
|
-
|
|
41
|
-
|
|
25
|
+
onChange?: (event: ChangeEvent<HTMLInputElement>) => void
|
|
26
|
+
onBlur?: (event: FocusEvent<HTMLInputElement>) => void
|
|
27
|
+
onInput?: (event: InputEvent<HTMLInputElement>) => void
|
|
42
28
|
|
|
43
29
|
id?: string
|
|
44
30
|
autoComplete?: HTMLInputAutoCompleteAttribute
|
|
45
31
|
// onChange?: (value: string) => void
|
|
46
32
|
className?: string
|
|
47
33
|
invalid?: boolean
|
|
48
|
-
|
|
34
|
+
describedby?: string
|
|
49
35
|
}
|
|
50
|
-
const Input = forwardRef<HTMLInputElement, Readonly<InputProps>>(
|
|
51
|
-
|
|
52
|
-
const { onChange } = props
|
|
36
|
+
const Input = forwardRef<HTMLInputElement, Readonly<InputProps>>((props, ref) => {
|
|
37
|
+
const { onChange } = props
|
|
53
38
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
)
|
|
39
|
+
return (
|
|
40
|
+
<input
|
|
41
|
+
ref={ref}
|
|
42
|
+
type={props.type ?? "text"}
|
|
43
|
+
inputMode={props.inputMode}
|
|
44
|
+
placeholder={props.placeholder}
|
|
45
|
+
defaultValue={props.defaultValue}
|
|
46
|
+
value={props.value}
|
|
47
|
+
name={props.name}
|
|
48
|
+
min={props.min}
|
|
49
|
+
max={props.max}
|
|
50
|
+
minLength={props.minLength}
|
|
51
|
+
maxLength={props.maxLength}
|
|
52
|
+
pattern={props.pattern}
|
|
53
|
+
required={props.required}
|
|
54
|
+
readOnly={props.readOnly}
|
|
55
|
+
disabled={props.disabled}
|
|
56
|
+
onChange={onChange}
|
|
57
|
+
onBlur={props.onBlur}
|
|
58
|
+
onInvalid={e => e.preventDefault()}
|
|
59
|
+
id={props.id}
|
|
60
|
+
autoComplete={props.autoComplete}
|
|
61
|
+
data-invalid={props.invalid}
|
|
62
|
+
aria-describedby={props.describedby}
|
|
63
|
+
className={cn(
|
|
64
|
+
"flex items-center w-full h-12 px-4",
|
|
65
|
+
"transition transition-duration-150 transition-[box-shadow] ease-in",
|
|
66
|
+
"peer rounded-2xl",
|
|
67
|
+
input_variant[props.variant ?? "primary"],
|
|
68
|
+
props.className,
|
|
69
|
+
)}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
})
|
|
89
73
|
export default Input
|
package/src/label.tsx
CHANGED
|
@@ -23,12 +23,7 @@ export default function Label(props: LabelProps) {
|
|
|
23
23
|
<label
|
|
24
24
|
htmlFor={props.htmlFor}
|
|
25
25
|
aria-label={props.label}
|
|
26
|
-
className={cn(
|
|
27
|
-
"flex items-center gap-2",
|
|
28
|
-
"peer-disabled:text-(--disabled) invalid:text-(--red) text-sm font-medium leading-4",
|
|
29
|
-
"select-none",
|
|
30
|
-
props.className,
|
|
31
|
-
)}
|
|
26
|
+
className={cn("flex items-center gap-2", "peer-disabled:text-disabled invalid:text-red text-sm/4 font-medium", "select-none", props.className)}
|
|
32
27
|
>
|
|
33
28
|
{props.children}
|
|
34
29
|
</label>
|
package/src/select.tsx
CHANGED
|
@@ -1,172 +1,171 @@
|
|
|
1
1
|
"use client"
|
|
2
|
-
import { IconChevronDown } from "@tabler/icons-react"
|
|
2
|
+
import { IconChevronDown, IconSelector } from "@tabler/icons-react"
|
|
3
3
|
import Button from "./button"
|
|
4
4
|
import Text from "./text"
|
|
5
5
|
import { cn } from "./utils"
|
|
6
6
|
import React, { type ReactElement, type ReactNode, useState } from "react"
|
|
7
7
|
|
|
8
8
|
export interface SelectTriggerProps {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
placeholder?: string
|
|
10
|
+
id?: string
|
|
11
|
+
children?: string
|
|
12
12
|
}
|
|
13
13
|
export function SelectTrigger(props: SelectTriggerProps) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
return (
|
|
15
|
+
<button type="button" className={cn("flex items-center", "bg-(--background)")}>
|
|
16
|
+
{props.placeholder}
|
|
17
|
+
</button>
|
|
18
|
+
)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function Dropdown() {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
return (
|
|
23
|
+
<div className="grid">
|
|
24
|
+
<SelectTrigger placeholder="Select a gender" />
|
|
25
|
+
<Button variant="outline" size="xl" className="text-(--description)">
|
|
26
|
+
<Text className="flex grow-1">Country</Text>
|
|
27
|
+
<IconChevronDown />
|
|
28
|
+
</Button>
|
|
29
|
+
</div>
|
|
30
|
+
)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export interface SelectItemProps {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
value: string
|
|
35
|
+
children: ReactNode
|
|
36
|
+
className?: string
|
|
37
|
+
onPress?: () => void
|
|
38
38
|
}
|
|
39
39
|
export function SelectItem(props: Readonly<SelectItemProps>): ReactElement<SelectItemProps> {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
40
|
+
return (
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
role="option"
|
|
44
|
+
value={props.value}
|
|
45
|
+
onClick={props.onPress}
|
|
46
|
+
className={cn(
|
|
47
|
+
"flex items-center min-h-12 px-4 gap-4",
|
|
48
|
+
"select-none hover:bg-secondary",
|
|
49
|
+
// "[&>svg]:size-4 [&>svg]:scale-125",
|
|
50
|
+
props.className,
|
|
51
|
+
)}
|
|
52
|
+
>
|
|
53
|
+
{props.children}
|
|
54
|
+
</button>
|
|
55
|
+
)
|
|
56
56
|
}
|
|
57
57
|
export interface SelectContentProps {
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
defaultValue: string
|
|
59
|
+
children?: ReactNode
|
|
60
60
|
}
|
|
61
61
|
export function SelectContent(props: SelectContentProps) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
const [value, setValue] = useState<string>(props.defaultValue)
|
|
63
|
+
const [expanded, setExpanded] = useState(false)
|
|
64
|
+
const enhanced = React.Children.map(props.children, child => {
|
|
65
|
+
// We don't use Radix
|
|
66
|
+
const children = child as ReactElement<SelectItemProps>
|
|
67
|
+
if (!React.isValidElement(children)) return children
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
// Add/override props here
|
|
70
|
+
return React.cloneElement(children, {
|
|
71
|
+
onPress() {
|
|
72
|
+
setValue(children.props.value)
|
|
73
|
+
setExpanded(false)
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
)
|
|
78
|
+
return (
|
|
79
|
+
<div className="relative flex">
|
|
80
|
+
<button
|
|
81
|
+
type="button"
|
|
82
|
+
role="combobox"
|
|
83
|
+
aria-expanded={expanded}
|
|
84
|
+
aria-autocomplete="none"
|
|
85
|
+
aria-haspopup="listbox"
|
|
86
|
+
value={value}
|
|
87
|
+
id="country"
|
|
88
|
+
onClick={() => setExpanded(!expanded)}
|
|
89
|
+
// onBlur={() => setExpanded(false)}
|
|
90
|
+
className={cn("absolute flex items-center justify-center gap-1 px-4 h-12")}
|
|
91
|
+
>
|
|
92
|
+
<p className="font-medium w-6 h-6">{value}</p>
|
|
93
|
+
<IconSelector size={16} className="text-muted-foreground" />
|
|
94
|
+
</button>
|
|
95
|
+
<ul
|
|
96
|
+
className={cn(
|
|
97
|
+
"absolute top-[100%] flex flex-col mt-2 w-full max-h-72 overflow-auto",
|
|
98
|
+
"rounded-2xl",
|
|
99
|
+
"bg-background shadow-[0_0_16px_0_var(--shadow)]",
|
|
100
|
+
"animate-popup origin-top-left",
|
|
101
|
+
)}
|
|
102
|
+
>
|
|
103
|
+
{enhanced}
|
|
104
|
+
</ul>
|
|
105
|
+
</div>
|
|
106
|
+
)
|
|
108
107
|
}
|
|
109
108
|
|
|
110
109
|
export interface SelectProps {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
110
|
+
defaultValue?: string
|
|
111
|
+
placeholder: string
|
|
112
|
+
value?: string
|
|
113
|
+
name?: string
|
|
114
|
+
id?: string
|
|
115
|
+
children: ReactElement<SelectItemProps>[]
|
|
117
116
|
}
|
|
118
117
|
export function Select(props: SelectProps) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
const [expanded, setExpanded] = useState(false)
|
|
119
|
+
const [value, setValue] = useState(props.defaultValue)
|
|
120
|
+
const [selected, setSelected] = useState<ReactNode>(props.placeholder)
|
|
122
121
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
122
|
+
const children: ReactElement[] = []
|
|
123
|
+
for (let i = 0; i < props.children.length; i++) {
|
|
124
|
+
const child = props.children[i]
|
|
125
|
+
if (React.isValidElement<SelectItemProps>(child)) {
|
|
126
|
+
children.push(
|
|
127
|
+
React.cloneElement(child as ReactElement<SelectItemProps>, {
|
|
128
|
+
onPress() {
|
|
129
|
+
setValue(child.props.value)
|
|
130
|
+
setSelected(child.props.children)
|
|
131
|
+
setExpanded(false)
|
|
132
|
+
},
|
|
133
|
+
}),
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
138
137
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
138
|
+
return (
|
|
139
|
+
<div className="relative flex flex-col w-full max-w-56">
|
|
140
|
+
<button
|
|
141
|
+
type="button"
|
|
142
|
+
role="combobox"
|
|
143
|
+
aria-expanded={expanded}
|
|
144
|
+
aria-autocomplete="none"
|
|
145
|
+
aria-haspopup="listbox"
|
|
146
|
+
value={value}
|
|
147
|
+
name={props.name}
|
|
148
|
+
id={props.id}
|
|
149
|
+
onClick={() => setExpanded(!expanded)}
|
|
150
|
+
// onBlur={() => setExpanded(false)}
|
|
151
|
+
className={cn("flex items-center gap-x-4", "[&>svg]:size-4 [&>svg]:scale-125", "focus:underline")}
|
|
152
|
+
>
|
|
153
|
+
{selected}
|
|
154
|
+
<IconChevronDown size={16} />
|
|
155
|
+
</button>
|
|
156
|
+
{expanded ? (
|
|
157
|
+
<ul
|
|
158
|
+
id={props.id}
|
|
159
|
+
className={cn(
|
|
160
|
+
"absolute flex flex-col w-full top-[100%] overflow-hidden",
|
|
161
|
+
"rounded-2xl",
|
|
162
|
+
"bg-(--background) shadow-[0_0_16px_0_var(--shadow)]",
|
|
163
|
+
"animate-appear-tc",
|
|
164
|
+
)}
|
|
165
|
+
>
|
|
166
|
+
{children}
|
|
167
|
+
</ul>
|
|
168
|
+
) : null}
|
|
169
|
+
</div>
|
|
170
|
+
)
|
|
172
171
|
}
|
package/src/toast.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export interface ToastOptions {
|
|
2
2
|
description?: string
|
|
3
3
|
}
|
|
4
|
-
export function toast(title: string,
|
|
4
|
+
export function toast(title: string, _options?: ToastOptions) {
|
|
5
5
|
const toast = document.createElement("div")
|
|
6
6
|
toast.className =
|
|
7
7
|
"transition transition-all transition-duration-250 ease-out absolute top-[-64px] p-4 rounded-2xl text-bold bg-white dark:bg-black inset-ring inset-ring-black/25"
|
package/tsconfig.json
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"allowJs": true,
|
|
4
|
+
"allowImportingTsExtensions": true,
|
|
4
5
|
"declaration": true,
|
|
5
6
|
"declarationMap": true,
|
|
6
7
|
"esModuleInterop": true,
|
|
7
8
|
"incremental": false,
|
|
8
9
|
"isolatedModules": true,
|
|
9
|
-
"
|
|
10
|
-
"
|
|
10
|
+
"jsx": "react-jsx",
|
|
11
|
+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
12
|
+
"module": "Preserve",
|
|
11
13
|
"moduleDetection": "force",
|
|
12
|
-
"moduleResolution": "
|
|
14
|
+
"moduleResolution": "bundler",
|
|
15
|
+
"noEmit": true,
|
|
13
16
|
"noUncheckedIndexedAccess": true,
|
|
17
|
+
"outdir": "dist",
|
|
14
18
|
"resolveJsonModule": true,
|
|
19
|
+
"rootDir": "src",
|
|
15
20
|
"skipLibCheck": true,
|
|
16
21
|
"strict": true,
|
|
17
|
-
"target": "
|
|
18
|
-
"
|
|
19
|
-
"outdir": "dist",
|
|
20
|
-
"rootDir": "src"
|
|
22
|
+
"target": "ESNext",
|
|
23
|
+
"verbatimModuleSyntax": true
|
|
21
24
|
},
|
|
22
25
|
"include": ["src"],
|
|
23
26
|
"exclude": ["node_modules", "dist"]
|