@modlin/ui 0.0.243 → 0.0.244

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/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ The Modlin Distributable License (MDL)
2
+
3
+ Copyright (c) 2025 Modlin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the “Software”), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ Any use, distribution, modification, integration, or implementation of the
16
+ Software in another project must include clear and visible credit to the
17
+ Software and to its original author. This credit must be placed in all public
18
+ distributions, documentation, and any “About” or “Credits” section of the
19
+ implementing project.
20
+
21
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
+ THE SOFTWARE.
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.243",
5
+ "version": "0.0.244",
6
6
  "main": "src/index.ts",
7
7
  "type": "module",
8
8
  "exports": {
package/src/alert.tsx CHANGED
@@ -54,7 +54,7 @@ export function Alert(props: Readonly<AlertProps>) {
54
54
  className={cn(
55
55
  "grid grid-cols-[16px_1fr] auto-rows-auto gap-x-4 gap-y-1 items-center justify-center",
56
56
  "p-4 rounded-2xl",
57
- "inset-ring inset-ring-black/15 dark:inset-ring-white/10",
57
+ "inset-ring inset-ring-border",
58
58
  props.className,
59
59
  )}
60
60
  >
@@ -68,7 +68,7 @@ Alert.Destructive = (props: Readonly<AlertProps>) => {
68
68
  className={cn(
69
69
  "grid grid-cols-[16px_1fr] auto-rows-auto gap-x-4 gap-y-1 items-center justify-center",
70
70
  "p-4 rounded-2xl",
71
- "inset-ring inset-ring-black/15 dark:inset-ring-white/10",
71
+ "inset-ring inset-ring-border",
72
72
  "text-red-600 dark:text-red-400",
73
73
  props.className,
74
74
  )}
package/src/avatar.tsx CHANGED
@@ -1,3 +1,5 @@
1
+ "use client"
2
+ import { useState } from "react"
1
3
  import { cn } from "./utils"
2
4
  import Image, { type ImageProps } from "next/image"
3
5
 
@@ -5,22 +7,27 @@ export interface AvatarProps extends ImageProps {
5
7
  fallback: string
6
8
  }
7
9
  export default function Avatar({ fallback, ...props }: Readonly<AvatarProps>) {
8
- const show = false
9
-
10
+ const [error, setError] = useState(false)
11
+ if (error) {
12
+ return (
13
+ <span
14
+ className={cn(
15
+ "relative flex shrink-0 size-8 overflow-hidden rounded-full bg-background inset-ring inset-ring-border text-sm font-medium leading-none",
16
+ props.className,
17
+ )}
18
+ >
19
+ <span className="flex items-center justify-center w-full font-mono text-sm">{fallback.slice(0, 3).toUpperCase()}</span>
20
+ </span>
21
+ )
22
+ }
10
23
  return (
11
24
  <span
12
25
  className={cn(
13
- "relative flex shrink-0 size-8 overflow-hidden rounded-full ring ring-(--outline)",
26
+ "relative flex shrink-0 size-8 overflow-hidden rounded-full bg-background border border-border text-sm font-medium leading-none",
14
27
  props.className,
15
28
  )}
16
29
  >
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
- )}
30
+ <Image width={32} height={32} onError={() => setError(true)} {...props} className={cn("aspect-square size-full", props.className)} />
24
31
  </span>
25
32
  )
26
33
  }
package/src/button.tsx CHANGED
@@ -2,22 +2,22 @@ import { cn } from "./utils"
2
2
  import { IconLoader2 } from "@tabler/icons-react"
3
3
  import type { MouseEvent, FocusEvent, ReactNode, ReactHTMLElement } from "react"
4
4
  import React from "react"
5
- import type { Variant, Variants } from "./global"
5
+ import type { Variant, Size, Shape } from "./globals"
6
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("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"),
7
+ const size: Record<Size, string> = {
8
+ sm: cn("h-8 px-4 gap-x-1 text-sm font-medium"),
9
+ md: cn("h-9 px-4.5 gap-x-1 text-sm font-medium"),
10
+ lg: cn("h-11 px-5 gap-x-1 text-base font-medium", "[&>svg]:size-4"),
11
+ xl: cn("h-12 px-6 gap-x-1 text-base 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 text-sm font-medium"),
17
+ // iconr: cn("p-2 rounded-2xl text-sm font-medium"),
18
18
  none: cn("overflow-visible"),
19
19
  }
20
- const variant: Variants = {
20
+ const variant: Record<Variant, string> = {
21
21
  primary: "bg-primary disabled:bg-primary/60 hover:bg-primary/85 active:bg-primary/85 text-background",
22
22
  secondary: "bg-secondary hover:bg-secondary/75",
23
23
  destructive: "bg-red hover:bg-red/85 text-white",
@@ -32,43 +32,65 @@ const variant: Variants = {
32
32
  // outline_red: "inset-ring inset-ring-(--red)/50 hover:bg-(--red)/5 text-(--red)",
33
33
  // shadcn: "rounded-xl bg-(--primary) hover:bg-(--primary)/85 text-white dark:text-black",
34
34
  } as const
35
- const shapes = {
35
+ const shape: Record<Shape, string> = {
36
36
  square: "rounded-none",
37
- rounded: "",
37
+ rounded: "rounded-2xl",
38
38
  pill: "rounded-full",
39
39
  }
40
40
  export interface ButtonProps {
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"
46
-
47
- disabled?: boolean
48
- loading?: boolean
49
-
41
+ /** @android @ios @web */
42
+ disabled?: boolean // state
43
+ /** @android @ios @web */
44
+ loading?: boolean // state
45
+ /** @android @ios @web */
50
46
  label?: string
51
- // haptics?: boolean
52
-
47
+ /** @android @ios @web */
48
+ title?: string
49
+ /** @android @ios @web */
53
50
  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
58
-
59
- type?: "button" | "reset" | "submit"
51
+ /** @android @ios @web */
52
+ asChild?: boolean
53
+ /** @android @ios @web */
54
+ variant?: Variant
55
+ /** @android @ios @web */
56
+ size?: Size
57
+ /** @android @ios @web */
58
+ shape?: Shape
59
+ /** @android @ios */
60
+ haptics?: boolean
61
+ /** @android @ios */
62
+ full?: boolean // utility
63
+ /** @android @ios */
64
+ rounded?: number // utility
65
+ /** @web */
60
66
  id?: string
67
+ /** @web */
68
+ type?: "button" | "reset" | "submit"
69
+ /** @web */
61
70
  className?: string
62
-
63
- asChild?: boolean
71
+ /** @android @ios @web */
72
+ onPress?(event: MouseEvent): void | Promise<void>
73
+ /** @android @ios @web */
74
+ onPressIn?(event: MouseEvent): void | Promise<void>
75
+ /** @android @ios @web */
76
+ onPressOut?(event: MouseEvent): void | Promise<void>
77
+ /** @web */
78
+ onHover?(event: MouseEvent): void | Promise<void>
79
+ /** @web */
80
+ onFocus?(event: FocusEvent): void | Promise<void>
81
+ /** @web */
82
+ onBlur?(event: FocusEvent): void | Promise<void>
83
+ // tone?: "default" | "success" | "error" | "warning"
84
+ // elevation?: "none" | "xs" | "sm" | "md" | "lg" | "xl"
64
85
  }
65
86
  export default function Button(props: Readonly<ButtonProps>) {
66
87
  const className = cn(
67
88
  "line-clamp-1 flex items-center justify-center leading-none text-center",
68
89
  "select-none hover:cursor-pointer disabled:hover:cursor-not-allowed",
69
- "transition-[background-color] transition-duration-250 ease",
90
+ "transition-[background-color] duration-250 ease",
91
+ size[props.size ?? "xl"],
70
92
  variant[props.variant ?? "primary"],
71
- sizeMap[props.size ?? "xl"],
93
+ shape[props.shape ?? "pill"],
72
94
  props.className,
73
95
  )
74
96
 
@@ -80,6 +102,8 @@ export default function Button(props: Readonly<ButtonProps>) {
80
102
  disabled: props.disabled,
81
103
  "aria-label": props.label,
82
104
  onClick: props.onPress,
105
+ onMouseDown: props.onPressIn,
106
+ onMouseUp: props.onPressOut,
83
107
  onMouseOver: props.onHover,
84
108
  onFocus: props.onFocus,
85
109
  id: props.id,
@@ -93,13 +117,15 @@ export default function Button(props: Readonly<ButtonProps>) {
93
117
  disabled={props.loading ? true : props.disabled}
94
118
  aria-label={props.label}
95
119
  onClick={props.onPress}
120
+ onMouseDown={props.onPressIn}
121
+ onMouseUp={props.onPressOut}
96
122
  onMouseOver={props.onHover}
97
123
  onFocus={props.onFocus}
98
124
  onBlur={props.onBlur}
99
125
  id={props.id}
100
126
  className={className}
101
127
  >
102
- {props.loading ? <IconLoader2 className="animate-spin" /> : props.children}
128
+ {props.loading ? <IconLoader2 className="animate-spin" /> : (props.title || props.children)}
103
129
  </button>
104
130
  )
105
131
  }
package/src/card.tsx CHANGED
@@ -2,23 +2,20 @@ import { cn } from "./utils"
2
2
  import type * as React from "react"
3
3
 
4
4
  export interface CardHeaderProps {
5
+ children?: React.ReactNode
5
6
  className?: string
6
- children: React.ReactNode
7
7
  }
8
8
  export const CardHeader: React.FC<CardHeaderProps> = props => {
9
9
  return (
10
- <header
11
- data-slot="card-header"
12
- className={cn("flex flex-col gap-4", props.className)}
13
- >
10
+ <header data-slot="card-header" className={cn("flex flex-col gap-4", props.className)}>
14
11
  {props.children}
15
12
  </header>
16
13
  )
17
14
  }
18
15
 
19
16
  export interface CardContentProps {
17
+ children?: React.ReactNode
20
18
  className?: string
21
- children: React.ReactNode
22
19
  }
23
20
  export const CardContent: React.FC<CardContentProps> = props => {
24
21
  return (
@@ -29,22 +26,19 @@ export const CardContent: React.FC<CardContentProps> = props => {
29
26
  }
30
27
 
31
28
  export interface CardFooterProps {
29
+ children?: React.ReactNode
32
30
  className?: string
33
- children: React.ReactNode
34
31
  }
35
32
  export const CardFooter: React.FC<CardFooterProps> = props => {
36
33
  return (
37
- <footer
38
- data-slot="card-footer"
39
- className={cn("flex [.border-t]:pt-4", props.className)}
40
- >
34
+ <footer data-slot="card-footer" className={cn("flex [.border-t]:pt-4", props.className)}>
41
35
  {props.children}
42
36
  </footer>
43
37
  )
44
38
  }
45
39
 
46
40
  export interface CardAction {
47
- children: React.ReactNode
41
+ children?: React.ReactNode
48
42
  className?: string
49
43
  }
50
44
  export function CardAction(props: Readonly<CardAction>) {
@@ -56,29 +50,19 @@ export function CardAction(props: Readonly<CardAction>) {
56
50
  }
57
51
 
58
52
  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",
53
+ md: "p-4 gap-4 rounded-2xl",
54
+ lg: "p-6 gap-6 rounded-2xl",
55
+ xl: "p-8 gap-8 rounded-4xl",
62
56
  }
63
57
 
64
58
  export interface CardProps {
65
- className?: string
66
59
  size?: "lg" | "xl"
67
- children: React.ReactNode
60
+ children?: React.ReactNode
61
+ className?: string
68
62
  }
69
63
  export const Card: React.FC<CardProps> = props => {
70
64
  return (
71
- <div
72
- className={cn(
73
- "flex flex-col",
74
- size[props.size ?? "md"],
75
- "bg-background",
76
- "sm:inset-ring inset-ring-border",
77
- props.className,
78
- )}
79
- >
80
- {props.children}
81
- </div>
65
+ <div className={cn("flex flex-col", size[props.size ?? "md"], "bg-background", "inset-ring inset-ring-border", props.className)}>{props.children}</div>
82
66
  )
83
67
  }
84
68
 
package/src/checkbox.tsx CHANGED
@@ -1,7 +1,6 @@
1
1
  "use client"
2
- import { useEffect, useRef, useState, type ChangeEventHandler, type ReactElement, type ReactNode } from "react"
2
+ import { useRef, type ChangeEvent, type ReactElement, type ReactNode } from "react"
3
3
  import { cn } from "./utils"
4
- import { setConfig } from "next/config"
5
4
 
6
5
  export interface ViewProps {
7
6
  children?: ReactNode
@@ -9,18 +8,29 @@ export interface ViewProps {
9
8
  }
10
9
 
11
10
  export interface CheckboxProps {
11
+ /** @android @ios @web */
12
12
  disabled?: boolean
13
- onChange?: ChangeEventHandler<HTMLInputElement> // web
14
- onValueChange?(checked: boolean): void | Promise<void> // web
15
- checked?: boolean
16
- defaultChecked?: boolean
17
- // loading?: boolean
13
+ /** @android @ios @web */
14
+ defaultChecked?: boolean
15
+ /** @android @ios @web */
16
+ checked?: boolean
17
+ /** @android @ios @web */
18
18
  label?: string
19
-
20
- required?: boolean // web
21
- name?: string // web
22
- id?: string // web
23
- value?: string
19
+ /** @web */
20
+ required?: boolean
21
+ /** @web */
22
+ name?: string
23
+ /** @web */
24
+ id?: string
25
+ /** @web */
26
+ asChild?: boolean
27
+ /** @android @ios @web */
28
+ onChange?(checked: boolean): void
29
+ // note: dropped support for RHF to add native support
30
+ /** @deprecated @use checked */
31
+ value?: string
32
+ /** @deprecated @use onChange */
33
+ onValueChange?(event: ChangeEvent<HTMLInputElement>): void
24
34
  }
25
35
  export default function Checkbox(props: Readonly<CheckboxProps>): ReactElement<CheckboxProps> {
26
36
  const input = useRef<HTMLInputElement>(null)
@@ -0,0 +1,30 @@
1
+ import { Heading4 } from "./heading"
2
+ import Button from "./button"
3
+ import Input from "./input"
4
+ import Label from "./label"
5
+ import { Muted } from "./typography"
6
+
7
+ export function Example() {
8
+ return (
9
+ <div className="flex flex-1 flex-col gap-8 p-8 bg-background">
10
+ <header>
11
+ <Heading4 className="h-8">Login to your account</Heading4>
12
+ <Muted>Enter your email address below to login to your account</Muted>
13
+ </header>
14
+ <div className="flex flex-col gap-4">
15
+ <div className="flex flex-col gap-2">
16
+ <Label htmlFor="email">Email address</Label>
17
+ <Input placeholder="m@example.com" id="email" type="email" inputMode="email" autoComplete="email" autoCapitalize="none" />
18
+ </div>
19
+ <div className="flex flex-col gap-2">
20
+ <Label htmlFor="password">Password</Label>
21
+ <Input id="password" type="password" autoComplete="password" autoCapitalize="none" />
22
+ </div>
23
+ </div>
24
+ <div className="flex flex-col gap-4">
25
+ <Button title="Login" haptics />
26
+ <Button variant="outline" title="Login with Google" haptics />
27
+ </div>
28
+ </div>
29
+ )
30
+ }
package/src/globals.ts ADDED
@@ -0,0 +1,3 @@
1
+ export type Variant = "primary" | "secondary" | "destructive" | "outline" | "ghost" | "link" | "none"
2
+ export type Size = "sm" | "md" | "lg" | "xl" | "icon" | "none"
3
+ export type Shape = "square" | "rounded" | "pill"
package/src/heading.tsx CHANGED
@@ -17,7 +17,7 @@ export default function Heading(props: Readonly<HeadingProps>) {
17
17
  )
18
18
  }
19
19
 
20
- Heading.H1 = (props: Readonly<HeadingProps>) => {
20
+ export function Heading1(props: Readonly<HeadingProps>) {
21
21
  return (
22
22
  <h1
23
23
  className={cn(
@@ -29,7 +29,7 @@ Heading.H1 = (props: Readonly<HeadingProps>) => {
29
29
  </h1>
30
30
  )
31
31
  }
32
- Heading.H2 = (props: Readonly<HeadingProps>) => {
32
+ export function Heading2(props: Readonly<HeadingProps>) {
33
33
  return (
34
34
  <h2
35
35
  className={cn(
@@ -41,7 +41,7 @@ Heading.H2 = (props: Readonly<HeadingProps>) => {
41
41
  </h2>
42
42
  )
43
43
  }
44
- Heading.H3 = (props: Readonly<HeadingProps>) => {
44
+ export function Heading3(props: Readonly<HeadingProps>) {
45
45
  return (
46
46
  <h3
47
47
  className={cn(
@@ -53,7 +53,7 @@ Heading.H3 = (props: Readonly<HeadingProps>) => {
53
53
  </h3>
54
54
  )
55
55
  }
56
- Heading.H4 = (props: Readonly<HeadingProps>) => {
56
+ export function Heading4(props: Readonly<HeadingProps>) {
57
57
  return (
58
58
  <h4
59
59
  className={cn(
@@ -65,7 +65,7 @@ Heading.H4 = (props: Readonly<HeadingProps>) => {
65
65
  </h4>
66
66
  )
67
67
  }
68
- Heading.H5 = (props: Readonly<HeadingProps>) => {
68
+ export function Heading5(props: Readonly<HeadingProps>) {
69
69
  return (
70
70
  <h5
71
71
  className={cn(
@@ -77,7 +77,7 @@ Heading.H5 = (props: Readonly<HeadingProps>) => {
77
77
  </h5>
78
78
  )
79
79
  }
80
- Heading.H6 = (props: Readonly<HeadingProps>) => {
80
+ export function Heading6(props: Readonly<HeadingProps>) {
81
81
  return (
82
82
  <h6
83
83
  className={cn(
package/src/input.tsx CHANGED
@@ -1,37 +1,89 @@
1
- import { type ChangeEvent, type FocusEvent, type InputEvent, type HTMLInputAutoCompleteAttribute, forwardRef } from "react"
2
- import { input_variant, type Variant } from "./global"
1
+ import { type ChangeEvent, type FocusEvent, type HTMLInputAutoCompleteAttribute, forwardRef } from "react"
2
+ import type { Variant } from "./globals"
3
3
  import { cn } from "./utils"
4
4
 
5
- export interface InputProps {
6
- variant?: Variant
5
+ export const input_variant: Record<Variant, string> = {
6
+ primary: cn(
7
+ "bg-background/25 backdrop-blur-sm",
8
+ "placeholder:text-muted-foreground disabled:text-disabled",
9
+ "inset-ring inset-ring-border disabled:inset-ring-disabled focus:inset-ring-primary/75",
10
+ // "border border-border disabled:border-disabled focus:border-primary/75",
11
+ "focus:ring-4 focus:ring-primary/10 dark:focus:ring-primary/15",
12
+ "invalid:inset-ring-red",
13
+ // "data-[invalid=true]:border data-[invalid=true]:border-red/50 data-[invalid=true]:focus:border-red",
14
+ "data-[invalid=true]:inset-ring-red/50 data-[invalid=true]:focus:inset-ring-red/75",
15
+ "data-[invalid=true]:focus:ring-4 data-[invalid=true]:focus:ring-red/10",
16
+ ),
17
+ secondary: cn(),
18
+ destructive: cn(
19
+ "bg-background",
20
+ "placeholder:text-muted-foreground disabled:text-disabled",
21
+ "inset-ring inset-ring-red/50 disabled:inset-ring-red/25 focus:inset-ring-red/75",
22
+ "focus:ring-4 focus:ring-red/10",
23
+ // "border border-red/50 disabled:border-red/25 focus:border-red",
24
+ "focus:ring-4 focus:ring-red/5",
25
+ ),
26
+ outline: cn(),
27
+ ghost: cn(),
28
+ link: cn(),
29
+ none: cn("text-()"),
30
+ }
31
+
32
+ type InputType = "text" | "password" | "email" | "number" | "tel" | "url" | "file"
33
+ type InputMode = "search" | "text" | "email" | "tel" | "url" | "none" | "numeric" | "decimal"
34
+ type BlurEvent = FocusEvent<HTMLInputElement, Element>
7
35
 
8
- type?: "text" | "password" | "email" | "number" | "tel" | "url" | "file"
9
- inputMode?: "search" | "text" | "email" | "tel" | "url" | "none" | "numeric" | "decimal"
36
+ export interface InputProps {
37
+ /** @android @ios @web */
10
38
  placeholder?: string
39
+ /** @android @ios @web */
11
40
  defaultValue?: string
12
-
41
+ /** @android @ios @web */
42
+ inputMode?: InputMode
43
+ /** @android @ios @web */
44
+ value?: string
45
+ /** @android @ios @web */
46
+ readOnly?: boolean
47
+ /** @android @ios @web */
48
+ maxLength?: number
49
+ /** @android @ios @web */
50
+ autoCapitalize?: "none" | "sentences" | "words" | "characters"
51
+ /** @android @ios @web */
52
+ autoComplete?: HTMLInputAutoCompleteAttribute
53
+ /** @android @ios @web */
54
+ autoCorrect?: boolean
55
+ /** @android @ios @web */
56
+ variant?: Variant
57
+ /** @web */
58
+ id?: string
59
+ /** @web */
60
+ type?: InputType
61
+ /** @web */
13
62
  name?: string
63
+ /** @web */
64
+ required?: boolean // validation
65
+ /** @web */
66
+ disabled?: boolean
67
+ /** @web */
14
68
  pattern?: string // validation
69
+ /** @web */
15
70
  min?: number | string // validation
71
+ /** @web */
16
72
  max?: number | string // validation
17
- maxLength?: number // validation
73
+ /** @web */
18
74
  minLength?: number // validation
19
- required?: boolean // validation
20
-
21
- readOnly?: boolean
22
- disabled?: boolean
23
-
24
- value?: string
25
- onChange?: (event: ChangeEvent<HTMLInputElement>) => void
26
- onBlur?: (event: FocusEvent<HTMLInputElement>) => void
27
- onInput?: (event: InputEvent<HTMLInputElement>) => void
28
-
29
- id?: string
30
- autoComplete?: HTMLInputAutoCompleteAttribute
31
- // onChange?: (value: string) => void
32
- className?: string
75
+ /** @web */
33
76
  invalid?: boolean
77
+ /** @web */
34
78
  describedby?: string
79
+ /** @web */
80
+ className?: string
81
+ /** @android @ios @web */
82
+ onChange?(event: ChangeEvent): void
83
+ /** @android @ios @web */
84
+ onFocus?(event: FocusEvent): void
85
+ /** @android @ios @web */
86
+ onBlur?(event: BlurEvent): void
35
87
  }
36
88
  const Input = forwardRef<HTMLInputElement, Readonly<InputProps>>((props, ref) => {
37
89
  const { onChange } = props
@@ -54,12 +106,15 @@ const Input = forwardRef<HTMLInputElement, Readonly<InputProps>>((props, ref) =>
54
106
  readOnly={props.readOnly}
55
107
  disabled={props.disabled}
56
108
  onChange={onChange}
57
- onBlur={props.onBlur}
109
+ onFocus={props.onFocus}
110
+ onBlur={props.onBlur}
58
111
  onInvalid={e => e.preventDefault()}
59
112
  id={props.id}
113
+ autoCapitalize={props.autoCapitalize}
60
114
  autoComplete={props.autoComplete}
61
- data-invalid={props.invalid}
115
+ autoCorrect={props.autoCorrect ? "on" : "off"}
62
116
  aria-describedby={props.describedby}
117
+ data-invalid={props.invalid}
63
118
  className={cn(
64
119
  "flex items-center w-full h-12 px-4",
65
120
  "transition transition-duration-150 transition-[box-shadow] ease-in",
@@ -0,0 +1,45 @@
1
+ import type { ChangeEvent, ReactNode } from "react"
2
+ import { cn } from "./utils"
3
+
4
+ export interface SelectProps {
5
+ children?: ReactNode
6
+ name?: string
7
+ id?: string
8
+ onChange?: (event: ChangeEvent<HTMLSelectElement>) => void
9
+ }
10
+ export function Select(props: SelectProps) {
11
+ return (
12
+ <select
13
+ name={props.name}
14
+ id={props.id}
15
+ onChange={props.onChange}
16
+ className={cn(
17
+ "flex items-center h-12 px-4 rounded-2xl bg-background/25 backdrop-blur-sm inset-ring inset-ring-border",
18
+ "open:inset-ring-primary/75 open:ring-4 open:ring-primary/10 dark:open:ring-primary/15 focus:inset-ring-primary/75 focus:ring-4 focus:ring-primary/10 dark:focus:ring-primary/15",
19
+ "transition duration-150 ease-in",
20
+ )}
21
+ >
22
+ {props.children}
23
+ </select>
24
+ )
25
+ }
26
+
27
+ export interface SelectItemProps {
28
+ value: string
29
+ children?: ReactNode
30
+ className?: string
31
+ }
32
+ export function SelectItem(props: SelectItemProps) {
33
+ return (
34
+ <option
35
+ className={cn(
36
+ "gap-4 h-9 min-h-9 px-3 rounded-lg text-foreground hover:bg-secondary focus:bg-secondary checked:bg-secondary",
37
+ "transition duration-150 ease-out",
38
+ props.className,
39
+ )}
40
+ value={props.value}
41
+ >
42
+ {props.children}
43
+ </option>
44
+ )
45
+ }
package/src/global.ts DELETED
@@ -1,38 +0,0 @@
1
- import { cn } from "./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-border disabled:inset-ring-disabled focus:inset-ring-primary/75",
18
- // "border border-border disabled:border-disabled focus:border-primary/75",
19
- "focus:ring-4 focus:ring-primary/10",
20
- "invalid:inset-ring-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",
23
- "data-[invalid=true]:focus:ring-4 data-[invalid=true]:focus:ring-red/10",
24
- ),
25
- secondary: cn(),
26
- destructive: cn(
27
- "bg-background",
28
- "placeholder:text-description disabled:text-disabled",
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",
32
- "focus:ring-4 focus:ring-red/5",
33
- ),
34
- outline: cn(),
35
- ghost: cn(),
36
- link: cn(),
37
- none: cn("text-()"),
38
- }