@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 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.241",
5
+ "version": "0.0.243",
6
6
  "main": "src/index.ts",
7
7
  "type": "module",
8
8
  "exports": {
package/src/avatar.tsx CHANGED
@@ -5,7 +5,7 @@ export interface AvatarProps extends ImageProps {
5
5
  fallback: string
6
6
  }
7
7
  export default function Avatar({ fallback, ...props }: Readonly<AvatarProps>) {
8
- const show = true
8
+ const show = false
9
9
 
10
10
  return (
11
11
  <span
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
- 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("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
- primary:
37
- "bg-primary disabled:bg-primary/60 hover:bg-primary/85 text-background",
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",
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
- square: "rounded-none",
53
- rounded: "",
54
- pill: "rounded-full",
36
+ square: "rounded-none",
37
+ rounded: "",
38
+ pill: "rounded-full",
55
39
  }
56
40
  export interface ButtonProps {
57
- variant?: Variant
58
- size?: keyof typeof sizeMap
59
- shape?: "square" | "rounded" | "pill"
60
- // tone?: "default" | "success" | "error" | "warning"
61
- // elevation?: "none" | "xs" | "sm" | "md" | "lg" | "xl"
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
- disabled?: boolean
64
- loading?: boolean
47
+ disabled?: boolean
48
+ loading?: boolean
65
49
 
66
- label?: string
67
- // haptics?: boolean
50
+ label?: string
51
+ // haptics?: boolean
68
52
 
69
- children: ReactNode
70
- onPress?: (event: MouseEvent) => void | Promise<void>
71
- onHover?: (event: MouseEvent) => void | Promise<void> // Web
72
- onFocus?: (event: FocusEvent) => void | Promise<void> // web
73
- onBlur?: (event: FocusEvent) => void | Promise<void> // web
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
- type?: "button" | "reset" | "submit"
76
- id?: string
77
- className?: string
59
+ type?: "button" | "reset" | "submit"
60
+ id?: string
61
+ className?: string
78
62
 
79
- asChild?: boolean
63
+ asChild?: boolean
80
64
  }
81
65
  export default function Button(props: Readonly<ButtonProps>) {
82
- const className = cn(
83
- "flex items-center justify-center leading-none truncate",
84
- "select-none hover:cursor-pointer disabled:hover:cursor-not-allowed",
85
- "transition transition-[background-color] transition-duration-250 ease",
86
- variant[props.variant ?? "primary"],
87
- sizeMap[props.size ?? "xl"],
88
- props.className,
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
- if (props.asChild) {
92
- const children = props.children as ReactHTMLElement<HTMLElement>
93
- return React.cloneElement(children, {
94
- type: props.type,
95
- role: "button",
96
- disabled: props.disabled,
97
- "aria-label": props.label,
98
- onClick: props.onPress,
99
- onMouseOver: props.onHover,
100
- onFocus: props.onFocus,
101
- id: props.id,
102
- className: cn(className, children.props.className),
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
- return (
107
- <button
108
- type={props.type ?? "button"}
109
- disabled={props.loading ? true : props.disabled}
110
- aria-label={props.label}
111
- onClick={props.onPress}
112
- onMouseOver={props.onHover}
113
- onFocus={props.onFocus}
114
- onBlur={props.onBlur}
115
- id={props.id}
116
- className={className}
117
- >
118
- {props.loading ? (
119
- <IconLoader2 className="animate-spin" />
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
@@ -73,7 +73,7 @@ export const Card: React.FC<CardProps> = props => {
73
73
  "flex flex-col",
74
74
  size[props.size ?? "md"],
75
75
  "bg-background",
76
- "sm:inset-ring inset-ring-outline",
76
+ "sm:inset-ring inset-ring-border",
77
77
  props.className,
78
78
  )}
79
79
  >
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 [checked, setChecked] = useState<boolean>(props.checked ?? false)
20
-
21
- const onChange = props.onChange
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
- aria-label={props.label}
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={() => setChecked(v => !v)}
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]: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",
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
- <hr
9
+ <div
10
+ data-orientation={props.orientation ?? "horizontal"}
10
11
  className={cn(
11
- "border-(--outline)",
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
- // "inset-ring inset-ring-disabled disabled:inset-ring-outline focus:inset-ring-primary",
18
- "border border-disabled disabled:border-outline focus:border-primary/75",
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
- // "inset-ring inset-ring-(--red)/50 disabled:inset-ring-(--red)/25 focus:inset-ring-(--red)",
29
- "border border-red/50 disabled:border-red/25 focus:border-red",
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
- <h1
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
- </h1>
41
+ </h2>
42
42
  )
43
43
  }
44
44
  Heading.H3 = (props: Readonly<HeadingProps>) => {
45
45
  return (
46
- <h1
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
- </h1>
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
- <h4
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
- </h4>
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?: ChangeEventHandler<HTMLInputElement>
40
- onInput?: InputEventHandler<HTMLInputElement>
41
- onBlur?: FocusEventHandler<HTMLInputElement>
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
- describedby?: string
34
+ describedby?: string
49
35
  }
50
- const Input = forwardRef<HTMLInputElement, Readonly<InputProps>>(
51
- (props, ref) => {
52
- const { onChange } = props
36
+ const Input = forwardRef<HTMLInputElement, Readonly<InputProps>>((props, ref) => {
37
+ const { onChange } = props
53
38
 
54
- return (
55
- <input
56
- ref={ref}
57
- type={props.type ?? "text"}
58
- inputMode={props.inputMode}
59
- placeholder={props.placeholder}
60
- defaultValue={props.defaultValue}
61
- value={props.value}
62
- name={props.name}
63
- min={props.min}
64
- max={props.max}
65
- minLength={props.minLength}
66
- maxLength={props.maxLength}
67
- pattern={props.pattern}
68
- required={props.required}
69
- readOnly={props.readOnly}
70
- disabled={props.disabled}
71
- onChange={onChange}
72
- onBlur={props.onBlur}
73
- onInvalid={e => e.preventDefault()}
74
- id={props.id}
75
- autoComplete={props.autoComplete}
76
- data-invalid={props.invalid}
77
- aria-describedby={props.describedby}
78
- className={cn(
79
- "flex items-center w-full h-12 px-4",
80
- "peer rounded-2xl",
81
- "transition transition-duration-150 transition-[border,box-shadow] ease-in",
82
- input_variant[props.variant ?? "primary"],
83
- props.className,
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
- placeholder?: string
10
- id?: string
11
- children?: string
9
+ placeholder?: string
10
+ id?: string
11
+ children?: string
12
12
  }
13
13
  export function SelectTrigger(props: SelectTriggerProps) {
14
- return (
15
- <button type="button" className={cn("flex items-center", "bg-(--background)")}>
16
- {props.placeholder}
17
- </button>
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
- 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
- )
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
- value: string
35
- children: ReactNode
36
- className?: string
37
- onPress?: () => void
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
- 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
- )
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
- defaultValue: string
59
- children?: ReactNode
58
+ defaultValue: string
59
+ children?: ReactNode
60
60
  }
61
61
  export function SelectContent(props: SelectContentProps) {
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
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
- // Add/override props here
70
- return React.cloneElement(children, {
71
- onPress() {
72
- setValue(children.props.value)
73
- setExpanded(false)
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
- 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
- {...register("country")}
91
- className={cn("absolute flex items-center justify-center gap-1 px-4 h-12")}
92
- >
93
- <p className="font-medium w-6 h-6">{value}</p>
94
- <IconSelector size={16} className="text-muted-foreground" />
95
- </button>
96
- <ul
97
- className={cn(
98
- "absolute top-[100%] flex flex-col mt-2 w-full max-h-72 overflow-auto",
99
- "rounded-2xl",
100
- "bg-background shadow-[0_0_16px_0_var(--shadow)]",
101
- "animate-popup origin-top-left",
102
- )}
103
- >
104
- {enhanced}
105
- </ul>
106
- </div>
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
- defaultValue?: string
112
- placeholder: string
113
- value?: string
114
- name?: string
115
- id?: string
116
- children: ReactElement<SelectItemProps>[]
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
- const [expanded, setExpanded] = useState(false)
120
- const [value, setValue] = useState(props.defaultValue)
121
- const [selected, setSelected] = useState<ReactNode>(props.placeholder)
118
+ const [expanded, setExpanded] = useState(false)
119
+ const [value, setValue] = useState(props.defaultValue)
120
+ const [selected, setSelected] = useState<ReactNode>(props.placeholder)
122
121
 
123
- const children: ReactElement[] = []
124
- for (let i = 0; i < props.children.length; i++) {
125
- const child = props.children[i]
126
- if (React.isValidElement<SelectItemProps>(child)) {
127
- children.push(
128
- React.cloneElement(child, {
129
- onClick() {
130
- setValue(child.props.value)
131
- setSelected(child.props.children)
132
- setExpanded(false)
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
- return (
140
- <div className="relative flex flex-col w-full max-w-56">
141
- <button
142
- type="button"
143
- role="combobox"
144
- aria-expanded={expanded}
145
- aria-autocomplete="none"
146
- aria-haspopup="listbox"
147
- value={value}
148
- name={props.name}
149
- id={props.id}
150
- onClick={() => setExpanded(!expanded)}
151
- // onBlur={() => setExpanded(false)}
152
- className={cn("flex items-center gap-x-4", "[&>svg]:size-4 [&>svg]:scale-125", "focus:underline")}
153
- >
154
- {selected}
155
- <IconChevronDown size={16} />
156
- </button>
157
- {expanded ? (
158
- <ul
159
- id={props.id}
160
- className={cn(
161
- "absolute flex flex-col w-full top-[100%] overflow-hidden",
162
- "rounded-2xl",
163
- "bg-(--background) shadow-[0_0_16px_0_var(--shadow)]",
164
- "animate-appear-tc",
165
- )}
166
- >
167
- {children}
168
- </ul>
169
- ) : null}
170
- </div>
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, options?: ToastOptions) {
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
- "$schema": "https://json.schemastore.org/tsconfig",
3
- "compileroptions": {
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
- "lib": ["es2022", "DOM", "DOM.Iterable"],
10
- "module": "NodeNext",
10
+ "jsx": "react-jsx",
11
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
12
+ "module": "Preserve",
11
13
  "moduleDetection": "force",
12
- "moduleResolution": "NodeNext",
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": "ES2022",
18
- "jsx": "react-jsx",
19
- "outdir": "dist",
20
- "rootDir": "src"
22
+ "target": "ESNext",
23
+ "verbatimModuleSyntax": true
21
24
  },
22
25
  "include": ["src"],
23
26
  "exclude": ["node_modules", "dist"]