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
@@ -1,198 +0,0 @@
1
- import type { Dispatch, MutableRefObject, SetStateAction } from 'react'
2
- import { useState } from 'react'
3
- import { cn } from '@/lib/utils'
4
- import { OptionList } from '../../basic/option-list'
5
- import type { OptionItem } from '../../basic/option-list'
6
- import type { UserQuestionItem, UserQuestionLabels } from './types'
7
-
8
- export interface UserQuestionCardProps {
9
- question: UserQuestionItem
10
- qIndex: number
11
- labels: Required<UserQuestionLabels>
12
- answers: Record<string, string[]>
13
- customInputs: Record<string, string>
14
- focusedOptionIndex: number
15
- isCurrentQuestion: boolean
16
- isSubmitting: boolean
17
- allQuestionsAnswered: boolean
18
- questionsLength: number
19
- questionRefs: MutableRefObject<Map<number, HTMLDivElement>>
20
- customInputRefs: MutableRefObject<Map<string, HTMLInputElement>>
21
- setCurrentQuestionIndex: (i: number) => void
22
- setFocusedOptionIndex: (i: number) => void
23
- setAnswers: Dispatch<SetStateAction<Record<string, string[]>>>
24
- setCustomInputs: Dispatch<SetStateAction<Record<string, string>>>
25
- getSelectedOptionIndex: (i: number) => number
26
- shouldScrollToQuestionRef: MutableRefObject<boolean>
27
- onOptionClick: (questionText: string, optionLabel: string, questionIndex: number) => void
28
- onContinue: () => void
29
- }
30
-
31
- export function UserQuestionCard({
32
- question,
33
- qIndex,
34
- labels,
35
- answers,
36
- customInputs,
37
- focusedOptionIndex,
38
- isCurrentQuestion,
39
- isSubmitting,
40
- allQuestionsAnswered,
41
- questionsLength,
42
- questionRefs,
43
- customInputRefs,
44
- setCurrentQuestionIndex,
45
- setFocusedOptionIndex,
46
- setAnswers,
47
- setCustomInputs,
48
- getSelectedOptionIndex,
49
- shouldScrollToQuestionRef,
50
- onOptionClick,
51
- onContinue,
52
- }: UserQuestionCardProps) {
53
- const questionOptions = question.options || []
54
- const [isInputFocused, setIsInputFocused] = useState(false)
55
- const hasInputValue = !!(customInputs[question.question]?.trim())
56
- const showUnderline = isInputFocused || hasInputValue
57
-
58
- return (
59
- <div
60
- key={question.question}
61
- ref={(el) => {
62
- if (el) questionRefs.current.set(qIndex, el)
63
- else questionRefs.current.delete(qIndex)
64
- }}
65
- className={cn(
66
- 'transition-opacity duration-200',
67
- isCurrentQuestion ? 'opacity-100' : 'opacity-40'
68
- )}
69
- >
70
- <div className="flex items-start gap-3 pl-2 mb-3">
71
- <div className="flex-shrink-0 w-5 flex items-center justify-center text-sm leading-sm font-medium text-text-tertiary">
72
- {qIndex + 1}.
73
- </div>
74
- <div className="text-sm leading-sm font-medium text-text min-w-0 flex-1">
75
- {question.question}
76
- </div>
77
- </div>
78
- <OptionList
79
- items={questionOptions.map((opt) => ({
80
- id: opt.label,
81
- label: opt.label,
82
- description: opt.description,
83
- }))}
84
- selectedIds={answers[question.question]?.map((l) => l) || []}
85
- focusedId={
86
- isCurrentQuestion ? questionOptions[focusedOptionIndex]?.label : undefined
87
- }
88
- disabled={isSubmitting || !isCurrentQuestion}
89
- onItemClick={(item: OptionItem, index: number) => {
90
- if (!isCurrentQuestion) {
91
- setCurrentQuestionIndex(qIndex)
92
- setFocusedOptionIndex(getSelectedOptionIndex(qIndex))
93
- return
94
- }
95
- onOptionClick(question.question, item.label, qIndex)
96
- setFocusedOptionIndex(index)
97
- }}
98
- />
99
- {question.allowCustomInput !== false && (
100
- <div
101
- className={cn(
102
- 'w-full flex gap-3 pl-2 mt-3 transition-colors',
103
- !isCurrentQuestion && 'opacity-40',
104
- isSubmitting && 'opacity-50'
105
- )}
106
- >
107
- <div
108
- className={cn(
109
- 'flex-shrink-0 w-5 h-5 rounded-full flex items-center justify-center text-xs leading-xs font-medium transition-colors',
110
- (isCurrentQuestion && focusedOptionIndex === questionOptions.length) ||
111
- customInputs[question.question]?.trim()
112
- ? 'bg-primary-active text-text-on-primary'
113
- : 'bg-fill-secondary text-text-tertiary'
114
- )}
115
- >
116
- {questionOptions.length + 1}
117
- </div>
118
- <div className="flex-1 min-w-0 flex flex-col">
119
- <div className="h-5 flex items-center">
120
- <input
121
- ref={(el) => {
122
- if (el) customInputRefs.current.set(question.question, el)
123
- else customInputRefs.current.delete(question.question)
124
- }}
125
- type="text"
126
- value={customInputs[question.question] || ''}
127
- style={{ outline: 'none', outlineOffset: 0, boxShadow: 'none', border: 'none' }}
128
- onChange={(e) => {
129
- const value = e.target.value
130
- setCustomInputs((prev) => ({ ...prev, [question.question]: value }))
131
- if (!question.multiSelect && value.trim()) {
132
- setAnswers((prev) => ({
133
- ...prev,
134
- [question.question]: [],
135
- }))
136
- }
137
- }}
138
- onFocus={() => {
139
- setIsInputFocused(true)
140
- if (isCurrentQuestion) {
141
- setFocusedOptionIndex(questionOptions.length)
142
- if (!question.multiSelect) {
143
- setAnswers((prev) => ({
144
- ...prev,
145
- [question.question]: [],
146
- }))
147
- }
148
- }
149
- }}
150
- onBlur={() => setIsInputFocused(false)}
151
- onKeyDown={(e) => {
152
- if (e.key === 'Enter' && !e.shiftKey) {
153
- e.preventDefault()
154
- e.stopPropagation()
155
- const currentCustomInput = customInputs[question.question]?.trim()
156
- const currentSelectedOptions = answers[question.question] || []
157
- const hasAnswer =
158
- currentSelectedOptions.length > 0 || !!currentCustomInput
159
- if (hasAnswer) {
160
- if (allQuestionsAnswered) {
161
- onContinue()
162
- } else if (qIndex < questionsLength - 1) {
163
- const nextIndex = qIndex + 1
164
- shouldScrollToQuestionRef.current = true
165
- setCurrentQuestionIndex(nextIndex)
166
- setFocusedOptionIndex(0)
167
- } else {
168
- onContinue()
169
- }
170
- }
171
- }
172
- }}
173
- placeholder={labels.customInputPlaceholder}
174
- disabled={isSubmitting || !isCurrentQuestion}
175
- className={cn(
176
- 'w-full h-full py-0 bg-transparent text-sm leading-sm font-medium text-text',
177
- 'placeholder:text-text-tertiary placeholder:font-normal',
178
- 'border-0 rounded-none outline-none outline-offset-0',
179
- 'focus:outline-none focus:outline-offset-0',
180
- 'focus-visible:outline-none focus-visible:outline-offset-0',
181
- 'ring-0 focus:ring-0 focus:ring-offset-0 focus-visible:ring-0 focus-visible:ring-offset-0 shadow-none',
182
- 'disabled:cursor-not-allowed'
183
- )}
184
- />
185
- </div>
186
- <div
187
- className="mt-1.5 overflow-hidden origin-left transition-[width] duration-200 ease-out"
188
- style={{ width: showUnderline ? '100%' : '0%' }}
189
- aria-hidden
190
- >
191
- <div className="h-px bg-[var(--color-border-tertiary)]" />
192
- </div>
193
- </div>
194
- </div>
195
- )}
196
- </div>
197
- )
198
- }
@@ -1,66 +0,0 @@
1
- import type { ReactNode } from 'react'
2
- import { Button } from '../../basic/button'
3
- import type { UserQuestionLabels } from './types'
4
-
5
- export interface UserQuestionFooterProps {
6
- labels: Required<UserQuestionLabels>
7
- sparklingIcon: ReactNode
8
- isSubmitting: boolean
9
- hasCustomText: boolean
10
- allQuestionsAnswered: boolean
11
- onRecommend: () => void
12
- onSkip: () => void
13
- onContinue: () => void
14
- }
15
-
16
- export function UserQuestionFooter({
17
- labels,
18
- sparklingIcon,
19
- isSubmitting,
20
- hasCustomText,
21
- allQuestionsAnswered,
22
- onRecommend,
23
- onSkip,
24
- onContinue,
25
- }: UserQuestionFooterProps) {
26
- return (
27
- <div className="flex items-center justify-between px-3 py-2">
28
- <Button
29
- variant="ghost"
30
- size="sm"
31
- onClick={onRecommend}
32
- disabled={isSubmitting}
33
- >
34
- {sparklingIcon}
35
- {labels.recommend}
36
- </Button>
37
- <div className="flex items-center gap-2">
38
- {!allQuestionsAnswered && (
39
- <Button
40
- variant="ghost"
41
- size="sm"
42
- onClick={onSkip}
43
- disabled={isSubmitting}
44
- >
45
- {labels.skipAll}
46
- </Button>
47
- )}
48
- <Button
49
- size="sm"
50
- variant="secondary"
51
- onClick={onContinue}
52
- disabled={isSubmitting || hasCustomText || !allQuestionsAnswered}
53
- >
54
- {isSubmitting ? (
55
- labels.sending
56
- ) : (
57
- <>
58
- {allQuestionsAnswered ? labels.submit : labels.continue}
59
- <span className="opacity-70"> ↵</span>
60
- </>
61
- )}
62
- </Button>
63
- </div>
64
- </div>
65
- )
66
- }
@@ -1,64 +0,0 @@
1
- import type { ReactNode } from 'react'
2
- import { IconButton } from '../../basic/icon-button'
3
- import type { UserQuestionItem } from './types'
4
- import type { UserQuestionLabels } from './types'
5
-
6
- export interface UserQuestionHeaderProps {
7
- currentQuestion: UserQuestionItem | undefined
8
- questionsLength: number
9
- labels: Required<UserQuestionLabels>
10
- chat4Icon: ReactNode
11
- arrowUpSIcon: ReactNode
12
- arrowDownSIcon: ReactNode
13
- currentQuestionIndex: number
14
- onPrevious: () => void
15
- onNext: () => void
16
- }
17
-
18
- export function UserQuestionHeader({
19
- currentQuestion,
20
- questionsLength,
21
- labels,
22
- chat4Icon,
23
- arrowUpSIcon,
24
- arrowDownSIcon,
25
- currentQuestionIndex,
26
- onPrevious,
27
- onNext,
28
- }: UserQuestionHeaderProps) {
29
- return (
30
- <div className="flex items-center justify-between mb-2 -mt-2 px-3 pt-2 pb-2 rounded-t-xl bg-fill-tertiary">
31
- <div className="flex items-center gap-1.5">
32
- {chat4Icon}
33
- <span className="text-xs leading-xs text-text-tertiary">
34
- {currentQuestion?.header || labels.questionHeader}
35
- </span>
36
- <span className="text-text-tertiary/50">•</span>
37
- <span className="text-xs leading-xs text-text-tertiary">
38
- {currentQuestion?.multiSelect ? labels.multiSelect : labels.singleSelect}
39
- </span>
40
- </div>
41
- {questionsLength > 1 && (
42
- <div className="flex items-center gap-1">
43
- <IconButton
44
- size="sm"
45
- variant="ghost"
46
- onClick={onPrevious}
47
- disabled={currentQuestionIndex === 0}
48
- icon={arrowUpSIcon}
49
- />
50
- <span className="text-xs leading-xs text-text-tertiary px-1">
51
- {currentQuestionIndex + 1} / {questionsLength}
52
- </span>
53
- <IconButton
54
- size="sm"
55
- variant="ghost"
56
- onClick={onNext}
57
- disabled={currentQuestionIndex === questionsLength - 1}
58
- icon={arrowDownSIcon}
59
- />
60
- </div>
61
- )}
62
- </div>
63
- )
64
- }
@@ -1,165 +0,0 @@
1
- import { useState, useEffect, useCallback, useRef, useImperativeHandle } from 'react'
2
- import type { Ref } from 'react'
3
- import type { UserQuestionItem, UserQuestionHandle } from './types'
4
-
5
- export interface UseUserQuestionStateProps {
6
- questions: UserQuestionItem[]
7
- resetKey?: string
8
- onAnswer: (answers: Record<string, string>) => void
9
- ref: Ref<UserQuestionHandle | null>
10
- }
11
-
12
- export function useUserQuestionState({
13
- questions,
14
- resetKey,
15
- ref,
16
- }: UseUserQuestionStateProps) {
17
- const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0)
18
- const [answers, setAnswers] = useState<Record<string, string[]>>({})
19
- const [customInputs, setCustomInputs] = useState<Record<string, string>>({})
20
- const [focusedOptionIndex, setFocusedOptionIndex] = useState(0)
21
- const [isSubmitting, setIsSubmitting] = useState(false)
22
- const [maxQuestionHeight, setMaxQuestionHeight] = useState<number | undefined>(undefined)
23
-
24
- const prevIndexRef = useRef(currentQuestionIndex)
25
- const prevResetKeyRef = useRef(resetKey)
26
- const scrollContainerRef = useRef<HTMLDivElement>(null)
27
- const questionRefs = useRef<Map<number, HTMLDivElement>>(new Map())
28
- const currentIndexRef = useRef(currentQuestionIndex)
29
- const isProgrammaticScrollRef = useRef(false)
30
- const shouldScrollToQuestionRef = useRef(false)
31
- const customInputRefs = useRef<Map<string, HTMLInputElement>>(new Map())
32
-
33
- const getSelectedOptionIndex = useCallback(
34
- (questionIndex: number) => {
35
- const question = questions[questionIndex]
36
- if (!question) return 0
37
- const selectedLabels = answers[question.question] || []
38
- if (selectedLabels.length === 0) return 0
39
- const options = question.options || []
40
- const selectedIndex = options.findIndex((opt) => selectedLabels.includes(opt.label))
41
- return selectedIndex >= 0 ? selectedIndex : 0
42
- },
43
- [questions, answers]
44
- )
45
-
46
- useImperativeHandle(
47
- ref,
48
- () => ({
49
- getAnswers: () => {
50
- const formattedAnswers: Record<string, string> = {}
51
- for (const question of questions) {
52
- const selected = answers[question.question] || []
53
- const customInput = customInputs[question.question]?.trim()
54
- const allParts: string[] = []
55
- if (selected.length > 0) allParts.push(...selected)
56
- if (customInput) allParts.push(customInput)
57
- if (allParts.length > 0) {
58
- formattedAnswers[question.question] = allParts.join(', ')
59
- }
60
- }
61
- return formattedAnswers
62
- },
63
- }),
64
- [answers, customInputs, questions]
65
- )
66
-
67
- useEffect(() => {
68
- if (prevResetKeyRef.current !== resetKey) {
69
- setIsSubmitting(false)
70
- setCurrentQuestionIndex(0)
71
- currentIndexRef.current = 0
72
- setAnswers({})
73
- setCustomInputs({})
74
- setFocusedOptionIndex(0)
75
- prevResetKeyRef.current = resetKey
76
- questionRefs.current.clear()
77
- customInputRefs.current.clear()
78
- }
79
- }, [resetKey])
80
-
81
- useEffect(() => {
82
- if (prevIndexRef.current !== currentQuestionIndex) {
83
- currentIndexRef.current = currentQuestionIndex
84
- if (shouldScrollToQuestionRef.current) {
85
- isProgrammaticScrollRef.current = true
86
- const questionEl = questionRefs.current.get(currentQuestionIndex)
87
- const container = scrollContainerRef.current
88
- if (questionEl && container) {
89
- const handleScrollEnd = () => {
90
- isProgrammaticScrollRef.current = false
91
- shouldScrollToQuestionRef.current = false
92
- container.removeEventListener('scrollend', handleScrollEnd)
93
- }
94
- container.addEventListener('scrollend', handleScrollEnd, { once: true })
95
- questionEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
96
- } else {
97
- isProgrammaticScrollRef.current = false
98
- shouldScrollToQuestionRef.current = false
99
- }
100
- }
101
- prevIndexRef.current = currentQuestionIndex
102
- }
103
- }, [currentQuestionIndex])
104
-
105
- useEffect(() => {
106
- const timer = setTimeout(() => {
107
- let maxHeight = 0
108
- questionRefs.current.forEach((el) => {
109
- const h = el.getBoundingClientRect().height
110
- if (h > maxHeight) maxHeight = h
111
- })
112
- if (maxHeight > 0) setMaxQuestionHeight(maxHeight)
113
- }, 50)
114
- return () => clearTimeout(timer)
115
- }, [questions, resetKey])
116
-
117
- useEffect(() => {
118
- const container = scrollContainerRef.current
119
- if (!container) return
120
- const observer = new IntersectionObserver(
121
- (entries) => {
122
- if (isProgrammaticScrollRef.current) return
123
- let maxRatio = 0
124
- let visibleIndex = currentIndexRef.current
125
- entries.forEach((entry) => {
126
- if (entry.isIntersecting && entry.intersectionRatio > maxRatio) {
127
- maxRatio = entry.intersectionRatio
128
- questionRefs.current.forEach((el, index) => {
129
- if (el === entry.target) visibleIndex = index
130
- })
131
- }
132
- })
133
- if (visibleIndex !== currentIndexRef.current && maxRatio > 0.5) {
134
- currentIndexRef.current = visibleIndex
135
- setCurrentQuestionIndex(visibleIndex)
136
- setFocusedOptionIndex(getSelectedOptionIndex(visibleIndex))
137
- }
138
- },
139
- { root: container, threshold: [0, 0.25, 0.5, 0.75, 1] }
140
- )
141
- questionRefs.current.forEach((el) => {
142
- if (el?.isConnected) observer.observe(el)
143
- })
144
- return () => observer.disconnect()
145
- }, [questions, getSelectedOptionIndex])
146
-
147
- return {
148
- currentQuestionIndex,
149
- setCurrentQuestionIndex,
150
- answers,
151
- setAnswers,
152
- customInputs,
153
- setCustomInputs,
154
- focusedOptionIndex,
155
- setFocusedOptionIndex,
156
- isSubmitting,
157
- setIsSubmitting,
158
- maxQuestionHeight,
159
- scrollContainerRef,
160
- questionRefs,
161
- shouldScrollToQuestionRef,
162
- customInputRefs,
163
- getSelectedOptionIndex,
164
- }
165
- }
@@ -1,36 +0,0 @@
1
- /**
2
- * [WHO]: Public exports from this file (see implementation below).
3
- * [FROM]: See the import block immediately after this header.
4
- * [TO]: sparkdesign package consumers; output of CLI `add` when applicable.
5
- * [HERE]: registry/chat/user-question/UserQuestionCard.tsx — Spark Design source; keep aligned with the main library.
6
- *
7
- * [PROTOCOL]:
8
- * 1. Keep this P3 header in sync when the public contract changes.
9
- * 2. Update module AGENTS.md (P2) and root AGENTS.md (P1) when boundaries change.
10
- * 3. Follow design tokens and explicit type exports.
11
- */
12
- import type { Dispatch, MutableRefObject, SetStateAction } from 'react';
13
- import type { UserQuestionItem, UserQuestionLabels } from './types';
14
- export interface UserQuestionCardProps {
15
- question: UserQuestionItem;
16
- qIndex: number;
17
- labels: Required<UserQuestionLabels>;
18
- answers: Record<string, string[]>;
19
- customInputs: Record<string, string>;
20
- focusedOptionIndex: number;
21
- isCurrentQuestion: boolean;
22
- isSubmitting: boolean;
23
- allQuestionsAnswered: boolean;
24
- questionsLength: number;
25
- questionRefs: MutableRefObject<Map<number, HTMLDivElement>>;
26
- customInputRefs: MutableRefObject<Map<string, HTMLInputElement>>;
27
- setCurrentQuestionIndex: (i: number) => void;
28
- setFocusedOptionIndex: (i: number) => void;
29
- setAnswers: Dispatch<SetStateAction<Record<string, string[]>>>;
30
- setCustomInputs: Dispatch<SetStateAction<Record<string, string>>>;
31
- getSelectedOptionIndex: (i: number) => number;
32
- shouldScrollToQuestionRef: MutableRefObject<boolean>;
33
- onOptionClick: (questionText: string, optionLabel: string, questionIndex: number) => void;
34
- onContinue: () => void;
35
- }
36
- export declare function UserQuestionCard({ question, qIndex, labels, answers, customInputs, focusedOptionIndex, isCurrentQuestion, isSubmitting, allQuestionsAnswered, questionsLength, questionRefs, customInputRefs, setCurrentQuestionIndex, setFocusedOptionIndex, setAnswers, setCustomInputs, getSelectedOptionIndex, shouldScrollToQuestionRef, onOptionClick, onContinue, }: UserQuestionCardProps): import("react/jsx-runtime").JSX.Element;
@@ -1,24 +0,0 @@
1
- /**
2
- * [WHO]: Public exports from this file (see implementation below).
3
- * [FROM]: See the import block immediately after this header.
4
- * [TO]: sparkdesign package consumers; output of CLI `add` when applicable.
5
- * [HERE]: registry/chat/user-question/UserQuestionFooter.tsx — Spark Design source; keep aligned with the main library.
6
- *
7
- * [PROTOCOL]:
8
- * 1. Keep this P3 header in sync when the public contract changes.
9
- * 2. Update module AGENTS.md (P2) and root AGENTS.md (P1) when boundaries change.
10
- * 3. Follow design tokens and explicit type exports.
11
- */
12
- import type { ReactNode } from 'react';
13
- import type { UserQuestionLabels } from './types';
14
- export interface UserQuestionFooterProps {
15
- labels: Required<UserQuestionLabels>;
16
- sparklingIcon: ReactNode;
17
- isSubmitting: boolean;
18
- hasCustomText: boolean;
19
- allQuestionsAnswered: boolean;
20
- onRecommend: () => void;
21
- onSkip: () => void;
22
- onContinue: () => void;
23
- }
24
- export declare function UserQuestionFooter({ labels, sparklingIcon, isSubmitting, hasCustomText, allQuestionsAnswered, onRecommend, onSkip, onContinue, }: UserQuestionFooterProps): import("react/jsx-runtime").JSX.Element;
@@ -1,26 +0,0 @@
1
- /**
2
- * [WHO]: Public exports from this file (see implementation below).
3
- * [FROM]: See the import block immediately after this header.
4
- * [TO]: sparkdesign package consumers; output of CLI `add` when applicable.
5
- * [HERE]: registry/chat/user-question/UserQuestionHeader.tsx — Spark Design source; keep aligned with the main library.
6
- *
7
- * [PROTOCOL]:
8
- * 1. Keep this P3 header in sync when the public contract changes.
9
- * 2. Update module AGENTS.md (P2) and root AGENTS.md (P1) when boundaries change.
10
- * 3. Follow design tokens and explicit type exports.
11
- */
12
- import type { ReactNode } from 'react';
13
- import type { UserQuestionItem } from './types';
14
- import type { UserQuestionLabels } from './types';
15
- export interface UserQuestionHeaderProps {
16
- currentQuestion: UserQuestionItem | undefined;
17
- questionsLength: number;
18
- labels: Required<UserQuestionLabels>;
19
- chat4Icon: ReactNode;
20
- arrowUpSIcon: ReactNode;
21
- arrowDownSIcon: ReactNode;
22
- currentQuestionIndex: number;
23
- onPrevious: () => void;
24
- onNext: () => void;
25
- }
26
- export declare function UserQuestionHeader({ currentQuestion, questionsLength, labels, chat4Icon, arrowUpSIcon, arrowDownSIcon, currentQuestionIndex, onPrevious, onNext, }: UserQuestionHeaderProps): import("react/jsx-runtime").JSX.Element;
@@ -1,26 +0,0 @@
1
- import type { Ref } from 'react';
2
- import type { UserQuestionItem, UserQuestionHandle } from './types';
3
- export interface UseUserQuestionStateProps {
4
- questions: UserQuestionItem[];
5
- resetKey?: string;
6
- onAnswer: (answers: Record<string, string>) => void;
7
- ref: Ref<UserQuestionHandle | null>;
8
- }
9
- export declare function useUserQuestionState({ questions, resetKey, ref, }: UseUserQuestionStateProps): {
10
- currentQuestionIndex: number;
11
- setCurrentQuestionIndex: import("react").Dispatch<import("react").SetStateAction<number>>;
12
- answers: Record<string, string[]>;
13
- setAnswers: import("react").Dispatch<import("react").SetStateAction<Record<string, string[]>>>;
14
- customInputs: Record<string, string>;
15
- setCustomInputs: import("react").Dispatch<import("react").SetStateAction<Record<string, string>>>;
16
- focusedOptionIndex: number;
17
- setFocusedOptionIndex: import("react").Dispatch<import("react").SetStateAction<number>>;
18
- isSubmitting: boolean;
19
- setIsSubmitting: import("react").Dispatch<import("react").SetStateAction<boolean>>;
20
- maxQuestionHeight: number | undefined;
21
- scrollContainerRef: import("react").RefObject<HTMLDivElement | null>;
22
- questionRefs: import("react").RefObject<Map<number, HTMLDivElement>>;
23
- shouldScrollToQuestionRef: import("react").RefObject<boolean>;
24
- customInputRefs: import("react").RefObject<Map<string, HTMLInputElement>>;
25
- getSelectedOptionIndex: (questionIndex: number) => number;
26
- };
@@ -1,43 +0,0 @@
1
- /**
2
- * [WHO]: Public exports from this file (see implementation below).
3
- * [FROM]: See the import block immediately after this header.
4
- * [TO]: sparkdesign package consumers; output of CLI `add` when applicable.
5
- * [HERE]: src/components/basic/CollapsibleSection/index.tsx — Spark Design source; keep aligned with the main library.
6
- *
7
- * [PROTOCOL]:
8
- * 1. Keep this P3 header in sync when the public contract changes.
9
- * 2. Update module AGENTS.md (P2) and root AGENTS.md (P1) when boundaries change.
10
- * 3. Follow design tokens and explicit type exports.
11
- */
12
- import { type ReactNode } from 'react';
13
- export interface CollapsibleSectionProps {
14
- /** 区块标题 */
15
- title: string;
16
- /** 展开时的描述文案(可选) */
17
- description?: string;
18
- /** 展开时的内容区域 */
19
- children?: ReactNode;
20
- /** 是否展开 */
21
- open: boolean;
22
- /** 切换展开/收起 */
23
- onToggle: () => void;
24
- /** 是否为最后一项(最后一项无底部分割线) */
25
- isLast?: boolean;
26
- /** 自定义 className */
27
- className?: string;
28
- /** 无障碍:内容区 id,不传则自动生成 */
29
- id?: string;
30
- /**
31
- * 内容区最大高度(px),超出时内部滚动并显示上下渐变遮罩。
32
- * 不传则无高度限制、不产生内部滚动。
33
- */
34
- contentMaxHeight?: number;
35
- /**
36
- * 吸底节点(如「查看全部」按钮)。与 contentMaxHeight 同时传入时,内容区为「可滚动列表 + 底部固定 footer」布局。
37
- */
38
- footer?: ReactNode;
39
- }
40
- export declare function CollapsibleSection({ title, description, children, open, onToggle, isLast, className, id: idProp, contentMaxHeight, footer, }: CollapsibleSectionProps): import("react/jsx-runtime").JSX.Element;
41
- export declare namespace CollapsibleSection {
42
- var displayName: string;
43
- }
@@ -1,12 +0,0 @@
1
- /**
2
- * [WHO]: Public exports from this file (see implementation below).
3
- * [FROM]: See the import block immediately after this header.
4
- * [TO]: sparkdesign package consumers; output of CLI `add` when applicable.
5
- * [HERE]: src/components/chat/Response/StreamingMarkdownBlock.tsx — Spark Design source; keep aligned with the main library.
6
- *
7
- * [PROTOCOL]:
8
- * 1. Keep this P3 header in sync when the public contract changes.
9
- * 2. Update module AGENTS.md (P2) and root AGENTS.md (P1) when boundaries change.
10
- * 3. Follow design tokens and explicit type exports.
11
- */
12
- export { StreamingMarkdownBlock, type StreamingMarkdownBlockProps, } from '../../../../registry/chat/streaming-markdown-block';