sparkdesign 0.4.8 → 0.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT_COMPONENT_LIBRARY_QUICKREF.md +117 -0
- package/AI_README.md +7 -2
- package/README.md +4 -1
- package/cli/registry/AGENTS.md +1 -1
- package/cli/registry/agent-manifest.json +4040 -67
- package/cli/registry/basic/accordion.tsx +79 -0
- package/cli/registry/basic/badge.tsx +49 -0
- package/cli/registry/basic/button.tsx +19 -14
- package/cli/registry/basic/calendar.tsx +16 -16
- package/cli/registry/basic/collapsible-card.tsx +10 -1
- package/cli/registry/basic/combobox.tsx +11 -2
- package/cli/registry/basic/date-picker.tsx +3 -2
- package/cli/registry/basic/ellipsis-text.tsx +151 -0
- package/cli/registry/basic/form.tsx +186 -0
- package/cli/registry/basic/icon-button.tsx +12 -4
- package/cli/registry/basic/popover.tsx +19 -2
- package/cli/registry/basic/rating.tsx +161 -0
- package/cli/registry/basic/sidebar.tsx +665 -0
- package/cli/registry/basic/sonner.tsx +10 -10
- package/cli/registry/basic/stepper.tsx +163 -0
- package/cli/registry/basic/timeline.tsx +129 -0
- package/cli/registry/chat/chat-input/compound.tsx +1 -0
- package/cli/registry/chat/permission-card.tsx +1 -1
- package/cli/registry/chat/user-question/compound.tsx +2 -0
- package/cli/registry/meta.json +171 -13
- package/dist/registry/basic/accordion.d.ts +15 -0
- package/dist/registry/basic/badge.d.ts +23 -0
- package/dist/registry/basic/calendar.d.ts +1 -1
- package/dist/registry/basic/combobox.d.ts +2 -1
- package/dist/registry/basic/date-picker.d.ts +2 -2
- package/dist/registry/basic/ellipsis-text.d.ts +45 -0
- package/dist/registry/basic/form.d.ts +23 -0
- package/dist/registry/basic/icon-button.d.ts +15 -2
- package/dist/registry/basic/item.d.ts +1 -1
- package/dist/registry/basic/popover.d.ts +2 -0
- package/dist/registry/basic/rating.d.ts +31 -0
- package/dist/registry/basic/sidebar.d.ts +72 -0
- package/dist/registry/basic/stepper.d.ts +36 -0
- package/dist/registry/basic/tag.d.ts +1 -1
- package/dist/registry/basic/timeline.d.ts +34 -0
- package/dist/spark-design.cjs.js +27 -30
- package/dist/spark-design.es.js +6398 -5130
- package/dist/sparkdesign.css +1 -1
- package/dist/src/components/basic/Accordion/index.d.ts +13 -0
- package/dist/src/components/basic/Badge/index.d.ts +13 -0
- package/dist/src/components/basic/EllipsisText/index.d.ts +4 -36
- package/dist/src/components/basic/Form/index.d.ts +12 -0
- package/dist/src/components/basic/Rating/index.d.ts +13 -0
- package/dist/src/components/basic/Sidebar/index.d.ts +13 -0
- package/dist/src/components/basic/Stepper/index.d.ts +13 -0
- package/dist/src/components/basic/Timeline/index.d.ts +13 -0
- package/dist/src/components/index.d.ts +12 -4
- package/docs/agent/component-selection.md +104 -4
- package/docs/agent/prompt-recipes.md +167 -0
- package/docs/guides/agent-usage.md +213 -0
- package/docs/guides/system-operating-model.md +148 -0
- package/package.json +20 -3
- package/registry/agent-manifest.json +4040 -67
- package/cli/registry/basic/sheet.tsx +0 -18
- package/cli/registry/chat/user-question/UserQuestionCard.tsx +0 -198
- package/cli/registry/chat/user-question/UserQuestionFooter.tsx +0 -66
- package/cli/registry/chat/user-question/UserQuestionHeader.tsx +0 -64
- package/cli/registry/chat/user-question/useUserQuestionState.ts +0 -165
- package/dist/registry/basic/sheet.d.ts +0 -13
- package/dist/registry/chat/user-question/UserQuestionCard.d.ts +0 -36
- package/dist/registry/chat/user-question/UserQuestionFooter.d.ts +0 -24
- package/dist/registry/chat/user-question/UserQuestionHeader.d.ts +0 -26
- package/dist/registry/chat/user-question/useUserQuestionState.d.ts +0 -26
- package/dist/src/components/basic/CollapsibleSection/index.d.ts +0 -43
- package/dist/src/components/basic/Sheet/index.d.ts +0 -13
- package/dist/src/components/chat/Response/StreamingMarkdownBlock.d.ts +0 -12
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
Drawer as Sheet,
|
|
3
|
-
DrawerTrigger as SheetTrigger,
|
|
4
|
-
DrawerClose as SheetClose,
|
|
5
|
-
DrawerPortal as SheetPortal,
|
|
6
|
-
DrawerOverlay as SheetOverlay,
|
|
7
|
-
DrawerContent as SheetContent,
|
|
8
|
-
DrawerHeader as SheetHeader,
|
|
9
|
-
DrawerFooter as SheetFooter,
|
|
10
|
-
DrawerTitle as SheetTitle,
|
|
11
|
-
DrawerDescription as SheetDescription,
|
|
12
|
-
drawerVariants as sheetVariants,
|
|
13
|
-
} from './drawer'
|
|
14
|
-
export type {
|
|
15
|
-
DrawerContentProps as SheetContentProps,
|
|
16
|
-
DrawerHeaderProps as SheetHeaderProps,
|
|
17
|
-
DrawerFooterProps as SheetFooterProps,
|
|
18
|
-
} from './drawer'
|
|
@@ -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,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* [WHO]: Sheet — compatibility alias for Drawer using shadcn-style naming.
|
|
3
|
-
* [FROM]: registry/basic/drawer.
|
|
4
|
-
* [TO]: sparkdesign package consumers; output of CLI `add sheet` when registered.
|
|
5
|
-
* [HERE]: registry/basic/sheet.tsx — Spark Design source; keep aligned with Drawer semantics.
|
|
6
|
-
*
|
|
7
|
-
* [PROTOCOL]:
|
|
8
|
-
* 1. Keep Sheet as a thin compatibility layer; Drawer remains the canonical semantic component.
|
|
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 { Drawer as Sheet, DrawerTrigger as SheetTrigger, DrawerClose as SheetClose, DrawerPortal as SheetPortal, DrawerOverlay as SheetOverlay, DrawerContent as SheetContent, DrawerHeader as SheetHeader, DrawerFooter as SheetFooter, DrawerTitle as SheetTitle, DrawerDescription as SheetDescription, drawerVariants as sheetVariants, } from './drawer';
|
|
13
|
-
export type { DrawerContentProps as SheetContentProps, DrawerHeaderProps as SheetHeaderProps, DrawerFooterProps as SheetFooterProps, } from './drawer';
|
|
@@ -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
|
-
};
|