sparkdesign 0.4.8 → 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.
Files changed (44) hide show
  1. package/cli/registry/AGENTS.md +1 -1
  2. package/cli/registry/agent-manifest.json +3996 -67
  3. package/cli/registry/basic/accordion.tsx +79 -0
  4. package/cli/registry/basic/badge.tsx +49 -0
  5. package/cli/registry/basic/button.tsx +19 -14
  6. package/cli/registry/basic/collapsible-card.tsx +10 -1
  7. package/cli/registry/basic/ellipsis-text.tsx +151 -0
  8. package/cli/registry/basic/form.tsx +186 -0
  9. package/cli/registry/basic/icon-button.tsx +12 -4
  10. package/cli/registry/basic/sidebar.tsx +665 -0
  11. package/cli/registry/basic/sonner.tsx +10 -10
  12. package/cli/registry/chat/chat-input/compound.tsx +1 -0
  13. package/cli/registry/chat/user-question/compound.tsx +2 -0
  14. package/cli/registry/meta.json +153 -0
  15. package/dist/registry/basic/accordion.d.ts +15 -0
  16. package/dist/registry/basic/badge.d.ts +23 -0
  17. package/dist/registry/basic/ellipsis-text.d.ts +45 -0
  18. package/dist/registry/basic/form.d.ts +23 -0
  19. package/dist/registry/basic/icon-button.d.ts +15 -2
  20. package/dist/registry/basic/item.d.ts +1 -1
  21. package/dist/registry/basic/sidebar.d.ts +72 -0
  22. package/dist/registry/basic/tag.d.ts +1 -1
  23. package/dist/spark-design.cjs.js +24 -27
  24. package/dist/spark-design.es.js +5353 -4470
  25. package/dist/sparkdesign.css +1 -1
  26. package/dist/src/components/basic/Accordion/index.d.ts +13 -0
  27. package/dist/src/components/basic/Badge/index.d.ts +13 -0
  28. package/dist/src/components/basic/EllipsisText/index.d.ts +4 -36
  29. package/dist/src/components/basic/Form/index.d.ts +12 -0
  30. package/dist/src/components/basic/Sidebar/index.d.ts +13 -0
  31. package/dist/src/components/index.d.ts +6 -2
  32. package/docs/agent/component-selection.md +106 -4
  33. package/package.json +8 -3
  34. package/registry/agent-manifest.json +3996 -67
  35. package/cli/registry/chat/user-question/UserQuestionCard.tsx +0 -198
  36. package/cli/registry/chat/user-question/UserQuestionFooter.tsx +0 -66
  37. package/cli/registry/chat/user-question/UserQuestionHeader.tsx +0 -64
  38. package/cli/registry/chat/user-question/useUserQuestionState.ts +0 -165
  39. package/dist/registry/chat/user-question/UserQuestionCard.d.ts +0 -36
  40. package/dist/registry/chat/user-question/UserQuestionFooter.d.ts +0 -24
  41. package/dist/registry/chat/user-question/UserQuestionHeader.d.ts +0 -26
  42. package/dist/registry/chat/user-question/useUserQuestionState.d.ts +0 -26
  43. package/dist/src/components/basic/CollapsibleSection/index.d.ts +0 -43
  44. 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-[colors,transform] duration-200 active:scale-[0.98] focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer',
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
- {loading ? (
95
- <Spinner className="shrink-0" />
96
- ) : prefixIcon ? (
97
- <span className="inline-flex shrink-0 items-center justify-center [&>*]:block [&>*]:leading-none">
98
- {prefixIcon}
99
- </span>
100
- ) : null}
101
- {children}
102
- {suffixIcon && (
103
- <span className="inline-flex shrink-0 items-center justify-center [&>*]:block [&>*]:leading-none">
104
- {suffixIcon}
105
- </span>
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
  )
@@ -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)]">
@@ -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'
@@ -0,0 +1,186 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as LabelPrimitive from "@radix-ui/react-label"
5
+ import { Slot } from "@radix-ui/react-slot"
6
+ import {
7
+ Controller,
8
+ FormProvider,
9
+ useFormContext,
10
+ type ControllerProps,
11
+ type FieldPath,
12
+ type FieldValues,
13
+ } from "react-hook-form"
14
+
15
+ import { cn } from "@/lib/utils"
16
+ import { Label } from "./label"
17
+
18
+ const Form = FormProvider
19
+
20
+ type FormFieldContextValue<
21
+ TFieldValues extends FieldValues = FieldValues,
22
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
23
+ > = {
24
+ name: TName
25
+ }
26
+
27
+ const FormFieldContext = React.createContext<FormFieldContextValue>(
28
+ {} as FormFieldContextValue
29
+ )
30
+
31
+ function FormField<
32
+ TFieldValues extends FieldValues = FieldValues,
33
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
34
+ >({ ...props }: ControllerProps<TFieldValues, TName>) {
35
+ return (
36
+ <FormFieldContext.Provider value={{ name: props.name }}>
37
+ <Controller {...props} />
38
+ </FormFieldContext.Provider>
39
+ )
40
+ }
41
+
42
+ type FormItemContextValue = {
43
+ id: string
44
+ }
45
+
46
+ const FormItemContext = React.createContext<FormItemContextValue>(
47
+ {} as FormItemContextValue
48
+ )
49
+
50
+ function useFormField() {
51
+ const fieldContext = React.useContext(FormFieldContext)
52
+ const itemContext = React.useContext(FormItemContext)
53
+ const { getFieldState, formState } = useFormContext()
54
+
55
+ if (!fieldContext.name) {
56
+ throw new Error("useFormField should be used within <FormField>")
57
+ }
58
+
59
+ const fieldState = getFieldState(fieldContext.name, formState)
60
+
61
+ const { id } = itemContext
62
+
63
+ return {
64
+ id,
65
+ name: fieldContext.name,
66
+ formItemId: `${id}-form-item`,
67
+ formDescriptionId: `${id}-form-item-description`,
68
+ formMessageId: `${id}-form-item-message`,
69
+ ...fieldState,
70
+ }
71
+ }
72
+
73
+ const FormItem = React.forwardRef<
74
+ HTMLDivElement,
75
+ React.HTMLAttributes<HTMLDivElement>
76
+ >(({ className, ...props }, ref) => {
77
+ const id = React.useId()
78
+
79
+ return (
80
+ <FormItemContext.Provider value={{ id }}>
81
+ <div
82
+ ref={ref}
83
+ data-slot="form-item"
84
+ className={cn("space-y-2", className)}
85
+ {...props}
86
+ />
87
+ </FormItemContext.Provider>
88
+ )
89
+ })
90
+ FormItem.displayName = "FormItem"
91
+
92
+ const FormLabel = React.forwardRef<
93
+ React.ElementRef<typeof LabelPrimitive.Root>,
94
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
95
+ >(({ className, ...props }, ref) => {
96
+ const { error, formItemId } = useFormField()
97
+
98
+ return (
99
+ <Label
100
+ ref={ref}
101
+ data-slot="form-label"
102
+ data-error={!!error}
103
+ className={cn("data-[error=true]:text-error", className)}
104
+ htmlFor={formItemId}
105
+ {...props}
106
+ />
107
+ )
108
+ })
109
+ FormLabel.displayName = "FormLabel"
110
+
111
+ const FormControl = React.forwardRef<
112
+ React.ElementRef<typeof Slot>,
113
+ React.ComponentPropsWithoutRef<typeof Slot>
114
+ >(({ ...props }, ref) => {
115
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
116
+
117
+ return (
118
+ <Slot
119
+ ref={ref}
120
+ data-slot="form-control"
121
+ id={formItemId}
122
+ aria-describedby={
123
+ !error
124
+ ? `${formDescriptionId}`
125
+ : `${formDescriptionId} ${formMessageId}`
126
+ }
127
+ aria-invalid={!!error}
128
+ {...props}
129
+ />
130
+ )
131
+ })
132
+ FormControl.displayName = "FormControl"
133
+
134
+ const FormDescription = React.forwardRef<
135
+ HTMLParagraphElement,
136
+ React.HTMLAttributes<HTMLParagraphElement>
137
+ >(({ className, ...props }, ref) => {
138
+ const { formDescriptionId } = useFormField()
139
+
140
+ return (
141
+ <p
142
+ ref={ref}
143
+ id={formDescriptionId}
144
+ data-slot="form-description"
145
+ className={cn("text-sm text-text-tertiary", className)}
146
+ {...props}
147
+ />
148
+ )
149
+ })
150
+ FormDescription.displayName = "FormDescription"
151
+
152
+ const FormMessage = React.forwardRef<
153
+ HTMLParagraphElement,
154
+ React.HTMLAttributes<HTMLParagraphElement>
155
+ >(({ className, children, ...props }, ref) => {
156
+ const { error, formMessageId } = useFormField()
157
+ const body = error ? String(error?.message ?? "") : children
158
+
159
+ if (!body) {
160
+ return null
161
+ }
162
+
163
+ return (
164
+ <p
165
+ ref={ref}
166
+ id={formMessageId}
167
+ data-slot="form-message"
168
+ className={cn("text-sm font-medium text-error", className)}
169
+ {...props}
170
+ >
171
+ {body}
172
+ </p>
173
+ )
174
+ })
175
+ FormMessage.displayName = "FormMessage"
176
+
177
+ export {
178
+ useFormField,
179
+ Form,
180
+ FormItem,
181
+ FormLabel,
182
+ FormControl,
183
+ FormDescription,
184
+ FormMessage,
185
+ FormField,
186
+ }
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from 'class-variance-authority'
5
5
  import { cn } from '@/lib/utils'
6
6
 
7
7
  const iconButtonVariants = cva(
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
+ 'flex-none shrink-0 inline-flex items-center justify-center box-border 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 [&>*]:flex [&>*]:items-center [&>*]:justify-center [&>*]:size-full [&>*]:[&>svg]:block [&>*]:[&>svg]:leading-none',
9
9
  {
10
10
  variants: {
11
11
  variant: {
@@ -34,9 +34,17 @@ const iconButtonVariants = cva(
34
34
  }
35
35
  )
36
36
 
37
- export interface IconButtonProps
38
- extends ButtonHTMLAttributes<HTMLButtonElement>,
39
- VariantProps<typeof iconButtonVariants> {
37
+ type IconButtonAccessibleName =
38
+ | { 'aria-label': string; 'aria-labelledby'?: string; title?: string }
39
+ | { 'aria-label'?: string; 'aria-labelledby': string; title?: string }
40
+ | { 'aria-label'?: string; 'aria-labelledby'?: string; title: string }
41
+
42
+ export type IconButtonProps = Omit<
43
+ ButtonHTMLAttributes<HTMLButtonElement>,
44
+ 'aria-label' | 'aria-labelledby' | 'title'
45
+ > &
46
+ VariantProps<typeof iconButtonVariants> &
47
+ IconButtonAccessibleName & {
40
48
  icon: ReactNode
41
49
  asChild?: boolean
42
50
  }