sparkdesign 0.4.7 → 0.4.8
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/cli/registry/basic/alert-dialog.tsx +3 -6
- package/cli/registry/basic/button.tsx +19 -6
- package/cli/registry/basic/card.tsx +20 -8
- package/cli/registry/basic/collapsible-card.tsx +2 -4
- package/cli/registry/basic/combobox.tsx +104 -46
- package/cli/registry/basic/context-menu.tsx +2 -3
- package/cli/registry/basic/date-picker.tsx +78 -7
- package/cli/registry/basic/dialog.tsx +3 -8
- package/cli/registry/basic/drawer.tsx +3 -5
- package/cli/registry/basic/dropdown-menu.tsx +2 -3
- package/cli/registry/basic/hover-card.tsx +2 -3
- package/cli/registry/basic/icon-button.tsx +18 -11
- package/cli/registry/basic/input-group.tsx +4 -4
- package/cli/registry/basic/input.tsx +29 -13
- package/cli/registry/basic/popover.tsx +2 -3
- package/cli/registry/basic/select.tsx +24 -4
- package/cli/registry/basic/spinner.tsx +20 -5
- package/cli/registry/basic/textarea.tsx +30 -12
- package/cli/registry/basic/tooltip.tsx +2 -1
- package/cli/registry/meta.json +97 -30
- package/dist/registry/basic/alert-dialog.d.ts +1 -1
- package/dist/registry/basic/avatar.d.ts +1 -1
- package/dist/registry/basic/button.d.ts +3 -1
- package/dist/registry/basic/card.d.ts +9 -4
- package/dist/registry/basic/combobox.d.ts +20 -9
- package/dist/registry/basic/date-picker.d.ts +18 -9
- package/dist/registry/basic/dialog.d.ts +1 -1
- package/dist/registry/basic/icon-button.d.ts +2 -1
- package/dist/registry/basic/input-group.d.ts +5 -3
- package/dist/registry/basic/input.d.ts +8 -3
- package/dist/registry/basic/item.d.ts +2 -2
- package/dist/registry/basic/resizable.d.ts +48 -48
- package/dist/registry/basic/select.d.ts +7 -2
- package/dist/registry/basic/spinner.d.ts +6 -2
- package/dist/registry/basic/tag.d.ts +1 -1
- package/dist/registry/basic/textarea.d.ts +9 -3
- package/dist/registry/basic/toggle.d.ts +1 -1
- package/dist/scale/computed.css +11 -0
- package/dist/scale/config.css +11 -0
- package/dist/scale/presets/compact.css +7 -0
- package/dist/scale/presets/dense.css +7 -0
- package/dist/scale/presets/sharp.css +7 -0
- package/dist/scale/presets/soft.css +7 -0
- package/dist/spark-design.cjs.js +35 -35
- package/dist/spark-design.es.js +5151 -3767
- package/dist/sparkdesign.css +1 -1
- package/dist/src/components/index.d.ts +1 -1
- package/dist/src/lib/index.d.ts +1 -1
- package/dist/src/lib/motion.d.ts +79 -0
- package/dist/theme-base.css +22 -0
- package/dist/themes/dark-mint.css +6 -0
- package/dist/themes/dark-parchment.css +6 -0
- package/dist/themes/light-parchment.css +6 -0
- package/dist/tokens/scale/computed.css +11 -0
- package/dist/tokens/scale/config.css +11 -0
- package/dist/tokens/scale/presets/compact.css +7 -0
- package/dist/tokens/scale/presets/dense.css +7 -0
- package/dist/tokens/scale/presets/sharp.css +7 -0
- package/dist/tokens/scale/presets/soft.css +7 -0
- package/dist/tokens/theme-base.css +22 -0
- package/dist/tokens/themes/dark-mint.css +6 -0
- package/dist/tokens/themes/dark-parchment.css +6 -0
- package/dist/tokens/themes/light-parchment.css +6 -0
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ import { motion } from 'framer-motion'
|
|
|
3
3
|
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
|
|
4
4
|
import { cva, type VariantProps } from 'class-variance-authority'
|
|
5
5
|
import { cn } from '@/lib/utils'
|
|
6
|
+
import { MOTION_FADE, MOTION_SLIDE_UP } from '@/lib/motion'
|
|
6
7
|
import { getThemeFromDocument } from './theme-from-document'
|
|
7
8
|
|
|
8
9
|
const AlertDialog = AlertDialogPrimitive.Root
|
|
@@ -16,9 +17,7 @@ const AlertDialogOverlay = React.forwardRef<
|
|
|
16
17
|
<AlertDialogPrimitive.Overlay ref={ref} asChild {...props}>
|
|
17
18
|
<motion.div
|
|
18
19
|
className={cn('fixed inset-0 z-50 bg-bg-mask/60', className)}
|
|
19
|
-
|
|
20
|
-
animate={{ opacity: 1 }}
|
|
21
|
-
transition={{ duration: 0.15 }}
|
|
20
|
+
{...MOTION_FADE}
|
|
22
21
|
/>
|
|
23
22
|
</AlertDialogPrimitive.Overlay>
|
|
24
23
|
))
|
|
@@ -59,9 +58,7 @@ const AlertDialogContent = React.forwardRef<
|
|
|
59
58
|
<AlertDialogPrimitive.Content ref={ref} asChild {...props}>
|
|
60
59
|
<motion.div
|
|
61
60
|
className={cn(contentVariants({ size }), className)}
|
|
62
|
-
|
|
63
|
-
animate={{ opacity: 1, y: 0 }}
|
|
64
|
-
transition={{ duration: 0.2, ease: [0.4, 0, 0.2, 1] }}
|
|
61
|
+
{...MOTION_SLIDE_UP}
|
|
65
62
|
>
|
|
66
63
|
{children}
|
|
67
64
|
</motion.div>
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { forwardRef } from 'react'
|
|
2
2
|
import type { ButtonHTMLAttributes, ReactNode } from 'react'
|
|
3
|
+
import { Slot } from 'radix-ui'
|
|
3
4
|
import { cva, type VariantProps } from 'class-variance-authority'
|
|
5
|
+
import { cn } from '@/lib/utils'
|
|
6
|
+
import { Spinner } from './spinner'
|
|
4
7
|
|
|
5
8
|
const buttonVariants = cva(
|
|
6
|
-
'inline-flex items-center justify-center gap-1.5 font-medium transition-colors duration-200 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer',
|
|
9
|
+
'inline-flex items-center justify-center gap-1.5 font-medium transition-[colors,transform] duration-200 active:scale-[0.98] focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer',
|
|
7
10
|
{
|
|
8
11
|
variants: {
|
|
9
12
|
variant: {
|
|
@@ -13,6 +16,7 @@ const buttonVariants = cva(
|
|
|
13
16
|
outline: 'border border-border-tertiary bg-bg-container text-text hover:bg-fill-secondary',
|
|
14
17
|
ghost: 'bg-transparent text-text-secondary hover:bg-fill-secondary hover:text-text',
|
|
15
18
|
text: 'bg-transparent text-text-secondary hover:text-text',
|
|
19
|
+
destructive: 'bg-error text-text-on-primary hover:bg-error-hover',
|
|
16
20
|
},
|
|
17
21
|
size: {
|
|
18
22
|
sm: 'h-7 px-2 text-xs',
|
|
@@ -49,6 +53,8 @@ export interface ButtonProps
|
|
|
49
53
|
textButton?: boolean
|
|
50
54
|
prefixIcon?: ReactNode
|
|
51
55
|
suffixIcon?: ReactNode
|
|
56
|
+
loading?: boolean
|
|
57
|
+
asChild?: boolean
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
@@ -62,13 +68,17 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
62
68
|
suffixIcon,
|
|
63
69
|
rounded = 'square',
|
|
64
70
|
disabled = false,
|
|
71
|
+
loading = false,
|
|
72
|
+
asChild = false,
|
|
65
73
|
className,
|
|
66
74
|
...props
|
|
67
75
|
},
|
|
68
76
|
ref
|
|
69
77
|
) => {
|
|
78
|
+
const Comp = asChild ? Slot.Root : 'button'
|
|
79
|
+
const isDisabled = disabled || loading
|
|
70
80
|
return (
|
|
71
|
-
<
|
|
81
|
+
<Comp
|
|
72
82
|
ref={ref}
|
|
73
83
|
className={buttonVariants({
|
|
74
84
|
variant: textButton ? 'text' : variant,
|
|
@@ -77,21 +87,24 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
77
87
|
textOnly: textButton,
|
|
78
88
|
className,
|
|
79
89
|
})}
|
|
80
|
-
disabled={
|
|
90
|
+
disabled={isDisabled}
|
|
91
|
+
aria-busy={loading || undefined}
|
|
81
92
|
{...props}
|
|
82
93
|
>
|
|
83
|
-
{
|
|
94
|
+
{loading ? (
|
|
95
|
+
<Spinner className="shrink-0" />
|
|
96
|
+
) : prefixIcon ? (
|
|
84
97
|
<span className="inline-flex shrink-0 items-center justify-center [&>*]:block [&>*]:leading-none">
|
|
85
98
|
{prefixIcon}
|
|
86
99
|
</span>
|
|
87
|
-
)}
|
|
100
|
+
) : null}
|
|
88
101
|
{children}
|
|
89
102
|
{suffixIcon && (
|
|
90
103
|
<span className="inline-flex shrink-0 items-center justify-center [&>*]:block [&>*]:leading-none">
|
|
91
104
|
{suffixIcon}
|
|
92
105
|
</span>
|
|
93
106
|
)}
|
|
94
|
-
</
|
|
107
|
+
</Comp>
|
|
95
108
|
)
|
|
96
109
|
}
|
|
97
110
|
)
|
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
2
3
|
import { cn } from '@/lib/utils'
|
|
3
4
|
|
|
4
|
-
const
|
|
5
|
-
|
|
5
|
+
const cardVariants = cva('flex flex-col gap-4 rounded-xl text-text py-6', {
|
|
6
|
+
variants: {
|
|
7
|
+
variant: {
|
|
8
|
+
outline: 'border border-border-tertiary bg-bg-container shadow-sm transition-shadow hover:shadow-md',
|
|
9
|
+
filled: 'bg-fill-tertiary',
|
|
10
|
+
ghost: 'bg-transparent',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
defaultVariants: { variant: 'outline' },
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export interface CardProps
|
|
17
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
18
|
+
VariantProps<typeof cardVariants> {}
|
|
19
|
+
|
|
20
|
+
const Card = React.forwardRef<HTMLDivElement, CardProps>(
|
|
21
|
+
({ className, variant = 'outline', ...props }, ref) => (
|
|
6
22
|
<div
|
|
7
23
|
ref={ref}
|
|
8
|
-
className={cn(
|
|
9
|
-
'flex flex-col gap-4 rounded-xl border border-border-tertiary bg-bg-container text-text shadow-sm',
|
|
10
|
-
'py-6',
|
|
11
|
-
className,
|
|
12
|
-
)}
|
|
24
|
+
className={cn(cardVariants({ variant }), className)}
|
|
13
25
|
{...props}
|
|
14
26
|
/>
|
|
15
27
|
),
|
|
@@ -84,7 +96,6 @@ const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDiv
|
|
|
84
96
|
)
|
|
85
97
|
CardFooter.displayName = 'CardFooter'
|
|
86
98
|
|
|
87
|
-
export type CardProps = React.HTMLAttributes<HTMLDivElement>
|
|
88
99
|
export type CardHeaderProps = React.HTMLAttributes<HTMLDivElement>
|
|
89
100
|
export type CardTitleProps = React.HTMLAttributes<HTMLDivElement>
|
|
90
101
|
export type CardDescriptionProps = React.HTMLAttributes<HTMLDivElement>
|
|
@@ -100,4 +111,5 @@ export {
|
|
|
100
111
|
CardAction,
|
|
101
112
|
CardContent,
|
|
102
113
|
CardFooter,
|
|
114
|
+
cardVariants,
|
|
103
115
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useRef, useLayoutEffect, type ReactNode } from 'react'
|
|
2
2
|
import { motion, AnimatePresence } from 'framer-motion'
|
|
3
3
|
import { cn } from '@/lib/utils'
|
|
4
|
+
import { MOTION_EXPAND } from '@/lib/motion'
|
|
4
5
|
import { ArrowDownSLine } from './icons-inline'
|
|
5
6
|
|
|
6
7
|
export interface CollapsibleCardProps {
|
|
@@ -150,10 +151,7 @@ export function CollapsibleCard({
|
|
|
150
151
|
<AnimatePresence initial={false}>
|
|
151
152
|
{isExpanded && (
|
|
152
153
|
<motion.div
|
|
153
|
-
|
|
154
|
-
animate={{ height: 'auto', opacity: 1 }}
|
|
155
|
-
exit={{ height: 0, opacity: 0 }}
|
|
156
|
-
transition={{ duration: 0.2, ease: [0.2, 0.8, 0.2, 1] }}
|
|
154
|
+
{...MOTION_EXPAND}
|
|
157
155
|
className={cn('overflow-hidden', isAnimating && 'select-none')}
|
|
158
156
|
>
|
|
159
157
|
{/* 隐藏测量用,用于获取内容实际高度 */}
|
|
@@ -1,73 +1,131 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
|
+
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react'
|
|
3
|
+
|
|
2
4
|
import { cn } from '@/lib/utils'
|
|
3
|
-
import {
|
|
5
|
+
import { Button } from './button'
|
|
6
|
+
import {
|
|
7
|
+
Command,
|
|
8
|
+
CommandEmpty,
|
|
9
|
+
CommandGroup,
|
|
10
|
+
CommandInput,
|
|
11
|
+
CommandItem,
|
|
12
|
+
CommandList,
|
|
13
|
+
} from './command'
|
|
14
|
+
import { Popover, PopoverContent, PopoverTrigger } from './popover'
|
|
4
15
|
|
|
5
16
|
export interface ComboboxOption {
|
|
6
17
|
value: string
|
|
7
18
|
label: string
|
|
8
19
|
disabled?: boolean
|
|
20
|
+
keywords?: string[]
|
|
9
21
|
}
|
|
10
22
|
|
|
11
|
-
export interface ComboboxProps
|
|
23
|
+
export interface ComboboxProps {
|
|
12
24
|
options: ComboboxOption[]
|
|
13
25
|
value?: string
|
|
26
|
+
defaultValue?: string
|
|
14
27
|
onValueChange?: (value: string) => void
|
|
15
|
-
placeholder?:
|
|
16
|
-
|
|
28
|
+
placeholder?: React.ReactNode
|
|
29
|
+
searchPlaceholder?: string
|
|
30
|
+
emptyText?: React.ReactNode
|
|
31
|
+
disabled?: boolean
|
|
32
|
+
className?: string
|
|
33
|
+
contentClassName?: string
|
|
34
|
+
align?: 'start' | 'center' | 'end'
|
|
35
|
+
triggerClassName?: string
|
|
36
|
+
id?: string
|
|
37
|
+
name?: string
|
|
38
|
+
'aria-label'?: string
|
|
17
39
|
}
|
|
18
40
|
|
|
19
41
|
function Combobox({
|
|
20
42
|
options,
|
|
21
43
|
value,
|
|
44
|
+
defaultValue,
|
|
22
45
|
onValueChange,
|
|
23
|
-
placeholder = '
|
|
46
|
+
placeholder = 'Select an option…',
|
|
47
|
+
searchPlaceholder = 'Search…',
|
|
24
48
|
emptyText = 'No option found.',
|
|
49
|
+
disabled,
|
|
25
50
|
className,
|
|
26
|
-
|
|
51
|
+
contentClassName,
|
|
52
|
+
triggerClassName,
|
|
53
|
+
align = 'start',
|
|
54
|
+
id,
|
|
55
|
+
name,
|
|
56
|
+
'aria-label': ariaLabel,
|
|
27
57
|
}: ComboboxProps) {
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
58
|
+
const isControlled = value !== undefined
|
|
59
|
+
const [internal, setInternal] = React.useState<string | undefined>(defaultValue)
|
|
60
|
+
const [open, setOpen] = React.useState(false)
|
|
61
|
+
const current = isControlled ? value : internal
|
|
62
|
+
const selected = options.find((option) => option.value === current)
|
|
63
|
+
|
|
64
|
+
const handleSelect = React.useCallback(
|
|
65
|
+
(next: string) => {
|
|
66
|
+
const resolved = next === current ? '' : next
|
|
67
|
+
if (!isControlled) setInternal(resolved)
|
|
68
|
+
onValueChange?.(resolved)
|
|
69
|
+
setOpen(false)
|
|
70
|
+
},
|
|
71
|
+
[current, isControlled, onValueChange],
|
|
32
72
|
)
|
|
33
73
|
|
|
34
74
|
return (
|
|
35
|
-
<div className={cn('w-
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
>
|
|
64
|
-
{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
<div className={cn('w-60', className)}>
|
|
76
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
77
|
+
<PopoverTrigger asChild>
|
|
78
|
+
<Button
|
|
79
|
+
id={id}
|
|
80
|
+
variant="outline"
|
|
81
|
+
role="combobox"
|
|
82
|
+
aria-expanded={open}
|
|
83
|
+
aria-label={ariaLabel}
|
|
84
|
+
disabled={disabled}
|
|
85
|
+
className={cn(
|
|
86
|
+
'w-full justify-between font-normal',
|
|
87
|
+
!selected && 'text-text-tertiary',
|
|
88
|
+
triggerClassName,
|
|
89
|
+
)}
|
|
90
|
+
suffixIcon={<ChevronsUpDownIcon className="size-4 shrink-0 opacity-60" />}
|
|
91
|
+
>
|
|
92
|
+
<span className="truncate text-left">
|
|
93
|
+
{selected ? selected.label : placeholder}
|
|
94
|
+
</span>
|
|
95
|
+
</Button>
|
|
96
|
+
</PopoverTrigger>
|
|
97
|
+
<PopoverContent
|
|
98
|
+
align={align}
|
|
99
|
+
className={cn('w-[var(--radix-popover-trigger-width)] min-w-56 p-0', contentClassName)}
|
|
100
|
+
>
|
|
101
|
+
<Command>
|
|
102
|
+
<CommandInput placeholder={searchPlaceholder} />
|
|
103
|
+
<CommandList>
|
|
104
|
+
<CommandEmpty>{emptyText}</CommandEmpty>
|
|
105
|
+
<CommandGroup>
|
|
106
|
+
{options.map((option) => (
|
|
107
|
+
<CommandItem
|
|
108
|
+
key={option.value}
|
|
109
|
+
value={option.value}
|
|
110
|
+
keywords={[option.label, ...(option.keywords ?? [])]}
|
|
111
|
+
disabled={option.disabled}
|
|
112
|
+
onSelect={handleSelect}
|
|
113
|
+
>
|
|
114
|
+
<CheckIcon
|
|
115
|
+
className={cn(
|
|
116
|
+
'mr-2 size-4',
|
|
117
|
+
option.value === current ? 'opacity-100' : 'opacity-0',
|
|
118
|
+
)}
|
|
119
|
+
/>
|
|
120
|
+
<span className="truncate">{option.label}</span>
|
|
121
|
+
</CommandItem>
|
|
122
|
+
))}
|
|
123
|
+
</CommandGroup>
|
|
124
|
+
</CommandList>
|
|
125
|
+
</Command>
|
|
126
|
+
</PopoverContent>
|
|
127
|
+
</Popover>
|
|
128
|
+
{name ? <input type="hidden" name={name} value={current ?? ''} /> : null}
|
|
71
129
|
</div>
|
|
72
130
|
)
|
|
73
131
|
}
|
|
@@ -3,6 +3,7 @@ import { motion } from 'framer-motion'
|
|
|
3
3
|
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu'
|
|
4
4
|
import { cva, type VariantProps } from 'class-variance-authority'
|
|
5
5
|
import { cn } from '@/lib/utils'
|
|
6
|
+
import { MOTION_SCALE_IN } from '@/lib/motion'
|
|
6
7
|
import { ArrowRightLine, CheckLine } from './icons-inline'
|
|
7
8
|
import { getThemeFromDocument } from './theme-from-document'
|
|
8
9
|
|
|
@@ -40,9 +41,7 @@ const ContextMenuContent = React.forwardRef<
|
|
|
40
41
|
{...props}
|
|
41
42
|
>
|
|
42
43
|
<motion.div
|
|
43
|
-
|
|
44
|
-
animate={{ opacity: 1 }}
|
|
45
|
-
transition={{ duration: 0.12, ease: [0.4, 0, 0.2, 1] }}
|
|
44
|
+
{...MOTION_SCALE_IN}
|
|
46
45
|
>
|
|
47
46
|
{children}
|
|
48
47
|
</motion.div>
|
|
@@ -1,13 +1,84 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { format } from 'date-fns'
|
|
3
|
+
import { CalendarIcon } from 'lucide-react'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
import { cn } from '@/lib/utils'
|
|
6
|
+
import { Button } from './button'
|
|
7
|
+
import { Calendar } from './calendar'
|
|
8
|
+
import { Popover, PopoverContent, PopoverTrigger } from './popover'
|
|
9
|
+
|
|
10
|
+
export interface DatePickerProps {
|
|
11
|
+
value?: Date
|
|
12
|
+
defaultValue?: Date
|
|
13
|
+
onChange?: (date: Date | undefined) => void
|
|
14
|
+
placeholder?: React.ReactNode
|
|
15
|
+
disabled?: boolean
|
|
16
|
+
className?: string
|
|
17
|
+
align?: 'start' | 'center' | 'end'
|
|
18
|
+
formatString?: string
|
|
19
|
+
id?: string
|
|
20
|
+
name?: string
|
|
21
|
+
'aria-label'?: string
|
|
6
22
|
}
|
|
7
23
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
24
|
+
function DatePicker({
|
|
25
|
+
value,
|
|
26
|
+
defaultValue,
|
|
27
|
+
onChange,
|
|
28
|
+
placeholder = 'Pick a date',
|
|
29
|
+
disabled,
|
|
30
|
+
className,
|
|
31
|
+
align = 'start',
|
|
32
|
+
formatString = 'PPP',
|
|
33
|
+
id,
|
|
34
|
+
name,
|
|
35
|
+
'aria-label': ariaLabel,
|
|
36
|
+
}: DatePickerProps) {
|
|
37
|
+
const isControlled = value !== undefined
|
|
38
|
+
const [internal, setInternal] = React.useState<Date | undefined>(defaultValue)
|
|
39
|
+
const [open, setOpen] = React.useState(false)
|
|
40
|
+
const selected = isControlled ? value : internal
|
|
41
|
+
|
|
42
|
+
const handleSelect = React.useCallback(
|
|
43
|
+
(next: Date | undefined) => {
|
|
44
|
+
if (!isControlled) setInternal(next)
|
|
45
|
+
onChange?.(next)
|
|
46
|
+
if (next) setOpen(false)
|
|
47
|
+
},
|
|
48
|
+
[isControlled, onChange],
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
53
|
+
<PopoverTrigger asChild>
|
|
54
|
+
<Button
|
|
55
|
+
id={id}
|
|
56
|
+
variant="outline"
|
|
57
|
+
disabled={disabled}
|
|
58
|
+
aria-label={ariaLabel ?? (typeof placeholder === 'string' ? placeholder : 'Pick a date')}
|
|
59
|
+
className={cn(
|
|
60
|
+
'w-60 justify-start text-left font-normal',
|
|
61
|
+
!selected && 'text-text-tertiary',
|
|
62
|
+
className,
|
|
63
|
+
)}
|
|
64
|
+
suffixIcon={<CalendarIcon className="ml-auto size-4 opacity-60" />}
|
|
65
|
+
>
|
|
66
|
+
{selected ? format(selected, formatString) : <span>{placeholder}</span>}
|
|
67
|
+
</Button>
|
|
68
|
+
</PopoverTrigger>
|
|
69
|
+
<PopoverContent className="w-auto p-0" align={align}>
|
|
70
|
+
<Calendar
|
|
71
|
+
mode="single"
|
|
72
|
+
selected={selected}
|
|
73
|
+
onSelect={handleSelect}
|
|
74
|
+
autoFocus
|
|
75
|
+
/>
|
|
76
|
+
{name ? (
|
|
77
|
+
<input type="hidden" name={name} value={selected ? selected.toISOString() : ''} />
|
|
78
|
+
) : null}
|
|
79
|
+
</PopoverContent>
|
|
80
|
+
</Popover>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
12
83
|
|
|
13
84
|
export { DatePicker }
|
|
@@ -3,6 +3,7 @@ import { motion } from 'framer-motion'
|
|
|
3
3
|
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
|
4
4
|
import { cva, type VariantProps } from 'class-variance-authority'
|
|
5
5
|
import { cn } from '@/lib/utils'
|
|
6
|
+
import { MOTION_FADE, MOTION_SLIDE_UP } from '@/lib/motion'
|
|
6
7
|
import { CloseLine } from './icons-inline'
|
|
7
8
|
import { getThemeFromDocument } from './theme-from-document'
|
|
8
9
|
|
|
@@ -18,10 +19,7 @@ const DialogOverlay = React.forwardRef<
|
|
|
18
19
|
<DialogPrimitive.Overlay ref={ref} asChild {...props}>
|
|
19
20
|
<motion.div
|
|
20
21
|
className={cn('fixed inset-0 z-50 bg-bg-mask/60', className)}
|
|
21
|
-
|
|
22
|
-
animate={{ opacity: 1 }}
|
|
23
|
-
exit={{ opacity: 0 }}
|
|
24
|
-
transition={{ duration: 0.15 }}
|
|
22
|
+
{...MOTION_FADE}
|
|
25
23
|
/>
|
|
26
24
|
</DialogPrimitive.Overlay>
|
|
27
25
|
))
|
|
@@ -81,10 +79,7 @@ const DialogContent = React.forwardRef<
|
|
|
81
79
|
<DialogPrimitive.Content ref={ref} asChild {...props}>
|
|
82
80
|
<motion.div
|
|
83
81
|
className={cn(contentVariants({ size }), className)}
|
|
84
|
-
|
|
85
|
-
animate={{ opacity: 1, y: 0 }}
|
|
86
|
-
exit={{ opacity: 0, y: 8 }}
|
|
87
|
-
transition={{ duration: 0.2, ease: [0.4, 0, 0.2, 1] }}
|
|
82
|
+
{...MOTION_SLIDE_UP}
|
|
88
83
|
>
|
|
89
84
|
{children}
|
|
90
85
|
{!hideCloseButton && (
|
|
@@ -3,6 +3,7 @@ import { motion } from 'framer-motion'
|
|
|
3
3
|
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
|
4
4
|
import { cva, type VariantProps } from 'class-variance-authority'
|
|
5
5
|
import { cn } from '@/lib/utils'
|
|
6
|
+
import { MOTION_FADE, MOTION_DURATION, MOTION_EASE } from '@/lib/motion'
|
|
6
7
|
import { CloseLine } from './icons-inline'
|
|
7
8
|
import { getThemeFromDocument } from './theme-from-document'
|
|
8
9
|
|
|
@@ -18,10 +19,7 @@ const DrawerOverlay = React.forwardRef<
|
|
|
18
19
|
<DialogPrimitive.Overlay ref={ref} asChild {...props}>
|
|
19
20
|
<motion.div
|
|
20
21
|
className={cn('fixed inset-0 z-50 bg-bg-mask/60', className)}
|
|
21
|
-
|
|
22
|
-
animate={{ opacity: 1 }}
|
|
23
|
-
exit={{ opacity: 0 }}
|
|
24
|
-
transition={{ duration: 0.15 }}
|
|
22
|
+
{...MOTION_FADE}
|
|
25
23
|
/>
|
|
26
24
|
</DialogPrimitive.Overlay>
|
|
27
25
|
))
|
|
@@ -83,7 +81,7 @@ const DrawerContent = React.forwardRef<
|
|
|
83
81
|
initial={motionProps.initial}
|
|
84
82
|
animate={motionProps.animate}
|
|
85
83
|
exit={motionProps.exit}
|
|
86
|
-
transition={{ duration:
|
|
84
|
+
transition={{ duration: MOTION_DURATION.slow, ease: MOTION_EASE.standard }}
|
|
87
85
|
>
|
|
88
86
|
{children}
|
|
89
87
|
{showCloseButton && (
|
|
@@ -3,6 +3,7 @@ import { motion } from 'framer-motion'
|
|
|
3
3
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
|
|
4
4
|
import { cva, type VariantProps } from 'class-variance-authority'
|
|
5
5
|
import { cn } from '@/lib/utils'
|
|
6
|
+
import { MOTION_SCALE_IN } from '@/lib/motion'
|
|
6
7
|
import { ArrowRightLine, CheckLine } from './icons-inline'
|
|
7
8
|
import { getThemeFromDocument } from './theme-from-document'
|
|
8
9
|
|
|
@@ -42,9 +43,7 @@ const DropdownMenuContent = React.forwardRef<
|
|
|
42
43
|
{...props}
|
|
43
44
|
>
|
|
44
45
|
<motion.div
|
|
45
|
-
|
|
46
|
-
animate={{ opacity: 1 }}
|
|
47
|
-
transition={{ duration: 0.15, ease: [0.4, 0, 0.2, 1] }}
|
|
46
|
+
{...MOTION_SCALE_IN}
|
|
48
47
|
>
|
|
49
48
|
{children}
|
|
50
49
|
</motion.div>
|
|
@@ -2,6 +2,7 @@ import * as React from 'react'
|
|
|
2
2
|
import { motion } from 'framer-motion'
|
|
3
3
|
import * as HoverCardPrimitive from '@radix-ui/react-hover-card'
|
|
4
4
|
import { cn } from '@/lib/utils'
|
|
5
|
+
import { MOTION_SCALE_IN } from '@/lib/motion'
|
|
5
6
|
import { getThemeFromDocument } from './theme-from-document'
|
|
6
7
|
|
|
7
8
|
const HoverCard = HoverCardPrimitive.Root
|
|
@@ -41,9 +42,7 @@ const HoverCardContent = React.forwardRef<
|
|
|
41
42
|
{...props}
|
|
42
43
|
>
|
|
43
44
|
<motion.div
|
|
44
|
-
|
|
45
|
-
animate={{ opacity: 1, y: 0 }}
|
|
46
|
-
transition={{ duration: 0.15, ease: [0.4, 0, 0.2, 1] }}
|
|
45
|
+
{...MOTION_SCALE_IN}
|
|
47
46
|
>
|
|
48
47
|
{children}
|
|
49
48
|
</motion.div>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { forwardRef } from 'react'
|
|
2
2
|
import type { ButtonHTMLAttributes, ReactNode } from 'react'
|
|
3
|
+
import { Slot } from 'radix-ui'
|
|
3
4
|
import { cva, type VariantProps } from 'class-variance-authority'
|
|
4
5
|
import { cn } from '@/lib/utils'
|
|
5
6
|
|
|
6
7
|
const iconButtonVariants = cva(
|
|
7
|
-
'flex-none shrink-0 inline-flex items-center justify-center box-border transition-colors duration-200 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer [&>*]:flex [&>*]:items-center [&>*]:justify-center [&>*]:size-full [&>*]:[&>svg]:block [&>*]:[&>svg]:leading-none',
|
|
8
|
+
'flex-none shrink-0 inline-flex items-center justify-center box-border transition-[colors,transform] duration-200 active:scale-[0.98] focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer [&>*]:flex [&>*]:items-center [&>*]:justify-center [&>*]:size-full [&>*]:[&>svg]:block [&>*]:[&>svg]:leading-none',
|
|
8
9
|
{
|
|
9
10
|
variants: {
|
|
10
11
|
variant: {
|
|
@@ -13,6 +14,7 @@ const iconButtonVariants = cva(
|
|
|
13
14
|
tertiary: 'bg-fill-secondary text-text hover:bg-fill',
|
|
14
15
|
ghost: 'bg-transparent text-text-secondary hover:bg-fill-secondary hover:text-text',
|
|
15
16
|
iconOnly: 'bg-transparent text-text-secondary hover:text-text',
|
|
17
|
+
destructive: 'bg-error text-text-on-primary hover:bg-error-hover',
|
|
16
18
|
},
|
|
17
19
|
rounded: {
|
|
18
20
|
square: 'rounded',
|
|
@@ -36,6 +38,7 @@ export interface IconButtonProps
|
|
|
36
38
|
extends ButtonHTMLAttributes<HTMLButtonElement>,
|
|
37
39
|
VariantProps<typeof iconButtonVariants> {
|
|
38
40
|
icon: ReactNode
|
|
41
|
+
asChild?: boolean
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
|
|
@@ -46,20 +49,24 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
|
|
|
46
49
|
rounded = 'square',
|
|
47
50
|
icon,
|
|
48
51
|
disabled = false,
|
|
52
|
+
asChild = false,
|
|
49
53
|
className,
|
|
50
54
|
...props
|
|
51
55
|
},
|
|
52
56
|
ref
|
|
53
|
-
) =>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
) => {
|
|
58
|
+
const Comp = asChild ? Slot.Root : 'button'
|
|
59
|
+
return (
|
|
60
|
+
<Comp
|
|
61
|
+
ref={ref}
|
|
62
|
+
className={cn(iconButtonVariants({ variant, size, rounded }), className)}
|
|
63
|
+
disabled={disabled}
|
|
64
|
+
{...props}
|
|
65
|
+
>
|
|
66
|
+
<span className="inline-flex shrink-0 size-full items-center justify-center">{icon}</span>
|
|
67
|
+
</Comp>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
63
70
|
)
|
|
64
71
|
IconButton.displayName = 'IconButton'
|
|
65
72
|
|
|
@@ -3,8 +3,8 @@ import { cva, type VariantProps } from "class-variance-authority"
|
|
|
3
3
|
|
|
4
4
|
import { cn } from "@/lib/utils"
|
|
5
5
|
import { Button } from "./button"
|
|
6
|
-
import { Input } from "./input"
|
|
7
|
-
import { Textarea } from "./textarea"
|
|
6
|
+
import { Input, type InputProps } from "./input"
|
|
7
|
+
import { Textarea, type TextareaProps } from "./textarea"
|
|
8
8
|
|
|
9
9
|
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
10
10
|
return (
|
|
@@ -129,7 +129,7 @@ function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
|
|
|
129
129
|
function InputGroupInput({
|
|
130
130
|
className,
|
|
131
131
|
...props
|
|
132
|
-
}:
|
|
132
|
+
}: InputProps) {
|
|
133
133
|
return (
|
|
134
134
|
<Input
|
|
135
135
|
data-slot="input-group-control"
|
|
@@ -145,7 +145,7 @@ function InputGroupInput({
|
|
|
145
145
|
function InputGroupTextarea({
|
|
146
146
|
className,
|
|
147
147
|
...props
|
|
148
|
-
}:
|
|
148
|
+
}: TextareaProps) {
|
|
149
149
|
return (
|
|
150
150
|
<Textarea
|
|
151
151
|
data-slot="input-group-control"
|