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.
Files changed (96) 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/alert-dialog.tsx +3 -6
  5. package/cli/registry/basic/badge.tsx +49 -0
  6. package/cli/registry/basic/button.tsx +32 -14
  7. package/cli/registry/basic/card.tsx +20 -8
  8. package/cli/registry/basic/collapsible-card.tsx +12 -5
  9. package/cli/registry/basic/combobox.tsx +104 -46
  10. package/cli/registry/basic/context-menu.tsx +2 -3
  11. package/cli/registry/basic/date-picker.tsx +78 -7
  12. package/cli/registry/basic/dialog.tsx +3 -8
  13. package/cli/registry/basic/drawer.tsx +3 -5
  14. package/cli/registry/basic/dropdown-menu.tsx +2 -3
  15. package/cli/registry/basic/ellipsis-text.tsx +151 -0
  16. package/cli/registry/basic/form.tsx +186 -0
  17. package/cli/registry/basic/hover-card.tsx +2 -3
  18. package/cli/registry/basic/icon-button.tsx +29 -14
  19. package/cli/registry/basic/input-group.tsx +4 -4
  20. package/cli/registry/basic/input.tsx +29 -13
  21. package/cli/registry/basic/popover.tsx +2 -3
  22. package/cli/registry/basic/select.tsx +24 -4
  23. package/cli/registry/basic/sidebar.tsx +665 -0
  24. package/cli/registry/basic/sonner.tsx +10 -10
  25. package/cli/registry/basic/spinner.tsx +20 -5
  26. package/cli/registry/basic/textarea.tsx +30 -12
  27. package/cli/registry/basic/tooltip.tsx +2 -1
  28. package/cli/registry/chat/chat-input/compound.tsx +1 -0
  29. package/cli/registry/chat/user-question/compound.tsx +2 -0
  30. package/cli/registry/meta.json +250 -30
  31. package/dist/registry/basic/accordion.d.ts +15 -0
  32. package/dist/registry/basic/alert-dialog.d.ts +1 -1
  33. package/dist/registry/basic/avatar.d.ts +1 -1
  34. package/dist/registry/basic/badge.d.ts +23 -0
  35. package/dist/registry/basic/button.d.ts +3 -1
  36. package/dist/registry/basic/card.d.ts +9 -4
  37. package/dist/registry/basic/combobox.d.ts +20 -9
  38. package/dist/registry/basic/date-picker.d.ts +18 -9
  39. package/dist/registry/basic/dialog.d.ts +1 -1
  40. package/dist/registry/basic/ellipsis-text.d.ts +45 -0
  41. package/dist/registry/basic/form.d.ts +23 -0
  42. package/dist/registry/basic/icon-button.d.ts +17 -3
  43. package/dist/registry/basic/input-group.d.ts +5 -3
  44. package/dist/registry/basic/input.d.ts +8 -3
  45. package/dist/registry/basic/item.d.ts +3 -3
  46. package/dist/registry/basic/resizable.d.ts +48 -48
  47. package/dist/registry/basic/select.d.ts +7 -2
  48. package/dist/registry/basic/sidebar.d.ts +72 -0
  49. package/dist/registry/basic/spinner.d.ts +6 -2
  50. package/dist/registry/basic/tag.d.ts +1 -1
  51. package/dist/registry/basic/textarea.d.ts +9 -3
  52. package/dist/registry/basic/toggle.d.ts +1 -1
  53. package/dist/scale/computed.css +11 -0
  54. package/dist/scale/config.css +11 -0
  55. package/dist/scale/presets/compact.css +7 -0
  56. package/dist/scale/presets/dense.css +7 -0
  57. package/dist/scale/presets/sharp.css +7 -0
  58. package/dist/scale/presets/soft.css +7 -0
  59. package/dist/spark-design.cjs.js +34 -37
  60. package/dist/spark-design.es.js +7200 -4933
  61. package/dist/sparkdesign.css +1 -1
  62. package/dist/src/components/basic/Accordion/index.d.ts +13 -0
  63. package/dist/src/components/basic/Badge/index.d.ts +13 -0
  64. package/dist/src/components/basic/EllipsisText/index.d.ts +4 -36
  65. package/dist/src/components/basic/Form/index.d.ts +12 -0
  66. package/dist/src/components/basic/Sidebar/index.d.ts +13 -0
  67. package/dist/src/components/index.d.ts +7 -3
  68. package/dist/src/lib/index.d.ts +1 -1
  69. package/dist/src/lib/motion.d.ts +79 -0
  70. package/dist/theme-base.css +22 -0
  71. package/dist/themes/dark-mint.css +6 -0
  72. package/dist/themes/dark-parchment.css +6 -0
  73. package/dist/themes/light-parchment.css +6 -0
  74. package/dist/tokens/scale/computed.css +11 -0
  75. package/dist/tokens/scale/config.css +11 -0
  76. package/dist/tokens/scale/presets/compact.css +7 -0
  77. package/dist/tokens/scale/presets/dense.css +7 -0
  78. package/dist/tokens/scale/presets/sharp.css +7 -0
  79. package/dist/tokens/scale/presets/soft.css +7 -0
  80. package/dist/tokens/theme-base.css +22 -0
  81. package/dist/tokens/themes/dark-mint.css +6 -0
  82. package/dist/tokens/themes/dark-parchment.css +6 -0
  83. package/dist/tokens/themes/light-parchment.css +6 -0
  84. package/docs/agent/component-selection.md +106 -4
  85. package/package.json +8 -3
  86. package/registry/agent-manifest.json +3996 -67
  87. package/cli/registry/chat/user-question/UserQuestionCard.tsx +0 -198
  88. package/cli/registry/chat/user-question/UserQuestionFooter.tsx +0 -66
  89. package/cli/registry/chat/user-question/UserQuestionHeader.tsx +0 -64
  90. package/cli/registry/chat/user-question/useUserQuestionState.ts +0 -165
  91. package/dist/registry/chat/user-question/UserQuestionCard.d.ts +0 -36
  92. package/dist/registry/chat/user-question/UserQuestionFooter.d.ts +0 -24
  93. package/dist/registry/chat/user-question/UserQuestionHeader.d.ts +0 -26
  94. package/dist/registry/chat/user-question/useUserQuestionState.d.ts +0 -26
  95. package/dist/src/components/basic/CollapsibleSection/index.d.ts +0 -43
  96. package/dist/src/components/chat/Response/StreamingMarkdownBlock.d.ts +0 -12
@@ -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
- initial={{ opacity: 0 }}
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: 0.25, ease: [0.4, 0, 0.2, 1] }}
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
- initial={{ opacity: 0 }}
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>
@@ -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
+ }
@@ -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
- initial={{ opacity: 0, y: 4 }}
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-[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',
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',
@@ -32,10 +34,19 @@ const iconButtonVariants = cva(
32
34
  }
33
35
  )
34
36
 
35
- export interface IconButtonProps
36
- extends ButtonHTMLAttributes<HTMLButtonElement>,
37
- 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 & {
38
48
  icon: ReactNode
49
+ asChild?: boolean
39
50
  }
40
51
 
41
52
  export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
@@ -46,20 +57,24 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
46
57
  rounded = 'square',
47
58
  icon,
48
59
  disabled = false,
60
+ asChild = false,
49
61
  className,
50
62
  ...props
51
63
  },
52
64
  ref
53
- ) => (
54
- <button
55
- ref={ref}
56
- className={cn(iconButtonVariants({ variant, size, rounded }), className)}
57
- disabled={disabled}
58
- {...props}
59
- >
60
- <span className="inline-flex shrink-0 size-full items-center justify-center">{icon}</span>
61
- </button>
62
- )
65
+ ) => {
66
+ const Comp = asChild ? Slot.Root : 'button'
67
+ return (
68
+ <Comp
69
+ ref={ref}
70
+ className={cn(iconButtonVariants({ variant, size, rounded }), className)}
71
+ disabled={disabled}
72
+ {...props}
73
+ >
74
+ <span className="inline-flex shrink-0 size-full items-center justify-center">{icon}</span>
75
+ </Comp>
76
+ )
77
+ }
63
78
  )
64
79
  IconButton.displayName = 'IconButton'
65
80
 
@@ -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
- }: React.ComponentProps<"input">) {
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
- }: React.ComponentProps<"textarea">) {
148
+ }: TextareaProps) {
149
149
  return (
150
150
  <Textarea
151
151
  data-slot="input-group-control"
@@ -1,27 +1,43 @@
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
- export type InputProps = React.InputHTMLAttributes<HTMLInputElement>
5
+ const inputVariants = cva(
6
+ [
7
+ 'flex w-full min-w-0 rounded-lg border border-border-tertiary bg-bg-container text-text transition-colors',
8
+ 'placeholder:text-text-tertiary',
9
+ 'file:border-0 file:bg-transparent file:font-medium file:text-text',
10
+ 'outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0',
11
+ 'focus:border-border-secondary focus-visible:border-border-secondary',
12
+ 'disabled:cursor-not-allowed disabled:opacity-50',
13
+ 'aria-invalid:border-error aria-invalid:ring-0',
14
+ ].join(' '),
15
+ {
16
+ variants: {
17
+ size: {
18
+ sm: 'h-7 px-2 py-0.5 text-xs file:text-xs',
19
+ md: 'h-9 px-3 py-1 text-sm file:text-sm',
20
+ lg: 'h-11 px-4 py-1.5 text-base file:text-base',
21
+ },
22
+ },
23
+ defaultVariants: { size: 'md' },
24
+ }
25
+ )
26
+
27
+ export interface InputProps
28
+ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>,
29
+ VariantProps<typeof inputVariants> {}
5
30
 
6
31
  const Input = React.forwardRef<HTMLInputElement, InputProps>(
7
- ({ className, type, ...props }, ref) => (
32
+ ({ className, type, size = 'md', ...props }, ref) => (
8
33
  <input
9
34
  type={type}
10
35
  ref={ref}
11
- className={cn(
12
- 'flex h-9 w-full min-w-0 rounded-lg border border-border-tertiary bg-bg-container px-3 py-1 text-sm text-text transition-colors',
13
- 'placeholder:text-text-tertiary',
14
- 'file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-text',
15
- 'outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0',
16
- 'focus:border-border-secondary focus-visible:border-border-secondary',
17
- 'disabled:cursor-not-allowed disabled:opacity-50',
18
- 'aria-invalid:border-error aria-invalid:ring-0',
19
- className,
20
- )}
36
+ className={cn(inputVariants({ size }), className)}
21
37
  {...props}
22
38
  />
23
39
  ),
24
40
  )
25
41
  Input.displayName = 'Input'
26
42
 
27
- export { Input }
43
+ export { Input, inputVariants }
@@ -2,6 +2,7 @@ import * as React from 'react'
2
2
  import { motion } from 'framer-motion'
3
3
  import * as PopoverPrimitive from '@radix-ui/react-popover'
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 Popover = PopoverPrimitive.Root
@@ -42,9 +43,7 @@ const PopoverContent = React.forwardRef<
42
43
  {...props}
43
44
  >
44
45
  <motion.div
45
- initial={{ opacity: 0, y: 4 }}
46
- animate={{ opacity: 1, y: 0 }}
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>
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react'
2
2
  import * as SelectPrimitive from '@radix-ui/react-select'
3
+ import { cva, type VariantProps } from 'class-variance-authority'
3
4
  import { cn } from '@/lib/utils'
4
5
  import { ArrowDownSLine, ArrowUpLine, CheckLine } from './icons-inline'
5
6
  import { getThemeFromDocument } from './theme-from-document'
@@ -12,21 +13,39 @@ const Select = SelectPrimitive.Root
12
13
  const SelectGroup = SelectPrimitive.Group
13
14
  const SelectValue = SelectPrimitive.Value
14
15
 
16
+ const selectTriggerVariants = cva(
17
+ [
18
+ 'flex w-full items-center justify-between gap-2 whitespace-nowrap rounded-lg border border-border-tertiary bg-bg-container text-text placeholder:text-muted-foreground transition-colors disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
19
+ 'focus:border-border-secondary focus-visible:border-border-secondary data-[state=open]:border-border-secondary',
20
+ ].join(' '),
21
+ {
22
+ variants: {
23
+ size: {
24
+ sm: 'h-7 px-2 py-0.5 text-xs',
25
+ md: 'h-9 px-3 py-2 text-sm',
26
+ lg: 'h-11 px-4 py-2.5 text-base',
27
+ },
28
+ },
29
+ defaultVariants: { size: 'md' },
30
+ }
31
+ )
32
+
15
33
  export interface SelectTriggerProps
16
- extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> {
34
+ extends Omit<React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>, 'size'>,
35
+ VariantProps<typeof selectTriggerVariants> {
17
36
  triggerIcon?: React.ReactNode
37
+ size?: 'sm' | 'md' | 'lg'
18
38
  }
19
39
 
20
40
  const SelectTrigger = React.forwardRef<
21
41
  React.ElementRef<typeof SelectPrimitive.Trigger>,
22
42
  SelectTriggerProps
23
- >(({ className, children, disabled, triggerIcon, ...props }, ref) => (
43
+ >(({ className, children, disabled, triggerIcon, size = 'md', ...props }, ref) => (
24
44
  <SelectPrimitive.Trigger
25
45
  ref={ref}
26
46
  disabled={disabled}
27
47
  className={cn(
28
- 'flex h-9 w-full items-center justify-between gap-2 whitespace-nowrap rounded-lg border border-border-tertiary bg-bg-container px-3 py-2 text-sm text-text placeholder:text-muted-foreground transition-colors disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
29
- 'focus:border-border-secondary focus-visible:border-border-secondary data-[state=open]:border-border-secondary',
48
+ selectTriggerVariants({ size }),
30
49
  FOCUS_RESET,
31
50
  className
32
51
  )}
@@ -175,4 +194,5 @@ export {
175
194
  SelectSeparator,
176
195
  SelectScrollUpButton,
177
196
  SelectScrollDownButton,
197
+ selectTriggerVariants,
178
198
  }