@taicode/common-web 1.0.2 → 1.0.3

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taicode/common-web",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "author": "Alain",
5
5
  "license": "ISC",
6
6
  "description": "",
@@ -9,12 +9,24 @@
9
9
  "dev": "tsc -p tsconfig.json --watch"
10
10
  },
11
11
  "peerDependencies": {
12
+ "@types/react": ">=18",
12
13
  "mobx": ">=6",
13
- "react": ">=18",
14
- "@types/react": ">=18"
14
+ "react": ">=18"
15
15
  },
16
16
  "exports": {
17
- "./utils/*": ["./output/utils/*"],
18
- "./hooks/*": ["./output/hooks/*"]
17
+ "./utils/*": [
18
+ "./output/utils/*"
19
+ ],
20
+ "./hooks/*": [
21
+ "./output/hooks/*"
22
+ ],
23
+ "./catalyst/*": [
24
+ "./output/catalyst/*"
25
+ ]
26
+ },
27
+ "dependencies": {
28
+ "@headlessui/react": "^2.2.4",
29
+ "clsx": "^2.1.1",
30
+ "framer-motion": "^12.19.2"
19
31
  }
20
32
  }
@@ -0,0 +1,95 @@
1
+ import * as Headless from '@headlessui/react'
2
+ import clsx from 'clsx'
3
+ import type React from 'react'
4
+ import { Text } from './text'
5
+
6
+ const sizes = {
7
+ xs: 'sm:max-w-xs',
8
+ sm: 'sm:max-w-sm',
9
+ md: 'sm:max-w-md',
10
+ lg: 'sm:max-w-lg',
11
+ xl: 'sm:max-w-xl',
12
+ '2xl': 'sm:max-w-2xl',
13
+ '3xl': 'sm:max-w-3xl',
14
+ '4xl': 'sm:max-w-4xl',
15
+ '5xl': 'sm:max-w-5xl',
16
+ }
17
+
18
+ export function Alert({
19
+ size = 'md',
20
+ className,
21
+ children,
22
+ ...props
23
+ }: { size?: keyof typeof sizes; className?: string; children: React.ReactNode } & Omit<
24
+ Headless.DialogProps,
25
+ 'as' | 'className'
26
+ >) {
27
+ return (
28
+ <Headless.Dialog {...props}>
29
+ <Headless.DialogBackdrop
30
+ transition
31
+ className="fixed inset-0 flex w-screen justify-center overflow-y-auto bg-zinc-950/15 px-2 py-2 transition duration-100 focus:outline-0 data-closed:opacity-0 data-enter:ease-out data-leave:ease-in sm:px-6 sm:py-8 lg:px-8 lg:py-16 dark:bg-zinc-950/50"
32
+ />
33
+
34
+ <div className="fixed inset-0 w-screen overflow-y-auto pt-6 sm:pt-0">
35
+ <div className="grid min-h-full grid-rows-[1fr_auto_1fr] justify-items-center p-8 sm:grid-rows-[1fr_auto_3fr] sm:p-4">
36
+ <Headless.DialogPanel
37
+ transition
38
+ className={clsx(
39
+ className,
40
+ sizes[size],
41
+ 'row-start-2 w-full rounded-2xl bg-white p-8 shadow-lg ring-1 ring-zinc-950/10 sm:rounded-2xl sm:p-6 dark:bg-zinc-900 dark:ring-white/10 forced-colors:outline',
42
+ 'transition duration-100 will-change-transform data-closed:opacity-0 data-enter:ease-out data-closed:data-enter:scale-95 data-leave:ease-in'
43
+ )}
44
+ >
45
+ {children}
46
+ </Headless.DialogPanel>
47
+ </div>
48
+ </div>
49
+ </Headless.Dialog>
50
+ )
51
+ }
52
+
53
+ export function AlertTitle({
54
+ className,
55
+ ...props
56
+ }: { className?: string } & Omit<Headless.DialogTitleProps, 'as' | 'className'>) {
57
+ return (
58
+ <Headless.DialogTitle
59
+ {...props}
60
+ className={clsx(
61
+ className,
62
+ 'text-center text-base/6 font-semibold text-balance text-zinc-950 sm:text-left sm:text-sm/6 sm:text-wrap dark:text-white'
63
+ )}
64
+ />
65
+ )
66
+ }
67
+
68
+ export function AlertDescription({
69
+ className,
70
+ ...props
71
+ }: { className?: string } & Omit<Headless.DescriptionProps<typeof Text>, 'as' | 'className'>) {
72
+ return (
73
+ <Headless.Description
74
+ as={Text}
75
+ {...props}
76
+ className={clsx(className, 'mt-2 text-center text-pretty sm:text-left')}
77
+ />
78
+ )
79
+ }
80
+
81
+ export function AlertBody({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
82
+ return <div {...props} className={clsx(className, 'mt-4')} />
83
+ }
84
+
85
+ export function AlertActions({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
86
+ return (
87
+ <div
88
+ {...props}
89
+ className={clsx(
90
+ className,
91
+ 'mt-6 flex flex-col-reverse items-center justify-end gap-3 *:w-full sm:mt-4 sm:flex-row sm:*:w-auto'
92
+ )}
93
+ />
94
+ )
95
+ }
@@ -0,0 +1,11 @@
1
+ import type React from 'react'
2
+
3
+ export function AuthLayout({ children }: { children: React.ReactNode }) {
4
+ return (
5
+ <main className="flex min-h-dvh flex-col p-2">
6
+ <div className="flex grow items-center justify-center p-6 lg:rounded-lg lg:bg-white lg:p-10 lg:shadow-xs lg:ring-1 lg:ring-zinc-950/5 dark:lg:bg-zinc-900 dark:lg:ring-white/10">
7
+ {children}
8
+ </div>
9
+ </main>
10
+ )
11
+ }
@@ -0,0 +1,84 @@
1
+ import * as Headless from '@headlessui/react'
2
+ import clsx from 'clsx'
3
+ import React, { forwardRef } from 'react'
4
+ import { TouchTarget } from './button'
5
+ import { Link } from './link'
6
+
7
+ type AvatarProps = {
8
+ src?: string | null
9
+ square?: boolean
10
+ initials?: string
11
+ alt?: string
12
+ className?: string
13
+ }
14
+
15
+ export function Avatar({
16
+ src = null,
17
+ square = false,
18
+ initials,
19
+ alt = '',
20
+ className,
21
+ ...props
22
+ }: AvatarProps & React.ComponentPropsWithoutRef<'span'>) {
23
+ return (
24
+ <span
25
+ data-slot="avatar"
26
+ {...props}
27
+ className={clsx(
28
+ className,
29
+ // Basic layout
30
+ 'inline-grid shrink-0 align-middle [--avatar-radius:20%] *:col-start-1 *:row-start-1',
31
+ 'outline -outline-offset-1 outline-black/10 dark:outline-white/10',
32
+ // Border radius
33
+ square ? 'rounded-(--avatar-radius) *:rounded-(--avatar-radius)' : 'rounded-full *:rounded-full'
34
+ )}
35
+ >
36
+ {initials && (
37
+ <svg
38
+ className="size-full fill-current p-[5%] text-[48px] font-medium uppercase select-none"
39
+ viewBox="0 0 100 100"
40
+ aria-hidden={alt ? undefined : 'true'}
41
+ >
42
+ {alt && <title>{alt}</title>}
43
+ <text x="50%" y="50%" alignmentBaseline="middle" dominantBaseline="middle" textAnchor="middle" dy=".125em">
44
+ {initials}
45
+ </text>
46
+ </svg>
47
+ )}
48
+ {src && <img className="size-full" src={src} alt={alt} />}
49
+ </span>
50
+ )
51
+ }
52
+
53
+ export const AvatarButton = forwardRef(function AvatarButton(
54
+ {
55
+ src,
56
+ square = false,
57
+ initials,
58
+ alt,
59
+ className,
60
+ ...props
61
+ }: AvatarProps &
62
+ (Omit<Headless.ButtonProps, 'as' | 'className'> | Omit<React.ComponentPropsWithoutRef<typeof Link>, 'className'>),
63
+ ref: React.ForwardedRef<HTMLElement>
64
+ ) {
65
+ let classes = clsx(
66
+ className,
67
+ square ? 'rounded-[20%]' : 'rounded-full',
68
+ 'relative inline-grid focus:not-data-focus:outline-hidden data-focus:outline-2 data-focus:outline-offset-2 data-focus:outline-blue-500'
69
+ )
70
+
71
+ return 'href' in props ? (
72
+ <Link {...props} className={classes} ref={ref as React.ForwardedRef<HTMLAnchorElement>}>
73
+ <TouchTarget>
74
+ <Avatar src={src} square={square} initials={initials} alt={alt} />
75
+ </TouchTarget>
76
+ </Link>
77
+ ) : (
78
+ <Headless.Button {...props} className={classes} ref={ref}>
79
+ <TouchTarget>
80
+ <Avatar src={src} square={square} initials={initials} alt={alt} />
81
+ </TouchTarget>
82
+ </Headless.Button>
83
+ )
84
+ })
@@ -0,0 +1,82 @@
1
+ import * as Headless from '@headlessui/react'
2
+ import clsx from 'clsx'
3
+ import React, { forwardRef } from 'react'
4
+ import { TouchTarget } from './button'
5
+ import { Link } from './link'
6
+
7
+ const colors = {
8
+ red: 'bg-red-500/15 text-red-700 group-data-hover:bg-red-500/25 dark:bg-red-500/10 dark:text-red-400 dark:group-data-hover:bg-red-500/20',
9
+ orange:
10
+ 'bg-orange-500/15 text-orange-700 group-data-hover:bg-orange-500/25 dark:bg-orange-500/10 dark:text-orange-400 dark:group-data-hover:bg-orange-500/20',
11
+ amber:
12
+ 'bg-amber-400/20 text-amber-700 group-data-hover:bg-amber-400/30 dark:bg-amber-400/10 dark:text-amber-400 dark:group-data-hover:bg-amber-400/15',
13
+ yellow:
14
+ 'bg-yellow-400/20 text-yellow-700 group-data-hover:bg-yellow-400/30 dark:bg-yellow-400/10 dark:text-yellow-300 dark:group-data-hover:bg-yellow-400/15',
15
+ lime: 'bg-lime-400/20 text-lime-700 group-data-hover:bg-lime-400/30 dark:bg-lime-400/10 dark:text-lime-300 dark:group-data-hover:bg-lime-400/15',
16
+ green:
17
+ 'bg-green-500/15 text-green-700 group-data-hover:bg-green-500/25 dark:bg-green-500/10 dark:text-green-400 dark:group-data-hover:bg-green-500/20',
18
+ emerald:
19
+ 'bg-emerald-500/15 text-emerald-700 group-data-hover:bg-emerald-500/25 dark:bg-emerald-500/10 dark:text-emerald-400 dark:group-data-hover:bg-emerald-500/20',
20
+ teal: 'bg-teal-500/15 text-teal-700 group-data-hover:bg-teal-500/25 dark:bg-teal-500/10 dark:text-teal-300 dark:group-data-hover:bg-teal-500/20',
21
+ cyan: 'bg-cyan-400/20 text-cyan-700 group-data-hover:bg-cyan-400/30 dark:bg-cyan-400/10 dark:text-cyan-300 dark:group-data-hover:bg-cyan-400/15',
22
+ sky: 'bg-sky-500/15 text-sky-700 group-data-hover:bg-sky-500/25 dark:bg-sky-500/10 dark:text-sky-300 dark:group-data-hover:bg-sky-500/20',
23
+ blue: 'bg-blue-500/15 text-blue-700 group-data-hover:bg-blue-500/25 dark:text-blue-400 dark:group-data-hover:bg-blue-500/25',
24
+ indigo:
25
+ 'bg-indigo-500/15 text-indigo-700 group-data-hover:bg-indigo-500/25 dark:text-indigo-400 dark:group-data-hover:bg-indigo-500/20',
26
+ violet:
27
+ 'bg-violet-500/15 text-violet-700 group-data-hover:bg-violet-500/25 dark:text-violet-400 dark:group-data-hover:bg-violet-500/20',
28
+ purple:
29
+ 'bg-purple-500/15 text-purple-700 group-data-hover:bg-purple-500/25 dark:text-purple-400 dark:group-data-hover:bg-purple-500/20',
30
+ fuchsia:
31
+ 'bg-fuchsia-400/15 text-fuchsia-700 group-data-hover:bg-fuchsia-400/25 dark:bg-fuchsia-400/10 dark:text-fuchsia-400 dark:group-data-hover:bg-fuchsia-400/20',
32
+ pink: 'bg-pink-400/15 text-pink-700 group-data-hover:bg-pink-400/25 dark:bg-pink-400/10 dark:text-pink-400 dark:group-data-hover:bg-pink-400/20',
33
+ rose: 'bg-rose-400/15 text-rose-700 group-data-hover:bg-rose-400/25 dark:bg-rose-400/10 dark:text-rose-400 dark:group-data-hover:bg-rose-400/20',
34
+ zinc: 'bg-zinc-600/10 text-zinc-700 group-data-hover:bg-zinc-600/20 dark:bg-white/5 dark:text-zinc-400 dark:group-data-hover:bg-white/10',
35
+ }
36
+
37
+ type BadgeProps = { color?: keyof typeof colors }
38
+
39
+ export function Badge({ color = 'zinc', className, ...props }: BadgeProps & React.ComponentPropsWithoutRef<'span'>) {
40
+ return (
41
+ <span
42
+ {...props}
43
+ className={clsx(
44
+ className,
45
+ 'inline-flex items-center gap-x-1.5 rounded-md px-1.5 py-0.5 text-sm/5 font-medium sm:text-xs/5 forced-colors:outline',
46
+ colors[color]
47
+ )}
48
+ />
49
+ )
50
+ }
51
+
52
+ export const BadgeButton = forwardRef(function BadgeButton(
53
+ {
54
+ color = 'zinc',
55
+ className,
56
+ children,
57
+ ...props
58
+ }: BadgeProps & { className?: string; children: React.ReactNode } & (
59
+ | Omit<Headless.ButtonProps, 'as' | 'className'>
60
+ | Omit<React.ComponentPropsWithoutRef<typeof Link>, 'className'>
61
+ ),
62
+ ref: React.ForwardedRef<HTMLElement>
63
+ ) {
64
+ let classes = clsx(
65
+ className,
66
+ 'group relative inline-flex rounded-md focus:not-data-focus:outline-hidden data-focus:outline-2 data-focus:outline-offset-2 data-focus:outline-blue-500'
67
+ )
68
+
69
+ return 'href' in props ? (
70
+ <Link {...props} className={classes} ref={ref as React.ForwardedRef<HTMLAnchorElement>}>
71
+ <TouchTarget>
72
+ <Badge color={color}>{children}</Badge>
73
+ </TouchTarget>
74
+ </Link>
75
+ ) : (
76
+ <Headless.Button {...props} className={classes} ref={ref}>
77
+ <TouchTarget>
78
+ <Badge color={color}>{children}</Badge>
79
+ </TouchTarget>
80
+ </Headless.Button>
81
+ )
82
+ })
@@ -0,0 +1,204 @@
1
+ import * as Headless from '@headlessui/react'
2
+ import clsx from 'clsx'
3
+ import React, { forwardRef } from 'react'
4
+ import { Link } from './link'
5
+
6
+ const styles = {
7
+ base: [
8
+ // Base
9
+ 'relative isolate inline-flex items-baseline justify-center gap-x-2 rounded-lg border text-base/6 font-semibold',
10
+ // Sizing
11
+ 'px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)] sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)] sm:text-sm/6',
12
+ // Focus
13
+ 'focus:not-data-focus:outline-hidden data-focus:outline-2 data-focus:outline-offset-2 data-focus:outline-blue-500',
14
+ // Disabled
15
+ 'data-disabled:opacity-50',
16
+ // Icon
17
+ '*:data-[slot=icon]:-mx-0.5 *:data-[slot=icon]:my-0.5 *:data-[slot=icon]:size-5 *:data-[slot=icon]:shrink-0 *:data-[slot=icon]:self-center *:data-[slot=icon]:text-(--btn-icon) sm:*:data-[slot=icon]:my-1 sm:*:data-[slot=icon]:size-4 forced-colors:[--btn-icon:ButtonText] forced-colors:data-hover:[--btn-icon:ButtonText]',
18
+ ],
19
+ solid: [
20
+ // Optical border, implemented as the button background to avoid corner artifacts
21
+ 'border-transparent bg-(--btn-border)',
22
+ // Dark mode: border is rendered on `after` so background is set to button background
23
+ 'dark:bg-(--btn-bg)',
24
+ // Button background, implemented as foreground layer to stack on top of pseudo-border layer
25
+ 'before:absolute before:inset-0 before:-z-10 before:rounded-[calc(var(--radius-lg)-1px)] before:bg-(--btn-bg)',
26
+ // Drop shadow, applied to the inset `before` layer so it blends with the border
27
+ 'before:shadow-sm',
28
+ // Background color is moved to control and shadow is removed in dark mode so hide `before` pseudo
29
+ 'dark:before:hidden',
30
+ // Dark mode: Subtle white outline is applied using a border
31
+ 'dark:border-white/5',
32
+ // Shim/overlay, inset to match button foreground and used for hover state + highlight shadow
33
+ 'after:absolute after:inset-0 after:-z-10 after:rounded-[calc(var(--radius-lg)-1px)]',
34
+ // Inner highlight shadow
35
+ 'after:shadow-[inset_0_1px_--theme(--color-white/15%)]',
36
+ // White overlay on hover
37
+ 'data-active:after:bg-(--btn-hover-overlay) data-hover:after:bg-(--btn-hover-overlay)',
38
+ // Dark mode: `after` layer expands to cover entire button
39
+ 'dark:after:-inset-px dark:after:rounded-lg',
40
+ // Disabled
41
+ 'data-disabled:before:shadow-none data-disabled:after:shadow-none',
42
+ ],
43
+ outline: [
44
+ // Base
45
+ 'border-zinc-950/10 text-zinc-950 data-active:bg-zinc-950/2.5 data-hover:bg-zinc-950/2.5',
46
+ // Dark mode
47
+ 'dark:border-white/15 dark:text-white dark:[--btn-bg:transparent] dark:data-active:bg-white/5 dark:data-hover:bg-white/5',
48
+ // Icon
49
+ '[--btn-icon:var(--color-zinc-500)] data-active:[--btn-icon:var(--color-zinc-700)] data-hover:[--btn-icon:var(--color-zinc-700)] dark:data-active:[--btn-icon:var(--color-zinc-400)] dark:data-hover:[--btn-icon:var(--color-zinc-400)]',
50
+ ],
51
+ plain: [
52
+ // Base
53
+ 'border-transparent text-zinc-950 data-active:bg-zinc-950/5 data-hover:bg-zinc-950/5',
54
+ // Dark mode
55
+ 'dark:text-white dark:data-active:bg-white/10 dark:data-hover:bg-white/10',
56
+ // Icon
57
+ '[--btn-icon:var(--color-zinc-500)] data-active:[--btn-icon:var(--color-zinc-700)] data-hover:[--btn-icon:var(--color-zinc-700)] dark:[--btn-icon:var(--color-zinc-500)] dark:data-active:[--btn-icon:var(--color-zinc-400)] dark:data-hover:[--btn-icon:var(--color-zinc-400)]',
58
+ ],
59
+ colors: {
60
+ 'dark/zinc': [
61
+ 'text-white [--btn-bg:var(--color-zinc-900)] [--btn-border:var(--color-zinc-950)]/90 [--btn-hover-overlay:var(--color-white)]/10',
62
+ 'dark:text-white dark:[--btn-bg:var(--color-zinc-600)] dark:[--btn-hover-overlay:var(--color-white)]/5',
63
+ '[--btn-icon:var(--color-zinc-400)] data-active:[--btn-icon:var(--color-zinc-300)] data-hover:[--btn-icon:var(--color-zinc-300)]',
64
+ ],
65
+ light: [
66
+ 'text-zinc-950 [--btn-bg:white] [--btn-border:var(--color-zinc-950)]/10 [--btn-hover-overlay:var(--color-zinc-950)]/2.5 data-active:[--btn-border:var(--color-zinc-950)]/15 data-hover:[--btn-border:var(--color-zinc-950)]/15',
67
+ 'dark:text-white dark:[--btn-hover-overlay:var(--color-white)]/5 dark:[--btn-bg:var(--color-zinc-800)]',
68
+ '[--btn-icon:var(--color-zinc-500)] data-active:[--btn-icon:var(--color-zinc-700)] data-hover:[--btn-icon:var(--color-zinc-700)] dark:[--btn-icon:var(--color-zinc-500)] dark:data-active:[--btn-icon:var(--color-zinc-400)] dark:data-hover:[--btn-icon:var(--color-zinc-400)]',
69
+ ],
70
+ 'dark/white': [
71
+ 'text-white [--btn-bg:var(--color-zinc-900)] [--btn-border:var(--color-zinc-950)]/90 [--btn-hover-overlay:var(--color-white)]/10',
72
+ 'dark:text-zinc-950 dark:[--btn-bg:white] dark:[--btn-hover-overlay:var(--color-zinc-950)]/5',
73
+ '[--btn-icon:var(--color-zinc-400)] data-active:[--btn-icon:var(--color-zinc-300)] data-hover:[--btn-icon:var(--color-zinc-300)] dark:[--btn-icon:var(--color-zinc-500)] dark:data-active:[--btn-icon:var(--color-zinc-400)] dark:data-hover:[--btn-icon:var(--color-zinc-400)]',
74
+ ],
75
+ dark: [
76
+ 'text-white [--btn-bg:var(--color-zinc-900)] [--btn-border:var(--color-zinc-950)]/90 [--btn-hover-overlay:var(--color-white)]/10',
77
+ 'dark:[--btn-hover-overlay:var(--color-white)]/5 dark:[--btn-bg:var(--color-zinc-800)]',
78
+ '[--btn-icon:var(--color-zinc-400)] data-active:[--btn-icon:var(--color-zinc-300)] data-hover:[--btn-icon:var(--color-zinc-300)]',
79
+ ],
80
+ white: [
81
+ 'text-zinc-950 [--btn-bg:white] [--btn-border:var(--color-zinc-950)]/10 [--btn-hover-overlay:var(--color-zinc-950)]/2.5 data-active:[--btn-border:var(--color-zinc-950)]/15 data-hover:[--btn-border:var(--color-zinc-950)]/15',
82
+ 'dark:[--btn-hover-overlay:var(--color-zinc-950)]/5',
83
+ '[--btn-icon:var(--color-zinc-400)] data-active:[--btn-icon:var(--color-zinc-500)] data-hover:[--btn-icon:var(--color-zinc-500)]',
84
+ ],
85
+ zinc: [
86
+ 'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-zinc-600)] [--btn-border:var(--color-zinc-700)]/90',
87
+ 'dark:[--btn-hover-overlay:var(--color-white)]/5',
88
+ '[--btn-icon:var(--color-zinc-400)] data-active:[--btn-icon:var(--color-zinc-300)] data-hover:[--btn-icon:var(--color-zinc-300)]',
89
+ ],
90
+ indigo: [
91
+ 'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-indigo-500)] [--btn-border:var(--color-indigo-600)]/90',
92
+ '[--btn-icon:var(--color-indigo-300)] data-active:[--btn-icon:var(--color-indigo-200)] data-hover:[--btn-icon:var(--color-indigo-200)]',
93
+ ],
94
+ cyan: [
95
+ 'text-cyan-950 [--btn-bg:var(--color-cyan-300)] [--btn-border:var(--color-cyan-400)]/80 [--btn-hover-overlay:var(--color-white)]/25',
96
+ '[--btn-icon:var(--color-cyan-500)]',
97
+ ],
98
+ red: [
99
+ 'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-red-600)] [--btn-border:var(--color-red-700)]/90',
100
+ '[--btn-icon:var(--color-red-300)] data-active:[--btn-icon:var(--color-red-200)] data-hover:[--btn-icon:var(--color-red-200)]',
101
+ ],
102
+ orange: [
103
+ 'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-orange-500)] [--btn-border:var(--color-orange-600)]/90',
104
+ '[--btn-icon:var(--color-orange-300)] data-active:[--btn-icon:var(--color-orange-200)] data-hover:[--btn-icon:var(--color-orange-200)]',
105
+ ],
106
+ amber: [
107
+ 'text-amber-950 [--btn-hover-overlay:var(--color-white)]/25 [--btn-bg:var(--color-amber-400)] [--btn-border:var(--color-amber-500)]/80',
108
+ '[--btn-icon:var(--color-amber-600)]',
109
+ ],
110
+ yellow: [
111
+ 'text-yellow-950 [--btn-hover-overlay:var(--color-white)]/25 [--btn-bg:var(--color-yellow-300)] [--btn-border:var(--color-yellow-400)]/80',
112
+ '[--btn-icon:var(--color-yellow-600)] data-active:[--btn-icon:var(--color-yellow-700)] data-hover:[--btn-icon:var(--color-yellow-700)]',
113
+ ],
114
+ lime: [
115
+ 'text-lime-950 [--btn-hover-overlay:var(--color-white)]/25 [--btn-bg:var(--color-lime-300)] [--btn-border:var(--color-lime-400)]/80',
116
+ '[--btn-icon:var(--color-lime-600)] data-active:[--btn-icon:var(--color-lime-700)] data-hover:[--btn-icon:var(--color-lime-700)]',
117
+ ],
118
+ green: [
119
+ 'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-green-600)] [--btn-border:var(--color-green-700)]/90',
120
+ '[--btn-icon:var(--color-white)]/60 data-active:[--btn-icon:var(--color-white)]/80 data-hover:[--btn-icon:var(--color-white)]/80',
121
+ ],
122
+ emerald: [
123
+ 'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-emerald-600)] [--btn-border:var(--color-emerald-700)]/90',
124
+ '[--btn-icon:var(--color-white)]/60 data-active:[--btn-icon:var(--color-white)]/80 data-hover:[--btn-icon:var(--color-white)]/80',
125
+ ],
126
+ teal: [
127
+ 'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-teal-600)] [--btn-border:var(--color-teal-700)]/90',
128
+ '[--btn-icon:var(--color-white)]/60 data-active:[--btn-icon:var(--color-white)]/80 data-hover:[--btn-icon:var(--color-white)]/80',
129
+ ],
130
+ sky: [
131
+ 'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-sky-500)] [--btn-border:var(--color-sky-600)]/80',
132
+ '[--btn-icon:var(--color-white)]/60 data-active:[--btn-icon:var(--color-white)]/80 data-hover:[--btn-icon:var(--color-white)]/80',
133
+ ],
134
+ blue: [
135
+ 'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-blue-600)] [--btn-border:var(--color-blue-700)]/90',
136
+ '[--btn-icon:var(--color-blue-400)] data-active:[--btn-icon:var(--color-blue-300)] data-hover:[--btn-icon:var(--color-blue-300)]',
137
+ ],
138
+ violet: [
139
+ 'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-violet-500)] [--btn-border:var(--color-violet-600)]/90',
140
+ '[--btn-icon:var(--color-violet-300)] data-active:[--btn-icon:var(--color-violet-200)] data-hover:[--btn-icon:var(--color-violet-200)]',
141
+ ],
142
+ purple: [
143
+ 'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-purple-500)] [--btn-border:var(--color-purple-600)]/90',
144
+ '[--btn-icon:var(--color-purple-300)] data-active:[--btn-icon:var(--color-purple-200)] data-hover:[--btn-icon:var(--color-purple-200)]',
145
+ ],
146
+ fuchsia: [
147
+ 'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-fuchsia-500)] [--btn-border:var(--color-fuchsia-600)]/90',
148
+ '[--btn-icon:var(--color-fuchsia-300)] data-active:[--btn-icon:var(--color-fuchsia-200)] data-hover:[--btn-icon:var(--color-fuchsia-200)]',
149
+ ],
150
+ pink: [
151
+ 'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-pink-500)] [--btn-border:var(--color-pink-600)]/90',
152
+ '[--btn-icon:var(--color-pink-300)] data-active:[--btn-icon:var(--color-pink-200)] data-hover:[--btn-icon:var(--color-pink-200)]',
153
+ ],
154
+ rose: [
155
+ 'text-white [--btn-hover-overlay:var(--color-white)]/10 [--btn-bg:var(--color-rose-500)] [--btn-border:var(--color-rose-600)]/90',
156
+ '[--btn-icon:var(--color-rose-300)] data-active:[--btn-icon:var(--color-rose-200)] data-hover:[--btn-icon:var(--color-rose-200)]',
157
+ ],
158
+ },
159
+ }
160
+
161
+ type ButtonProps = (
162
+ | { color?: keyof typeof styles.colors; outline?: never; plain?: never }
163
+ | { color?: never; outline: true; plain?: never }
164
+ | { color?: never; outline?: never; plain: true }
165
+ ) & { className?: string; children: React.ReactNode } & (
166
+ | Omit<Headless.ButtonProps, 'as' | 'className'>
167
+ | Omit<React.ComponentPropsWithoutRef<typeof Link>, 'className'>
168
+ )
169
+
170
+ export const Button = forwardRef(function Button(
171
+ { color, outline, plain, className, children, ...props }: ButtonProps,
172
+ ref: React.ForwardedRef<HTMLElement>
173
+ ) {
174
+ let classes = clsx(
175
+ className,
176
+ styles.base,
177
+ outline ? styles.outline : plain ? styles.plain : clsx(styles.solid, styles.colors[color ?? 'dark/zinc'])
178
+ )
179
+
180
+ return 'href' in props ? (
181
+ <Link {...props} className={classes} ref={ref as React.ForwardedRef<HTMLAnchorElement>}>
182
+ <TouchTarget>{children}</TouchTarget>
183
+ </Link>
184
+ ) : (
185
+ <Headless.Button {...props} className={clsx(classes, 'cursor-default')} ref={ref}>
186
+ <TouchTarget>{children}</TouchTarget>
187
+ </Headless.Button>
188
+ )
189
+ })
190
+
191
+ /**
192
+ * Expand the hit area to at least 44×44px on touch devices
193
+ */
194
+ export function TouchTarget({ children }: { children: React.ReactNode }) {
195
+ return (
196
+ <>
197
+ <span
198
+ className="absolute top-1/2 left-1/2 size-[max(100%,2.75rem)] -translate-x-1/2 -translate-y-1/2 pointer-fine:hidden"
199
+ aria-hidden="true"
200
+ />
201
+ {children}
202
+ </>
203
+ )
204
+ }