sparkdesign 0.4.8 → 0.4.10
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/AGENT_COMPONENT_LIBRARY_QUICKREF.md +117 -0
- package/AI_README.md +7 -2
- package/README.md +4 -1
- package/cli/registry/AGENTS.md +1 -1
- package/cli/registry/agent-manifest.json +4040 -67
- package/cli/registry/basic/accordion.tsx +79 -0
- package/cli/registry/basic/badge.tsx +49 -0
- package/cli/registry/basic/button.tsx +19 -14
- package/cli/registry/basic/calendar.tsx +16 -16
- package/cli/registry/basic/collapsible-card.tsx +10 -1
- package/cli/registry/basic/combobox.tsx +11 -2
- package/cli/registry/basic/date-picker.tsx +3 -2
- package/cli/registry/basic/ellipsis-text.tsx +151 -0
- package/cli/registry/basic/form.tsx +186 -0
- package/cli/registry/basic/icon-button.tsx +12 -4
- package/cli/registry/basic/popover.tsx +19 -2
- package/cli/registry/basic/rating.tsx +161 -0
- package/cli/registry/basic/sidebar.tsx +665 -0
- package/cli/registry/basic/sonner.tsx +10 -10
- package/cli/registry/basic/stepper.tsx +163 -0
- package/cli/registry/basic/timeline.tsx +129 -0
- package/cli/registry/chat/chat-input/compound.tsx +1 -0
- package/cli/registry/chat/permission-card.tsx +1 -1
- package/cli/registry/chat/user-question/compound.tsx +2 -0
- package/cli/registry/meta.json +171 -13
- package/dist/registry/basic/accordion.d.ts +15 -0
- package/dist/registry/basic/badge.d.ts +23 -0
- package/dist/registry/basic/calendar.d.ts +1 -1
- package/dist/registry/basic/combobox.d.ts +2 -1
- package/dist/registry/basic/date-picker.d.ts +2 -2
- 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 +15 -2
- package/dist/registry/basic/item.d.ts +1 -1
- package/dist/registry/basic/popover.d.ts +2 -0
- package/dist/registry/basic/rating.d.ts +31 -0
- package/dist/registry/basic/sidebar.d.ts +72 -0
- package/dist/registry/basic/stepper.d.ts +36 -0
- package/dist/registry/basic/tag.d.ts +1 -1
- package/dist/registry/basic/timeline.d.ts +34 -0
- package/dist/spark-design.cjs.js +27 -30
- package/dist/spark-design.es.js +6398 -5130
- 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/Rating/index.d.ts +13 -0
- package/dist/src/components/basic/Sidebar/index.d.ts +13 -0
- package/dist/src/components/basic/Stepper/index.d.ts +13 -0
- package/dist/src/components/basic/Timeline/index.d.ts +13 -0
- package/dist/src/components/index.d.ts +12 -4
- package/docs/agent/component-selection.md +104 -4
- package/docs/agent/prompt-recipes.md +167 -0
- package/docs/guides/agent-usage.md +213 -0
- package/docs/guides/system-operating-model.md +148 -0
- package/package.json +20 -3
- package/registry/agent-manifest.json +4040 -67
- package/cli/registry/basic/sheet.tsx +0 -18
- 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/basic/sheet.d.ts +0 -13
- 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/basic/Sheet/index.d.ts +0 -13
- 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 }
|
|
@@ -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 }
|
|
@@ -2,11 +2,10 @@ import { forwardRef } from 'react'
|
|
|
2
2
|
import type { ButtonHTMLAttributes, ReactNode } from 'react'
|
|
3
3
|
import { Slot } from 'radix-ui'
|
|
4
4
|
import { cva, type VariantProps } from 'class-variance-authority'
|
|
5
|
-
import { cn } from '@/lib/utils'
|
|
6
5
|
import { Spinner } from './spinner'
|
|
7
6
|
|
|
8
7
|
const buttonVariants = cva(
|
|
9
|
-
'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',
|
|
10
9
|
{
|
|
11
10
|
variants: {
|
|
12
11
|
variant: {
|
|
@@ -91,18 +90,24 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
91
90
|
aria-busy={loading || undefined}
|
|
92
91
|
{...props}
|
|
93
92
|
>
|
|
94
|
-
{
|
|
95
|
-
|
|
96
|
-
) :
|
|
97
|
-
|
|
98
|
-
{
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
+
</>
|
|
106
111
|
)}
|
|
107
112
|
</Comp>
|
|
108
113
|
)
|
|
@@ -31,7 +31,7 @@ function Calendar({
|
|
|
31
31
|
<DayPicker
|
|
32
32
|
showOutsideDays={showOutsideDays}
|
|
33
33
|
className={cn(
|
|
34
|
-
"group/calendar bg-bg-base p-3 [--cell-size
|
|
34
|
+
"group/calendar bg-bg-base p-3 [--cell-size:2rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
|
|
35
35
|
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
|
36
36
|
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
|
37
37
|
className
|
|
@@ -55,24 +55,24 @@ function Calendar({
|
|
|
55
55
|
),
|
|
56
56
|
button_previous: cn(
|
|
57
57
|
buttonVariants({ variant: buttonVariant }),
|
|
58
|
-
"size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
|
|
58
|
+
"h-[var(--cell-size)] w-[var(--cell-size)] p-0 select-none aria-disabled:opacity-50",
|
|
59
59
|
defaultClassNames.button_previous
|
|
60
60
|
),
|
|
61
61
|
button_next: cn(
|
|
62
62
|
buttonVariants({ variant: buttonVariant }),
|
|
63
|
-
"size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
|
|
63
|
+
"h-[var(--cell-size)] w-[var(--cell-size)] p-0 select-none aria-disabled:opacity-50",
|
|
64
64
|
defaultClassNames.button_next
|
|
65
65
|
),
|
|
66
66
|
month_caption: cn(
|
|
67
|
-
"flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)",
|
|
67
|
+
"flex h-[var(--cell-size)] w-full items-center justify-center px-[var(--cell-size)]",
|
|
68
68
|
defaultClassNames.month_caption
|
|
69
69
|
),
|
|
70
70
|
dropdowns: cn(
|
|
71
|
-
"flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium",
|
|
71
|
+
"flex h-[var(--cell-size)] w-full items-center justify-center gap-1.5 text-sm font-medium",
|
|
72
72
|
defaultClassNames.dropdowns
|
|
73
73
|
),
|
|
74
74
|
dropdown_root: cn(
|
|
75
|
-
"
|
|
75
|
+
"has-focus:border-primary-border border-border-tertiary shadow-sm has-focus:ring-primary-border/50 has-focus:ring-[3px] relative rounded-md border",
|
|
76
76
|
defaultClassNames.dropdown_root
|
|
77
77
|
),
|
|
78
78
|
dropdown: cn(
|
|
@@ -80,7 +80,7 @@ function Calendar({
|
|
|
80
80
|
defaultClassNames.dropdown
|
|
81
81
|
),
|
|
82
82
|
caption_label: cn(
|
|
83
|
-
"font-medium
|
|
83
|
+
"select-none font-medium",
|
|
84
84
|
captionLayout === "label"
|
|
85
85
|
? "text-sm"
|
|
86
86
|
: "flex h-8 items-center gap-1 rounded-md pr-1 pl-2 text-sm [&>svg]:size-3.5 [&>svg]:text-text-tertiary",
|
|
@@ -94,7 +94,7 @@ function Calendar({
|
|
|
94
94
|
),
|
|
95
95
|
week: cn("mt-2 flex w-full", defaultClassNames.week),
|
|
96
96
|
week_number_header: cn(
|
|
97
|
-
"w-(--cell-size) select-none",
|
|
97
|
+
"w-[var(--cell-size)] select-none",
|
|
98
98
|
defaultClassNames.week_number_header
|
|
99
99
|
),
|
|
100
100
|
week_number: cn(
|
|
@@ -102,20 +102,20 @@ function Calendar({
|
|
|
102
102
|
defaultClassNames.week_number
|
|
103
103
|
),
|
|
104
104
|
day: cn(
|
|
105
|
-
"group/day relative aspect-square h-full w-full p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-
|
|
105
|
+
"group/day relative aspect-square h-full w-full p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-sm",
|
|
106
106
|
props.showWeekNumber
|
|
107
|
-
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-
|
|
108
|
-
: "[&:first-child[data-selected=true]_button]:rounded-l-
|
|
107
|
+
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-sm"
|
|
108
|
+
: "[&:first-child[data-selected=true]_button]:rounded-l-sm",
|
|
109
109
|
defaultClassNames.day
|
|
110
110
|
),
|
|
111
111
|
range_start: cn(
|
|
112
|
-
"rounded-l-
|
|
112
|
+
"rounded-l-sm bg-fill-secondary",
|
|
113
113
|
defaultClassNames.range_start
|
|
114
114
|
),
|
|
115
115
|
range_middle: cn("rounded-none", defaultClassNames.range_middle),
|
|
116
|
-
range_end: cn("rounded-r-
|
|
116
|
+
range_end: cn("rounded-r-sm bg-fill-secondary", defaultClassNames.range_end),
|
|
117
117
|
today: cn(
|
|
118
|
-
"rounded-
|
|
118
|
+
"rounded-sm bg-fill-secondary text-text data-[selected=true]:rounded-sm",
|
|
119
119
|
defaultClassNames.today
|
|
120
120
|
),
|
|
121
121
|
outside: cn(
|
|
@@ -164,7 +164,7 @@ function Calendar({
|
|
|
164
164
|
WeekNumber: ({ children, ...props }) => {
|
|
165
165
|
return (
|
|
166
166
|
<td {...props}>
|
|
167
|
-
<div className="flex size-(--cell-size) items-center justify-center text-center">
|
|
167
|
+
<div className="flex size-[var(--cell-size)] items-center justify-center text-center">
|
|
168
168
|
{children}
|
|
169
169
|
</div>
|
|
170
170
|
</td>
|
|
@@ -209,7 +209,7 @@ function CalendarDayButton({
|
|
|
209
209
|
data-range-middle={modifiers.range_middle}
|
|
210
210
|
className={cn(
|
|
211
211
|
"inline-flex items-center justify-center transition-colors hover:bg-fill-secondary hover:text-text",
|
|
212
|
-
"flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 px-0 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-primary-border group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-primary-border/50 data-[range-end=true]:rounded-
|
|
212
|
+
"flex aspect-square size-auto w-full min-w-[var(--cell-size)] flex-col gap-1 px-0 leading-none font-normal hover:rounded-sm group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-primary-border group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-primary-border/50 data-[range-end=true]:rounded-sm data-[range-end=true]:rounded-r-sm data-[range-end=true]:bg-primary data-[range-end=true]:text-text-on-primary data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-fill-secondary data-[range-middle=true]:text-text data-[range-start=true]:rounded-sm data-[range-start=true]:rounded-l-sm data-[range-start=true]:bg-primary data-[range-start=true]:text-text-on-primary data-[selected=true]:rounded-sm data-[selected=true]:!outline-none data-[selected=true]:!shadow-none data-[selected=true]:!ring-0 data-[selected=true]:!ring-offset-0 data-[selected=true]:!border-transparent data-[selected=true]:focus-visible:!ring-0 data-[selected=true]:focus-visible:!outline-none data-[selected-single=true]:rounded-sm data-[selected-single=true]:bg-primary data-[selected-single=true]:text-text-on-primary dark:hover:text-text [&>span]:text-xs [&>span]:opacity-70",
|
|
213
213
|
defaultClassNames.day,
|
|
214
214
|
className
|
|
215
215
|
)}
|
|
@@ -1,4 +1,4 @@
|
|
|
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
4
|
import { MOTION_EXPAND } from '@/lib/motion'
|
|
@@ -89,6 +89,13 @@ export function CollapsibleCard({
|
|
|
89
89
|
setTimeout(() => setIsAnimating(false), 300)
|
|
90
90
|
}
|
|
91
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
|
+
|
|
92
99
|
const showArrow = collapsible && (isHovered || headerIcon == null)
|
|
93
100
|
const headerIconNode = showArrow
|
|
94
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')} />
|
|
@@ -123,7 +130,9 @@ export function CollapsibleCard({
|
|
|
123
130
|
<div
|
|
124
131
|
className={cn('flex flex-row items-center gap-2 flex-1 min-w-0', collapsible && 'cursor-pointer')}
|
|
125
132
|
onClick={handleToggle}
|
|
133
|
+
onKeyDown={handleToggleKeyDown}
|
|
126
134
|
role={collapsible ? 'button' : undefined}
|
|
135
|
+
tabIndex={collapsible ? 0 : undefined}
|
|
127
136
|
aria-expanded={collapsible ? isExpanded : undefined}
|
|
128
137
|
>
|
|
129
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)]">
|
|
@@ -22,6 +22,7 @@ export interface ComboboxOption {
|
|
|
22
22
|
|
|
23
23
|
export interface ComboboxProps {
|
|
24
24
|
options: ComboboxOption[]
|
|
25
|
+
size?: 'default' | 'compact'
|
|
25
26
|
value?: string
|
|
26
27
|
defaultValue?: string
|
|
27
28
|
onValueChange?: (value: string) => void
|
|
@@ -46,6 +47,7 @@ function Combobox({
|
|
|
46
47
|
placeholder = 'Select an option…',
|
|
47
48
|
searchPlaceholder = 'Search…',
|
|
48
49
|
emptyText = 'No option found.',
|
|
50
|
+
size = 'compact',
|
|
49
51
|
disabled,
|
|
50
52
|
className,
|
|
51
53
|
contentClassName,
|
|
@@ -96,9 +98,16 @@ function Combobox({
|
|
|
96
98
|
</PopoverTrigger>
|
|
97
99
|
<PopoverContent
|
|
98
100
|
align={align}
|
|
99
|
-
|
|
101
|
+
contentPadding="compact"
|
|
102
|
+
className={cn('w-[var(--radix-popover-trigger-width)] min-w-56', contentClassName)}
|
|
100
103
|
>
|
|
101
|
-
<Command
|
|
104
|
+
<Command
|
|
105
|
+
className={cn(
|
|
106
|
+
'w-full',
|
|
107
|
+
size === 'compact' &&
|
|
108
|
+
'[&_[data-slot=command-input-wrapper]]:h-8 [&_[data-slot=command-input]]:h-8 [&_[data-slot=command-input-wrapper]]:px-2 [&_[data-slot=command-input]]:py-1.5 [&_[cmdk-group]]:p-0 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-1.5 [&_[data-slot=command-empty]]:py-3'
|
|
109
|
+
)}
|
|
110
|
+
>
|
|
102
111
|
<CommandInput placeholder={searchPlaceholder} />
|
|
103
112
|
<CommandList>
|
|
104
113
|
<CommandEmpty>{emptyText}</CommandEmpty>
|
|
@@ -61,15 +61,16 @@ function DatePicker({
|
|
|
61
61
|
!selected && 'text-text-tertiary',
|
|
62
62
|
className,
|
|
63
63
|
)}
|
|
64
|
-
|
|
64
|
+
prefixIcon={<CalendarIcon className="size-4 text-text-tertiary" />}
|
|
65
65
|
>
|
|
66
66
|
{selected ? format(selected, formatString) : <span>{placeholder}</span>}
|
|
67
67
|
</Button>
|
|
68
68
|
</PopoverTrigger>
|
|
69
|
-
<PopoverContent className="w-auto
|
|
69
|
+
<PopoverContent className="w-auto" contentPadding="compact" align={align}>
|
|
70
70
|
<Calendar
|
|
71
71
|
mode="single"
|
|
72
72
|
selected={selected}
|
|
73
|
+
defaultMonth={selected}
|
|
73
74
|
onSelect={handleSelect}
|
|
74
75
|
autoFocus
|
|
75
76
|
/>
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useRef,
|
|
5
|
+
useState,
|
|
6
|
+
forwardRef,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
type CSSProperties,
|
|
9
|
+
} from 'react'
|
|
10
|
+
import { clsx } from 'clsx'
|
|
11
|
+
import { Tooltip } from './tooltip'
|
|
12
|
+
|
|
13
|
+
export interface EllipsisTextProps {
|
|
14
|
+
/** Text or inline content to display. */
|
|
15
|
+
children: ReactNode
|
|
16
|
+
/**
|
|
17
|
+
* Maximum visible lines before truncation.
|
|
18
|
+
* @default 1
|
|
19
|
+
*/
|
|
20
|
+
lines?: number
|
|
21
|
+
/** Extra className for the text node. */
|
|
22
|
+
className?: string
|
|
23
|
+
/**
|
|
24
|
+
* Tooltip placement when overflow is detected.
|
|
25
|
+
* @default 'top'
|
|
26
|
+
*/
|
|
27
|
+
placement?: 'top' | 'bottom' | 'left' | 'right'
|
|
28
|
+
/** Extra className for Tooltip content. */
|
|
29
|
+
tooltipClassName?: string
|
|
30
|
+
/** Custom tooltip content. Defaults to children. */
|
|
31
|
+
tooltipContent?: ReactNode
|
|
32
|
+
/**
|
|
33
|
+
* Disable tooltip even when content overflows.
|
|
34
|
+
* @default false
|
|
35
|
+
*/
|
|
36
|
+
disabled?: boolean
|
|
37
|
+
/** Inline style for the text node. */
|
|
38
|
+
style?: CSSProperties
|
|
39
|
+
/**
|
|
40
|
+
* Rendered text element.
|
|
41
|
+
* @default 'span'
|
|
42
|
+
*/
|
|
43
|
+
as?: 'span' | 'div' | 'p'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function setRef<T>(ref: React.Ref<T> | undefined, value: T | null) {
|
|
47
|
+
if (!ref) return
|
|
48
|
+
if (typeof ref === 'function') ref(value)
|
|
49
|
+
else (ref as React.MutableRefObject<T | null>).current = value
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const EllipsisText = forwardRef<HTMLElement, EllipsisTextProps>(
|
|
53
|
+
(
|
|
54
|
+
{
|
|
55
|
+
children,
|
|
56
|
+
lines = 1,
|
|
57
|
+
className = '',
|
|
58
|
+
placement = 'top',
|
|
59
|
+
tooltipClassName = '',
|
|
60
|
+
tooltipContent,
|
|
61
|
+
disabled = false,
|
|
62
|
+
style,
|
|
63
|
+
as: Component = 'span',
|
|
64
|
+
},
|
|
65
|
+
ref
|
|
66
|
+
) => {
|
|
67
|
+
const textRef = useRef<HTMLElement>(null)
|
|
68
|
+
const [isOverflowing, setIsOverflowing] = useState(false)
|
|
69
|
+
|
|
70
|
+
const checkOverflow = useCallback(() => {
|
|
71
|
+
const el = textRef.current
|
|
72
|
+
if (!el) return
|
|
73
|
+
const overflowing =
|
|
74
|
+
lines === 1
|
|
75
|
+
? el.scrollWidth > el.clientWidth
|
|
76
|
+
: el.scrollHeight > el.clientHeight
|
|
77
|
+
setIsOverflowing(overflowing)
|
|
78
|
+
}, [lines])
|
|
79
|
+
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
const raf = requestAnimationFrame(() => checkOverflow())
|
|
82
|
+
const el = textRef.current
|
|
83
|
+
if (!el) return () => cancelAnimationFrame(raf)
|
|
84
|
+
const ro = new ResizeObserver(() => checkOverflow())
|
|
85
|
+
ro.observe(el)
|
|
86
|
+
window.addEventListener('resize', checkOverflow)
|
|
87
|
+
return () => {
|
|
88
|
+
cancelAnimationFrame(raf)
|
|
89
|
+
ro.disconnect()
|
|
90
|
+
window.removeEventListener('resize', checkOverflow)
|
|
91
|
+
}
|
|
92
|
+
}, [checkOverflow, children])
|
|
93
|
+
|
|
94
|
+
const ellipsisStyle: CSSProperties =
|
|
95
|
+
lines === 1
|
|
96
|
+
? {
|
|
97
|
+
display: 'block',
|
|
98
|
+
overflow: 'hidden',
|
|
99
|
+
textOverflow: 'ellipsis',
|
|
100
|
+
whiteSpace: 'nowrap',
|
|
101
|
+
...style,
|
|
102
|
+
}
|
|
103
|
+
: {
|
|
104
|
+
overflow: 'hidden',
|
|
105
|
+
display: '-webkit-box',
|
|
106
|
+
WebkitLineClamp: lines,
|
|
107
|
+
WebkitBoxOrient: 'vertical',
|
|
108
|
+
...style,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const refCallback = useCallback(
|
|
112
|
+
(el: HTMLElement | null) => {
|
|
113
|
+
setRef(textRef, el)
|
|
114
|
+
setRef(ref, el)
|
|
115
|
+
},
|
|
116
|
+
[ref]
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
const triggerNode = (
|
|
120
|
+
<Component
|
|
121
|
+
ref={refCallback}
|
|
122
|
+
className={className}
|
|
123
|
+
style={ellipsisStyle}
|
|
124
|
+
>
|
|
125
|
+
{children}
|
|
126
|
+
</Component>
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
const useTooltip = isOverflowing && !disabled
|
|
130
|
+
if (useTooltip) {
|
|
131
|
+
return (
|
|
132
|
+
<Tooltip
|
|
133
|
+
content={tooltipContent ?? children}
|
|
134
|
+
side={placement}
|
|
135
|
+
contentClassName={clsx(tooltipClassName)}
|
|
136
|
+
>
|
|
137
|
+
<span
|
|
138
|
+
ref={refCallback}
|
|
139
|
+
className={className}
|
|
140
|
+
style={ellipsisStyle}
|
|
141
|
+
>
|
|
142
|
+
{children}
|
|
143
|
+
</span>
|
|
144
|
+
</Tooltip>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
return triggerNode
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
EllipsisText.displayName = 'EllipsisText'
|