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.
Files changed (71) hide show
  1. package/AGENT_COMPONENT_LIBRARY_QUICKREF.md +117 -0
  2. package/AI_README.md +7 -2
  3. package/README.md +4 -1
  4. package/cli/registry/AGENTS.md +1 -1
  5. package/cli/registry/agent-manifest.json +4040 -67
  6. package/cli/registry/basic/accordion.tsx +79 -0
  7. package/cli/registry/basic/badge.tsx +49 -0
  8. package/cli/registry/basic/button.tsx +19 -14
  9. package/cli/registry/basic/calendar.tsx +16 -16
  10. package/cli/registry/basic/collapsible-card.tsx +10 -1
  11. package/cli/registry/basic/combobox.tsx +11 -2
  12. package/cli/registry/basic/date-picker.tsx +3 -2
  13. package/cli/registry/basic/ellipsis-text.tsx +151 -0
  14. package/cli/registry/basic/form.tsx +186 -0
  15. package/cli/registry/basic/icon-button.tsx +12 -4
  16. package/cli/registry/basic/popover.tsx +19 -2
  17. package/cli/registry/basic/rating.tsx +161 -0
  18. package/cli/registry/basic/sidebar.tsx +665 -0
  19. package/cli/registry/basic/sonner.tsx +10 -10
  20. package/cli/registry/basic/stepper.tsx +163 -0
  21. package/cli/registry/basic/timeline.tsx +129 -0
  22. package/cli/registry/chat/chat-input/compound.tsx +1 -0
  23. package/cli/registry/chat/permission-card.tsx +1 -1
  24. package/cli/registry/chat/user-question/compound.tsx +2 -0
  25. package/cli/registry/meta.json +171 -13
  26. package/dist/registry/basic/accordion.d.ts +15 -0
  27. package/dist/registry/basic/badge.d.ts +23 -0
  28. package/dist/registry/basic/calendar.d.ts +1 -1
  29. package/dist/registry/basic/combobox.d.ts +2 -1
  30. package/dist/registry/basic/date-picker.d.ts +2 -2
  31. package/dist/registry/basic/ellipsis-text.d.ts +45 -0
  32. package/dist/registry/basic/form.d.ts +23 -0
  33. package/dist/registry/basic/icon-button.d.ts +15 -2
  34. package/dist/registry/basic/item.d.ts +1 -1
  35. package/dist/registry/basic/popover.d.ts +2 -0
  36. package/dist/registry/basic/rating.d.ts +31 -0
  37. package/dist/registry/basic/sidebar.d.ts +72 -0
  38. package/dist/registry/basic/stepper.d.ts +36 -0
  39. package/dist/registry/basic/tag.d.ts +1 -1
  40. package/dist/registry/basic/timeline.d.ts +34 -0
  41. package/dist/spark-design.cjs.js +27 -30
  42. package/dist/spark-design.es.js +6398 -5130
  43. package/dist/sparkdesign.css +1 -1
  44. package/dist/src/components/basic/Accordion/index.d.ts +13 -0
  45. package/dist/src/components/basic/Badge/index.d.ts +13 -0
  46. package/dist/src/components/basic/EllipsisText/index.d.ts +4 -36
  47. package/dist/src/components/basic/Form/index.d.ts +12 -0
  48. package/dist/src/components/basic/Rating/index.d.ts +13 -0
  49. package/dist/src/components/basic/Sidebar/index.d.ts +13 -0
  50. package/dist/src/components/basic/Stepper/index.d.ts +13 -0
  51. package/dist/src/components/basic/Timeline/index.d.ts +13 -0
  52. package/dist/src/components/index.d.ts +12 -4
  53. package/docs/agent/component-selection.md +104 -4
  54. package/docs/agent/prompt-recipes.md +167 -0
  55. package/docs/guides/agent-usage.md +213 -0
  56. package/docs/guides/system-operating-model.md +148 -0
  57. package/package.json +20 -3
  58. package/registry/agent-manifest.json +4040 -67
  59. package/cli/registry/basic/sheet.tsx +0 -18
  60. package/cli/registry/chat/user-question/UserQuestionCard.tsx +0 -198
  61. package/cli/registry/chat/user-question/UserQuestionFooter.tsx +0 -66
  62. package/cli/registry/chat/user-question/UserQuestionHeader.tsx +0 -64
  63. package/cli/registry/chat/user-question/useUserQuestionState.ts +0 -165
  64. package/dist/registry/basic/sheet.d.ts +0 -13
  65. package/dist/registry/chat/user-question/UserQuestionCard.d.ts +0 -36
  66. package/dist/registry/chat/user-question/UserQuestionFooter.d.ts +0 -24
  67. package/dist/registry/chat/user-question/UserQuestionHeader.d.ts +0 -26
  68. package/dist/registry/chat/user-question/useUserQuestionState.d.ts +0 -26
  69. package/dist/src/components/basic/CollapsibleSection/index.d.ts +0 -43
  70. package/dist/src/components/basic/Sheet/index.d.ts +0 -13
  71. package/dist/src/components/chat/Response/StreamingMarkdownBlock.d.ts +0 -12
@@ -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
  }
@@ -14,6 +14,8 @@ export interface PopoverContentProps
14
14
  /** When provided (e.g. from ThemeStyleProvider), used for portal wrapper */
15
15
  dataStyle?: string
16
16
  dataTheme?: string
17
+ /** Popover internal padding; keep default to avoid breaking existing usages */
18
+ contentPadding?: 'default' | 'compact' | 'none'
17
19
  }
18
20
 
19
21
  const PopoverContent = React.forwardRef<
@@ -21,7 +23,16 @@ const PopoverContent = React.forwardRef<
21
23
  PopoverContentProps
22
24
  >(
23
25
  (
24
- { className, align = 'center', sideOffset = 4, children, dataStyle, dataTheme, ...props },
26
+ {
27
+ className,
28
+ align = 'center',
29
+ sideOffset = 4,
30
+ contentPadding = 'default',
31
+ children,
32
+ dataStyle,
33
+ dataTheme,
34
+ ...props
35
+ },
25
36
  ref,
26
37
  ) => {
27
38
  const fromDoc = getThemeFromDocument()
@@ -34,10 +45,16 @@ const PopoverContent = React.forwardRef<
34
45
  <div style={{ display: 'contents' }} {...dataProps}>
35
46
  <PopoverPrimitive.Content
36
47
  ref={ref}
48
+ data-slot="popover-content"
37
49
  align={align}
38
50
  sideOffset={sideOffset}
39
51
  className={cn(
40
- 'z-50 w-72 rounded-lg border border-border-tertiary bg-bg-container p-4 text-sm text-text shadow-md outline-none',
52
+ 'z-50 w-72 rounded-lg border border-border-tertiary bg-bg-container text-sm text-text shadow-md outline-none',
53
+ contentPadding === 'none'
54
+ ? 'p-0'
55
+ : contentPadding === 'compact'
56
+ ? 'p-1'
57
+ : 'p-4',
41
58
  className,
42
59
  )}
43
60
  {...props}
@@ -0,0 +1,161 @@
1
+ import * as React from 'react'
2
+ import { cva, type VariantProps } from 'class-variance-authority'
3
+
4
+ import { cn } from '@/lib/utils'
5
+
6
+ const ratingVariants = cva('inline-flex items-center gap-1', {
7
+ variants: {
8
+ size: {
9
+ sm: '[--rating-size:1rem]',
10
+ md: '[--rating-size:1.25rem]',
11
+ lg: '[--rating-size:1.5rem]',
12
+ },
13
+ },
14
+ defaultVariants: {
15
+ size: 'md',
16
+ },
17
+ })
18
+
19
+ type RatingPrecision = 1 | 0.5
20
+
21
+ export interface RatingProps
22
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'>,
23
+ VariantProps<typeof ratingVariants> {
24
+ value?: number
25
+ defaultValue?: number
26
+ onValueChange?: (value: number) => void
27
+ max?: number
28
+ precision?: RatingPrecision
29
+ readOnly?: boolean
30
+ disabled?: boolean
31
+ showValue?: boolean
32
+ label?: string
33
+ }
34
+
35
+ function clampRating(value: number, max: number, precision: RatingPrecision) {
36
+ const normalized = Math.max(0, Math.min(max, value))
37
+ return Math.round(normalized / precision) * precision
38
+ }
39
+
40
+ function RatingStar({ className }: { className?: string }) {
41
+ return (
42
+ <svg
43
+ aria-hidden="true"
44
+ viewBox="0 0 20 20"
45
+ className={cn('size-[var(--rating-size)]', className)}
46
+ >
47
+ <path d="M10 1.7l2.3 4.7 5.2.8-3.8 3.7.9 5.2-4.6-2.5-4.6 2.5.9-5.2-3.8-3.7 5.2-.8L10 1.7z" />
48
+ </svg>
49
+ )
50
+ }
51
+
52
+ const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
53
+ (
54
+ {
55
+ className,
56
+ size,
57
+ value,
58
+ defaultValue = 0,
59
+ onValueChange,
60
+ max = 5,
61
+ precision = 1,
62
+ readOnly = false,
63
+ disabled = false,
64
+ showValue = false,
65
+ label = 'Rating',
66
+ ...props
67
+ },
68
+ ref
69
+ ) => {
70
+ const [internalValue, setInternalValue] = React.useState(defaultValue)
71
+ const currentValue = clampRating(value ?? internalValue, max, precision)
72
+ const interactive = !readOnly && !disabled
73
+
74
+ const commitValue = (nextValue: number) => {
75
+ const next = clampRating(nextValue, max, precision)
76
+ if (value == null) setInternalValue(next)
77
+ onValueChange?.(next)
78
+ }
79
+
80
+ return (
81
+ <div
82
+ ref={ref}
83
+ role={interactive ? 'radiogroup' : 'img'}
84
+ aria-label={label}
85
+ aria-disabled={disabled || undefined}
86
+ aria-readonly={readOnly || undefined}
87
+ aria-valuenow={!interactive ? currentValue : undefined}
88
+ aria-valuemax={!interactive ? max : undefined}
89
+ data-slot="rating"
90
+ data-disabled={disabled || undefined}
91
+ className={cn(
92
+ ratingVariants({ size }),
93
+ disabled && 'cursor-not-allowed opacity-50',
94
+ className
95
+ )}
96
+ {...props}
97
+ >
98
+ {Array.from({ length: max }, (_, index) => {
99
+ const starValue = index + 1
100
+ const fillRatio = Math.max(0, Math.min(1, currentValue - index))
101
+ const halfValue = starValue - 0.5
102
+ return (
103
+ <span
104
+ key={starValue}
105
+ className="relative inline-flex size-[var(--rating-size)] shrink-0 text-border-tertiary"
106
+ >
107
+ <RatingStar className="fill-current" />
108
+ <span
109
+ aria-hidden="true"
110
+ className="absolute inset-0 overflow-hidden text-warning"
111
+ style={{ width: `${fillRatio * 100}%` }}
112
+ >
113
+ <RatingStar className="fill-current" />
114
+ </span>
115
+ {interactive ? (
116
+ precision === 0.5 ? (
117
+ <span className="absolute inset-0 grid grid-cols-2">
118
+ <button
119
+ type="button"
120
+ role="radio"
121
+ aria-label={`${halfValue} of ${max}`}
122
+ aria-checked={currentValue === halfValue}
123
+ className="cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-border"
124
+ onClick={() => commitValue(halfValue)}
125
+ />
126
+ <button
127
+ type="button"
128
+ role="radio"
129
+ aria-label={`${starValue} of ${max}`}
130
+ aria-checked={currentValue === starValue}
131
+ className="cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-border"
132
+ onClick={() => commitValue(starValue)}
133
+ />
134
+ </span>
135
+ ) : (
136
+ <button
137
+ type="button"
138
+ role="radio"
139
+ aria-label={`${starValue} of ${max}`}
140
+ aria-checked={currentValue === starValue}
141
+ className="absolute inset-0 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-border"
142
+ onClick={() => commitValue(starValue)}
143
+ />
144
+ )
145
+ ) : null}
146
+ </span>
147
+ )
148
+ })}
149
+ {showValue ? (
150
+ <span className="ml-1 text-sm font-medium text-text-secondary">
151
+ {currentValue.toFixed(precision === 0.5 && currentValue % 1 ? 1 : 0)}
152
+ </span>
153
+ ) : null}
154
+ </div>
155
+ )
156
+ }
157
+ )
158
+ Rating.displayName = 'Rating'
159
+
160
+ export { Rating, ratingVariants }
161
+ export type { RatingPrecision }