sparkdesign 0.4.7 → 0.4.9
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/AGENTS.md +1 -1
- package/cli/registry/agent-manifest.json +3996 -67
- package/cli/registry/basic/accordion.tsx +79 -0
- package/cli/registry/basic/alert-dialog.tsx +3 -6
- package/cli/registry/basic/badge.tsx +49 -0
- package/cli/registry/basic/button.tsx +32 -14
- package/cli/registry/basic/card.tsx +20 -8
- package/cli/registry/basic/collapsible-card.tsx +12 -5
- 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/ellipsis-text.tsx +151 -0
- package/cli/registry/basic/form.tsx +186 -0
- package/cli/registry/basic/hover-card.tsx +2 -3
- package/cli/registry/basic/icon-button.tsx +29 -14
- 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/sidebar.tsx +665 -0
- package/cli/registry/basic/sonner.tsx +10 -10
- 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/chat/chat-input/compound.tsx +1 -0
- package/cli/registry/chat/user-question/compound.tsx +2 -0
- package/cli/registry/meta.json +250 -30
- package/dist/registry/basic/accordion.d.ts +15 -0
- package/dist/registry/basic/alert-dialog.d.ts +1 -1
- package/dist/registry/basic/avatar.d.ts +1 -1
- package/dist/registry/basic/badge.d.ts +23 -0
- 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/ellipsis-text.d.ts +45 -0
- package/dist/registry/basic/form.d.ts +23 -0
- package/dist/registry/basic/icon-button.d.ts +17 -3
- 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 +3 -3
- package/dist/registry/basic/resizable.d.ts +48 -48
- package/dist/registry/basic/select.d.ts +7 -2
- package/dist/registry/basic/sidebar.d.ts +72 -0
- 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 +34 -37
- package/dist/spark-design.es.js +7200 -4933
- package/dist/sparkdesign.css +1 -1
- package/dist/src/components/basic/Accordion/index.d.ts +13 -0
- package/dist/src/components/basic/Badge/index.d.ts +13 -0
- package/dist/src/components/basic/EllipsisText/index.d.ts +4 -36
- package/dist/src/components/basic/Form/index.d.ts +12 -0
- package/dist/src/components/basic/Sidebar/index.d.ts +13 -0
- package/dist/src/components/index.d.ts +7 -3
- 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/docs/agent/component-selection.md +106 -4
- package/package.json +8 -3
- package/registry/agent-manifest.json +3996 -67
- package/cli/registry/chat/user-question/UserQuestionCard.tsx +0 -198
- package/cli/registry/chat/user-question/UserQuestionFooter.tsx +0 -66
- package/cli/registry/chat/user-question/UserQuestionHeader.tsx +0 -64
- package/cli/registry/chat/user-question/useUserQuestionState.ts +0 -165
- package/dist/registry/chat/user-question/UserQuestionCard.d.ts +0 -36
- package/dist/registry/chat/user-question/UserQuestionFooter.d.ts +0 -24
- package/dist/registry/chat/user-question/UserQuestionHeader.d.ts +0 -26
- package/dist/registry/chat/user-question/useUserQuestionState.d.ts +0 -26
- package/dist/src/components/basic/CollapsibleSection/index.d.ts +0 -43
- package/dist/src/components/chat/Response/StreamingMarkdownBlock.d.ts +0 -12
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
import { ArrowDownSLine } from "./icons-inline"
|
|
8
|
+
|
|
9
|
+
const Accordion = AccordionPrimitive.Root
|
|
10
|
+
|
|
11
|
+
const AccordionItem = React.forwardRef<
|
|
12
|
+
React.ElementRef<typeof AccordionPrimitive.Item>,
|
|
13
|
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
|
14
|
+
>(({ className, ...props }, ref) => (
|
|
15
|
+
<AccordionPrimitive.Item
|
|
16
|
+
ref={ref}
|
|
17
|
+
data-slot="accordion-item"
|
|
18
|
+
className={cn("border-b border-border-tertiary last:border-b-0", className)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
))
|
|
22
|
+
AccordionItem.displayName = "AccordionItem"
|
|
23
|
+
|
|
24
|
+
const AccordionTrigger = React.forwardRef<
|
|
25
|
+
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
|
26
|
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger> & {
|
|
27
|
+
chevronIcon?: React.ReactNode
|
|
28
|
+
}
|
|
29
|
+
>(({ className, children, chevronIcon, ...props }, ref) => (
|
|
30
|
+
<AccordionPrimitive.Header className="flex">
|
|
31
|
+
<AccordionPrimitive.Trigger
|
|
32
|
+
ref={ref}
|
|
33
|
+
data-slot="accordion-trigger"
|
|
34
|
+
className={cn(
|
|
35
|
+
"group flex flex-1 items-center justify-between gap-2 py-4 text-left text-sm font-medium text-text transition-all outline-none hover:text-text-secondary",
|
|
36
|
+
"focus-visible:ring-2 focus-visible:ring-primary-border focus-visible:ring-offset-2 focus-visible:ring-offset-bg-base",
|
|
37
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
38
|
+
className
|
|
39
|
+
)}
|
|
40
|
+
{...props}
|
|
41
|
+
>
|
|
42
|
+
{children}
|
|
43
|
+
<span
|
|
44
|
+
className="inline-flex shrink-0 transition-transform duration-200 group-data-[state=open]:rotate-180"
|
|
45
|
+
aria-hidden
|
|
46
|
+
>
|
|
47
|
+
{chevronIcon ?? <ArrowDownSLine className="size-4 text-text-secondary" />}
|
|
48
|
+
</span>
|
|
49
|
+
</AccordionPrimitive.Trigger>
|
|
50
|
+
</AccordionPrimitive.Header>
|
|
51
|
+
))
|
|
52
|
+
AccordionTrigger.displayName = "AccordionTrigger"
|
|
53
|
+
|
|
54
|
+
const AccordionContent = React.forwardRef<
|
|
55
|
+
React.ElementRef<typeof AccordionPrimitive.Content>,
|
|
56
|
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
|
57
|
+
>(({ className, children, ...props }, ref) => (
|
|
58
|
+
<AccordionPrimitive.Content
|
|
59
|
+
ref={ref}
|
|
60
|
+
data-slot="accordion-content"
|
|
61
|
+
className={cn(
|
|
62
|
+
"overflow-hidden text-sm text-text-secondary data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
|
|
63
|
+
className
|
|
64
|
+
)}
|
|
65
|
+
{...props}
|
|
66
|
+
>
|
|
67
|
+
<div className="pb-4 pt-0">{children}</div>
|
|
68
|
+
</AccordionPrimitive.Content>
|
|
69
|
+
))
|
|
70
|
+
AccordionContent.displayName = "AccordionContent"
|
|
71
|
+
|
|
72
|
+
export type AccordionProps = React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Root>
|
|
73
|
+
export type AccordionItemProps = React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
|
74
|
+
export type AccordionTriggerProps = React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger> & {
|
|
75
|
+
chevronIcon?: React.ReactNode
|
|
76
|
+
}
|
|
77
|
+
export type AccordionContentProps = React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
|
78
|
+
|
|
79
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
|
@@ -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>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot"
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
const badgeVariants = cva(
|
|
8
|
+
"inline-flex items-center justify-center gap-1 rounded-md border px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-border focus-visible:ring-offset-2 focus-visible:ring-offset-bg-base [&>svg]:size-3 [&>svg]:pointer-events-none",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: "border-transparent bg-primary text-text-on-primary hover:bg-primary-hover",
|
|
13
|
+
secondary: "border-transparent bg-fill-secondary text-text hover:bg-fill",
|
|
14
|
+
destructive: "border-transparent bg-error text-text-on-primary hover:bg-error-hover",
|
|
15
|
+
outline: "border-border-tertiary bg-transparent text-text",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
defaultVariants: {
|
|
19
|
+
variant: "default",
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
function Badge({
|
|
25
|
+
className,
|
|
26
|
+
variant,
|
|
27
|
+
asChild = false,
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentProps<"span"> &
|
|
30
|
+
VariantProps<typeof badgeVariants> & {
|
|
31
|
+
asChild?: boolean
|
|
32
|
+
}) {
|
|
33
|
+
const Comp = asChild ? Slot : "span"
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Comp
|
|
37
|
+
data-slot="badge"
|
|
38
|
+
className={cn(badgeVariants({ variant }), className)}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type BadgeProps = React.ComponentProps<"span"> &
|
|
45
|
+
VariantProps<typeof badgeVariants> & {
|
|
46
|
+
asChild?: boolean
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { Badge, badgeVariants }
|
|
@@ -1,9 +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'
|
|
5
|
+
import { Spinner } from './spinner'
|
|
4
6
|
|
|
5
7
|
const buttonVariants = cva(
|
|
6
|
-
'inline-flex items-center justify-center gap-1.5 font-medium transition-
|
|
8
|
+
'inline-flex items-center justify-center gap-1.5 font-medium transition-[color,background-color,border-color,box-shadow,transform] duration-200 active:scale-[0.98] focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer',
|
|
7
9
|
{
|
|
8
10
|
variants: {
|
|
9
11
|
variant: {
|
|
@@ -13,6 +15,7 @@ const buttonVariants = cva(
|
|
|
13
15
|
outline: 'border border-border-tertiary bg-bg-container text-text hover:bg-fill-secondary',
|
|
14
16
|
ghost: 'bg-transparent text-text-secondary hover:bg-fill-secondary hover:text-text',
|
|
15
17
|
text: 'bg-transparent text-text-secondary hover:text-text',
|
|
18
|
+
destructive: 'bg-error text-text-on-primary hover:bg-error-hover',
|
|
16
19
|
},
|
|
17
20
|
size: {
|
|
18
21
|
sm: 'h-7 px-2 text-xs',
|
|
@@ -49,6 +52,8 @@ export interface ButtonProps
|
|
|
49
52
|
textButton?: boolean
|
|
50
53
|
prefixIcon?: ReactNode
|
|
51
54
|
suffixIcon?: ReactNode
|
|
55
|
+
loading?: boolean
|
|
56
|
+
asChild?: boolean
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
@@ -62,13 +67,17 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
62
67
|
suffixIcon,
|
|
63
68
|
rounded = 'square',
|
|
64
69
|
disabled = false,
|
|
70
|
+
loading = false,
|
|
71
|
+
asChild = false,
|
|
65
72
|
className,
|
|
66
73
|
...props
|
|
67
74
|
},
|
|
68
75
|
ref
|
|
69
76
|
) => {
|
|
77
|
+
const Comp = asChild ? Slot.Root : 'button'
|
|
78
|
+
const isDisabled = disabled || loading
|
|
70
79
|
return (
|
|
71
|
-
<
|
|
80
|
+
<Comp
|
|
72
81
|
ref={ref}
|
|
73
82
|
className={buttonVariants({
|
|
74
83
|
variant: textButton ? 'text' : variant,
|
|
@@ -77,21 +86,30 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
77
86
|
textOnly: textButton,
|
|
78
87
|
className,
|
|
79
88
|
})}
|
|
80
|
-
disabled={
|
|
89
|
+
disabled={isDisabled}
|
|
90
|
+
aria-busy={loading || undefined}
|
|
81
91
|
{...props}
|
|
82
92
|
>
|
|
83
|
-
{
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
{asChild ? (
|
|
94
|
+
children
|
|
95
|
+
) : (
|
|
96
|
+
<>
|
|
97
|
+
{loading ? (
|
|
98
|
+
<Spinner className="shrink-0" />
|
|
99
|
+
) : prefixIcon ? (
|
|
100
|
+
<span className="inline-flex shrink-0 items-center justify-center [&>*]:block [&>*]:leading-none">
|
|
101
|
+
{prefixIcon}
|
|
102
|
+
</span>
|
|
103
|
+
) : null}
|
|
104
|
+
{children}
|
|
105
|
+
{suffixIcon && (
|
|
106
|
+
<span className="inline-flex shrink-0 items-center justify-center [&>*]:block [&>*]:leading-none">
|
|
107
|
+
{suffixIcon}
|
|
108
|
+
</span>
|
|
109
|
+
)}
|
|
110
|
+
</>
|
|
87
111
|
)}
|
|
88
|
-
|
|
89
|
-
{suffixIcon && (
|
|
90
|
-
<span className="inline-flex shrink-0 items-center justify-center [&>*]:block [&>*]:leading-none">
|
|
91
|
-
{suffixIcon}
|
|
92
|
-
</span>
|
|
93
|
-
)}
|
|
94
|
-
</button>
|
|
112
|
+
</Comp>
|
|
95
113
|
)
|
|
96
114
|
}
|
|
97
115
|
)
|
|
@@ -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
|
-
import { useState, useRef, useLayoutEffect, type ReactNode } from 'react'
|
|
1
|
+
import { useState, useRef, useLayoutEffect, type KeyboardEvent, 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 {
|
|
@@ -88,6 +89,13 @@ export function CollapsibleCard({
|
|
|
88
89
|
setTimeout(() => setIsAnimating(false), 300)
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
const handleToggleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
93
|
+
if (!collapsible) return
|
|
94
|
+
if (event.key !== 'Enter' && event.key !== ' ') return
|
|
95
|
+
event.preventDefault()
|
|
96
|
+
handleToggle()
|
|
97
|
+
}
|
|
98
|
+
|
|
91
99
|
const showArrow = collapsible && (isHovered || headerIcon == null)
|
|
92
100
|
const headerIconNode = showArrow
|
|
93
101
|
? <ArrowDownSLine className={cn('w-[var(--font-size-sm)] h-[var(--font-size-sm)] shrink-0 transition-transform duration-200 text-text-secondary', isExpanded ? 'rotate-0' : '-rotate-90')} />
|
|
@@ -122,7 +130,9 @@ export function CollapsibleCard({
|
|
|
122
130
|
<div
|
|
123
131
|
className={cn('flex flex-row items-center gap-2 flex-1 min-w-0', collapsible && 'cursor-pointer')}
|
|
124
132
|
onClick={handleToggle}
|
|
133
|
+
onKeyDown={handleToggleKeyDown}
|
|
125
134
|
role={collapsible ? 'button' : undefined}
|
|
135
|
+
tabIndex={collapsible ? 0 : undefined}
|
|
126
136
|
aria-expanded={collapsible ? isExpanded : undefined}
|
|
127
137
|
>
|
|
128
138
|
<div className="w-[var(--font-size-sm)] h-[var(--font-size-sm)] flex items-center justify-center shrink-0 text-text-secondary [&>svg]:w-[var(--font-size-sm)] [&>svg]:h-[var(--font-size-sm)]">
|
|
@@ -150,10 +160,7 @@ export function CollapsibleCard({
|
|
|
150
160
|
<AnimatePresence initial={false}>
|
|
151
161
|
{isExpanded && (
|
|
152
162
|
<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] }}
|
|
163
|
+
{...MOTION_EXPAND}
|
|
157
164
|
className={cn('overflow-hidden', isAnimating && 'select-none')}
|
|
158
165
|
>
|
|
159
166
|
{/* 隐藏测量用,用于获取内容实际高度 */}
|
|
@@ -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 && (
|