sparkdesign 0.3.1 → 0.3.3

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 (104) hide show
  1. package/README.md +7 -5
  2. package/cli/dist/index.js +0 -0
  3. package/cli/dist/utils/tokens.js +103 -17
  4. package/cli/registry/basic/button.test.tsx +333 -0
  5. package/cli/registry/chat/{question-part.tsx → ask-user-part.tsx} +4 -4
  6. package/cli/registry/chat/{browser-use-part.tsx → browser-action-part.tsx} +6 -6
  7. package/cli/registry/chat/{suggestion-part.tsx → hint-banner.tsx} +4 -4
  8. package/cli/registry/chat/markdown.test.tsx +387 -0
  9. package/cli/registry/chat/{reasoning-step.tsx → reasoning-step/compound.tsx} +163 -185
  10. package/cli/registry/chat/reasoning-step/context.tsx +114 -0
  11. package/cli/registry/chat/reasoning-step/index.tsx +45 -0
  12. package/cli/registry/chat/reasoning-step/types.ts +109 -0
  13. package/cli/registry/chat/response/compound.tsx +210 -0
  14. package/cli/registry/chat/{response.tsx → response/context.tsx} +65 -136
  15. package/cli/registry/chat/response/index.tsx +87 -0
  16. package/cli/registry/chat/response/types.ts +123 -0
  17. package/cli/registry/chat/thinking-indicator.test.tsx +244 -0
  18. package/cli/registry/chat/tool-invocation-card.test.tsx +346 -0
  19. package/cli/registry/chat/{request.tsx → user-message.tsx} +3 -3
  20. package/cli/registry/chat/user-question/compound.tsx +324 -0
  21. package/cli/registry/chat/user-question/context.tsx +456 -0
  22. package/cli/registry/chat/user-question/index.tsx +71 -316
  23. package/cli/registry/chat/user-question/useUserQuestionKeyboard.ts +5 -6
  24. package/cli/registry/tokens/index.css +31 -0
  25. package/cli/registry/tokens/scale/computed.css +103 -0
  26. package/cli/registry/tokens/scale/config.css +110 -0
  27. package/cli/registry/tokens/scale/index.css +30 -0
  28. package/cli/registry/tokens/scale/presets/compact.css +30 -0
  29. package/cli/registry/tokens/scale/presets/dense.css +64 -0
  30. package/cli/registry/tokens/scale/presets/sharp.css +40 -0
  31. package/cli/registry/tokens/scale/presets/soft.css +16 -0
  32. package/cli/registry/tokens/scale.css +12 -298
  33. package/cli/registry/tokens/scrollbar-utility.css +35 -0
  34. package/cli/registry/tokens/themes/dark-parchment.css +132 -0
  35. package/cli/registry/tokens/themes/dark-qoder.css +132 -0
  36. package/cli/registry/tokens/themes/light-parchment.css +123 -0
  37. package/cli/registry/tokens/themes/light-qoder.css +131 -0
  38. package/dist/qoder-design.css +1 -1
  39. package/dist/registry/chat/ask-user-part.d.ts +24 -0
  40. package/dist/registry/chat/browser-action-part.d.ts +28 -0
  41. package/dist/registry/chat/{suggestion-part.d.ts → hint-banner.d.ts} +4 -4
  42. package/dist/registry/chat/reasoning-step/compound.d.ts +17 -0
  43. package/dist/registry/chat/reasoning-step/context.d.ts +10 -0
  44. package/dist/registry/chat/reasoning-step/index.d.ts +14 -0
  45. package/dist/registry/chat/reasoning-step/types.d.ts +95 -0
  46. package/dist/registry/chat/response/compound.d.ts +25 -0
  47. package/dist/registry/chat/response/context.d.ts +9 -0
  48. package/dist/registry/chat/response/index.d.ts +15 -0
  49. package/dist/registry/chat/response/types.d.ts +99 -0
  50. package/dist/registry/chat/user-message.d.ts +6 -0
  51. package/dist/registry/chat/user-question/compound.d.ts +37 -0
  52. package/dist/registry/chat/user-question/context.d.ts +55 -0
  53. package/dist/registry/chat/user-question/index.d.ts +13 -5
  54. package/dist/registry/chat/user-question/useUserQuestionKeyboard.d.ts +2 -3
  55. package/dist/scale.css +9 -303
  56. package/dist/spark-design.cjs.js +62 -62
  57. package/dist/spark-design.es.js +3992 -3826
  58. package/dist/src/components/chat/AskUserPart/index.d.ts +6 -0
  59. package/dist/src/components/chat/BrowserActionPart/index.d.ts +7 -0
  60. package/dist/src/components/chat/HintBanner/index.d.ts +6 -0
  61. package/dist/src/components/chat/ReasoningStep/index.d.ts +11 -5
  62. package/dist/src/components/chat/Response/index.d.ts +16 -6
  63. package/dist/src/components/chat/UserMessage/index.d.ts +7 -0
  64. package/dist/src/components/chat/UserQuestion/index.d.ts +18 -4
  65. package/dist/src/components/index.d.ts +63 -63
  66. package/dist/theme.css +13 -800
  67. package/package.json +27 -3
  68. package/dist/registry/chat/browser-use-part.d.ts +0 -28
  69. package/dist/registry/chat/question-part.d.ts +0 -24
  70. package/dist/registry/chat/reasoning-step.d.ts +0 -35
  71. package/dist/registry/chat/request.d.ts +0 -6
  72. package/dist/registry/chat/response.d.ts +0 -28
  73. package/dist/src/components/chat/BrowserUsePart/index.d.ts +0 -7
  74. package/dist/src/components/chat/QuestionPart/index.d.ts +0 -6
  75. package/dist/src/components/chat/Request/index.d.ts +0 -7
  76. package/dist/src/components/chat/SuggestionPart/index.d.ts +0 -6
  77. /package/dist/src/components/{foundation → basic}/AlertDialog/index.d.ts +0 -0
  78. /package/dist/src/components/{foundation → basic}/Avatar/index.d.ts +0 -0
  79. /package/dist/src/components/{foundation → basic}/Button/index.d.ts +0 -0
  80. /package/dist/src/components/{foundation → basic}/Collapse/index.d.ts +0 -0
  81. /package/dist/src/components/{foundation → basic}/Collapsible/index.d.ts +0 -0
  82. /package/dist/src/components/{foundation → basic}/CollapsibleSection/index.d.ts +0 -0
  83. /package/dist/src/components/{foundation → basic}/DropdownMenu/index.d.ts +0 -0
  84. /package/dist/src/components/{foundation → basic}/EllipsisText/index.d.ts +0 -0
  85. /package/dist/src/components/{foundation → basic}/IconButton/index.d.ts +0 -0
  86. /package/dist/src/components/{foundation → basic}/Kbd/index.d.ts +0 -0
  87. /package/dist/src/components/{foundation → basic}/OptionList/index.d.ts +0 -0
  88. /package/dist/src/components/{foundation → basic}/Pagination/index.d.ts +0 -0
  89. /package/dist/src/components/{foundation → basic}/Progress/index.d.ts +0 -0
  90. /package/dist/src/components/{foundation → basic}/RadioGroup/index.d.ts +0 -0
  91. /package/dist/src/components/{foundation → basic}/Resizable/index.d.ts +0 -0
  92. /package/dist/src/components/{foundation → basic}/Scrollbar/index.d.ts +0 -0
  93. /package/dist/src/components/{foundation → basic}/Select/index.d.ts +0 -0
  94. /package/dist/src/components/{foundation → basic}/Skeleton/index.d.ts +0 -0
  95. /package/dist/src/components/{foundation → basic}/Slider/index.d.ts +0 -0
  96. /package/dist/src/components/{foundation → basic}/Spinner/index.d.ts +0 -0
  97. /package/dist/src/components/{foundation → basic}/Switch/index.d.ts +0 -0
  98. /package/dist/src/components/{foundation → basic}/Table/index.d.ts +0 -0
  99. /package/dist/src/components/{foundation → basic}/Tabs/index.d.ts +0 -0
  100. /package/dist/src/components/{foundation → basic}/Tag/index.d.ts +0 -0
  101. /package/dist/src/components/{foundation → basic}/Toast/index.d.ts +0 -0
  102. /package/dist/src/components/{foundation → basic}/Toggle/index.d.ts +0 -0
  103. /package/dist/src/components/{foundation → basic}/Tooltip/index.d.ts +0 -0
  104. /package/dist/src/components/{foundation → basic}/Typography/index.d.ts +0 -0
@@ -1,21 +1,21 @@
1
- import { memo, useCallback, forwardRef } from 'react'
2
- import type { ReactNode } from 'react'
3
- import { cn } from '@/lib/utils'
4
- import { motion } from 'framer-motion'
5
- import { Chat3Line, ArrowUpLine, ArrowDownSLine, SparklingLine } from '../../basic/icons-inline'
1
+ import { forwardRef } from 'react'
6
2
  import {
7
- DEFAULT_LABELS,
8
- NO_PREFERENCE_VALUE,
9
- type UserQuestionProps,
10
- type UserQuestionHandle,
11
- type UserQuestionItem,
3
+ UserQuestionRootProvider,
4
+ useUserQuestionContext,
5
+ type UserQuestionRootProps,
6
+ } from './context'
7
+ import {
8
+ UserQuestionHeader,
9
+ UserQuestionCard,
10
+ UserQuestionCards,
11
+ UserQuestionFooter,
12
+ UserQuestionDefaultLayout,
13
+ } from './compound'
14
+ import type {
15
+ UserQuestionProps,
16
+ UserQuestionHandle,
12
17
  } from './types'
13
- import { useUserQuestionState } from './useUserQuestionState'
14
18
  import { useUserQuestionKeyboard } from './useUserQuestionKeyboard'
15
- import { UserQuestionHeader } from './UserQuestionHeader'
16
- import { UserQuestionCard } from './UserQuestionCard'
17
- import { UserQuestionFooter } from './UserQuestionFooter'
18
- import { Scrollbar } from '../../basic/scrollbar'
19
19
 
20
20
  export type {
21
21
  UserQuestionOption,
@@ -25,316 +25,71 @@ export type {
25
25
  UserQuestionHandle,
26
26
  } from './types'
27
27
  export { DEFAULT_LABELS, NO_PREFERENCE_VALUE } from './types'
28
- export { UserQuestionHeader } from './UserQuestionHeader'
29
- export { UserQuestionCard } from './UserQuestionCard'
30
- export { UserQuestionFooter } from './UserQuestionFooter'
31
-
32
- const defaultChat4Icon: ReactNode = (
33
- <Chat3Line className="w-4 h-4 shrink-0 text-text-tertiary" />
34
- )
35
- const defaultArrowUpSIcon = <ArrowUpLine />
36
- const defaultArrowDownSIcon = <ArrowDownSLine />
37
- const defaultSparklingIcon = <SparklingLine className="w-4 h-4 mr-1" />
38
-
39
- export const UserQuestion = memo(
40
- forwardRef<UserQuestionHandle, UserQuestionProps>(function UserQuestion(
41
- {
42
- questions,
43
- resetKey,
44
- onAnswer,
45
- onSkip,
46
- hasCustomText = false,
47
- labels: labelsProp,
48
- chat4Icon = defaultChat4Icon,
49
- arrowUpSIcon = defaultArrowUpSIcon,
50
- arrowDownSIcon = defaultArrowDownSIcon,
51
- sparklingIcon = defaultSparklingIcon,
52
- }: UserQuestionProps,
53
- ref
54
- ) {
55
- const labels = { ...DEFAULT_LABELS, ...labelsProp }
56
- const state = useUserQuestionState({ questions, resetKey, ref, onAnswer })
57
-
58
- const {
59
- currentQuestionIndex,
60
- setCurrentQuestionIndex,
61
- answers,
62
- setAnswers,
63
- customInputs,
64
- setCustomInputs,
65
- focusedOptionIndex,
66
- setFocusedOptionIndex,
67
- isSubmitting,
68
- setIsSubmitting,
69
- maxQuestionHeight,
70
- scrollContainerRef,
71
- questionRefs,
72
- shouldScrollToQuestionRef,
73
- customInputRefs,
74
- getSelectedOptionIndex,
75
- } = state
76
-
77
- const currentQuestion = questions[currentQuestionIndex]
78
- const currentOptions = currentQuestion?.options || []
79
- const allQuestionsAnswered = questions.every(
80
- (q) =>
81
- (answers[q.question] || []).length > 0 ||
82
- (customInputs[q.question]?.trim() || '').length > 0
83
- )
84
- const currentQuestionHasAnswer =
85
- (answers[currentQuestion?.question] || []).length > 0 ||
86
- (customInputs[currentQuestion?.question]?.trim() || '').length > 0
87
28
 
88
- const handleOptionClick = useCallback(
89
- (questionText: string, optionLabel: string, questionIndex: number) => {
90
- const question = questions[questionIndex]
91
- const allowMultiple = question?.multiSelect ?? false
92
- const isLastQuestion = questionIndex === questions.length - 1
93
- const currentAnswers = answers[questionText] || []
94
- const isDeselecting = currentAnswers.includes(optionLabel)
95
-
96
- setAnswers((prev) => {
97
- const cur = prev[questionText] || []
98
- if (allowMultiple) {
99
- if (cur.includes(optionLabel)) {
100
- return { ...prev, [questionText]: cur.filter((l) => l !== optionLabel) }
101
- }
102
- return { ...prev, [questionText]: [...cur, optionLabel] }
103
- }
104
- if (cur.includes(optionLabel)) return { ...prev, [questionText]: [] }
105
- return { ...prev, [questionText]: [optionLabel] }
106
- })
29
+ // ===== UserQuestion 复合组件类型 =====
30
+
31
+ export interface UserQuestionCompound {
32
+ Root: React.ForwardRefExoticComponent<UserQuestionRootProps & React.RefAttributes<UserQuestionHandle>>
33
+ Header: typeof UserQuestionHeader
34
+ Card: typeof UserQuestionCard
35
+ Cards: typeof UserQuestionCards
36
+ Footer: typeof UserQuestionFooter
37
+ DefaultLayout: typeof UserQuestionDefaultLayout
38
+ }
39
+
40
+ // ===== 内部键盘处理组件 =====
41
+
42
+ function KeyboardHandler() {
43
+ const ctx = useUserQuestionContext()
44
+ useUserQuestionKeyboard({
45
+ isSubmitting: ctx.isSubmitting,
46
+ currentQuestion: ctx.currentQuestion,
47
+ currentOptions: ctx.currentOptions,
48
+ currentQuestionIndex: ctx.currentQuestionIndex,
49
+ questions: ctx.questions,
50
+ focusedOptionIndex: ctx.focusedOptionIndex,
51
+ setFocusedOptionIndex: ctx.setFocusedOptionIndex,
52
+ currentQuestionHasAnswer: ctx.currentQuestionHasAnswer,
53
+ getSelectedOptionIndex: ctx.getSelectedOptionIndex,
54
+ setShouldScrollToQuestion: ctx.setShouldScrollToQuestion,
55
+ setCurrentQuestionIndex: ctx.setCurrentQuestionIndex,
56
+ handleOptionClick: ctx.handleOptionClick,
57
+ handleContinue: ctx.handleContinue,
58
+ })
59
+ return null
60
+ }
107
61
 
108
- if (!allowMultiple) {
109
- setCustomInputs((prev) => ({ ...prev, [questionText]: '' }))
110
- }
62
+ // ===== UserQuestion 便捷组件 =====
111
63
 
112
- const shouldAutoNext =
113
- !allowMultiple && !isLastQuestion && !isDeselecting
114
- if (shouldAutoNext) {
115
- setTimeout(() => {
116
- const nextIndex = questionIndex + 1
117
- shouldScrollToQuestionRef.current = true
118
- setCurrentQuestionIndex(nextIndex)
119
- setFocusedOptionIndex(getSelectedOptionIndex(nextIndex))
120
- }, 150)
121
- }
122
- },
123
- [
124
- questions,
125
- answers,
126
- setAnswers,
127
- setCustomInputs,
128
- setCurrentQuestionIndex,
129
- setFocusedOptionIndex,
130
- getSelectedOptionIndex,
131
- shouldScrollToQuestionRef,
132
- ]
64
+ const UserQuestionBase = forwardRef<UserQuestionHandle, UserQuestionProps>(
65
+ function UserQuestion(props, ref) {
66
+ return (
67
+ <UserQuestionRootProvider ref={ref} {...props}>
68
+ <KeyboardHandler />
69
+ <UserQuestionDefaultLayout />
70
+ </UserQuestionRootProvider>
133
71
  )
72
+ }
73
+ )
134
74
 
135
- const handlePrevious = useCallback(() => {
136
- if (currentQuestionIndex > 0) {
137
- const prevIndex = currentQuestionIndex - 1
138
- shouldScrollToQuestionRef.current = true
139
- setCurrentQuestionIndex(prevIndex)
140
- setFocusedOptionIndex(getSelectedOptionIndex(prevIndex))
141
- }
142
- }, [currentQuestionIndex, setCurrentQuestionIndex, setFocusedOptionIndex, getSelectedOptionIndex, shouldScrollToQuestionRef])
143
-
144
- const handleNext = useCallback(() => {
145
- if (currentQuestionIndex < questions.length - 1) {
146
- const nextIndex = currentQuestionIndex + 1
147
- shouldScrollToQuestionRef.current = true
148
- setCurrentQuestionIndex(nextIndex)
149
- setFocusedOptionIndex(getSelectedOptionIndex(nextIndex))
150
- }
151
- }, [currentQuestionIndex, questions.length, setCurrentQuestionIndex, setFocusedOptionIndex, getSelectedOptionIndex, shouldScrollToQuestionRef])
75
+ UserQuestionBase.displayName = 'UserQuestion'
152
76
 
153
- const handleContinue = useCallback(() => {
154
- if (isSubmitting) return
155
- if (allQuestionsAnswered) {
156
- setIsSubmitting(true)
157
- const formatted: Record<string, string> = {}
158
- for (const q of questions) {
159
- const selected = answers[q.question] || []
160
- const custom = customInputs[q.question]?.trim()
161
- const parts: string[] = []
162
- if (selected.length > 0) parts.push(...selected)
163
- if (custom) parts.push(custom)
164
- formatted[q.question] = parts.join(', ')
165
- }
166
- onAnswer(formatted)
167
- } else {
168
- const isUnanswered = (q: UserQuestionItem) =>
169
- (answers[q.question] || []).length === 0 &&
170
- (customInputs[q.question]?.trim() || '').length === 0
171
- const len = questions.length
172
- let nextUnansweredIndex = -1
173
- for (let i = 1; i < len; i++) {
174
- const idx = (currentQuestionIndex + i) % len
175
- if (isUnanswered(questions[idx])) {
176
- nextUnansweredIndex = idx
177
- break
178
- }
179
- }
180
- if (nextUnansweredIndex >= 0) {
181
- shouldScrollToQuestionRef.current = true
182
- setCurrentQuestionIndex(nextUnansweredIndex)
183
- setFocusedOptionIndex(getSelectedOptionIndex(nextUnansweredIndex))
184
- }
185
- }
186
- }, [
187
- isSubmitting,
188
- allQuestionsAnswered,
189
- questions,
190
- answers,
191
- customInputs,
192
- currentQuestionIndex,
193
- onAnswer,
194
- setIsSubmitting,
195
- setCurrentQuestionIndex,
196
- setFocusedOptionIndex,
197
- getSelectedOptionIndex,
198
- shouldScrollToQuestionRef,
199
- ])
77
+ // ===== 挂载复合组件 =====
200
78
 
201
- const handleSkipWithGuard = useCallback(() => {
202
- if (isSubmitting) return
203
- setIsSubmitting(true)
204
- const finalAnswers: Record<string, string> = {}
205
- for (const q of questions) {
206
- const selected = answers[q.question] || []
207
- const custom = customInputs[q.question]?.trim()
208
- const hasAnswer = selected.length > 0 || !!custom
209
- if (hasAnswer) {
210
- const parts: string[] = []
211
- if (selected.length > 0) parts.push(...selected)
212
- if (custom) parts.push(custom)
213
- finalAnswers[q.question] = parts.join(', ')
214
- } else {
215
- finalAnswers[q.question] = NO_PREFERENCE_VALUE
216
- }
217
- }
218
- onAnswer(finalAnswers)
219
- onSkip()
220
- }, [isSubmitting, questions, answers, customInputs, onAnswer, onSkip, setIsSubmitting])
79
+ type UserQuestionBaseType = React.ForwardRefExoticComponent<UserQuestionProps & React.RefAttributes<UserQuestionHandle>> & Partial<UserQuestionCompound>
221
80
 
222
- const handleRecommend = useCallback(() => {
223
- if (isSubmitting) return
224
- const recommended: Record<string, string[]> = {}
225
- for (const q of questions) {
226
- const first = q.options?.[0]
227
- if (first) recommended[q.question] = [first.label]
228
- }
229
- setAnswers(recommended)
230
- setCustomInputs({})
231
- shouldScrollToQuestionRef.current = true
232
- setCurrentQuestionIndex(questions.length - 1)
233
- setFocusedOptionIndex(0)
234
- setTimeout(() => {
235
- if (scrollContainerRef.current) {
236
- scrollContainerRef.current.scrollTo({
237
- top: scrollContainerRef.current.scrollHeight,
238
- behavior: 'smooth',
239
- })
240
- }
241
- }, 50)
242
- }, [
243
- isSubmitting,
244
- questions,
245
- setAnswers,
246
- setCustomInputs,
247
- setCurrentQuestionIndex,
248
- setFocusedOptionIndex,
249
- shouldScrollToQuestionRef,
250
- scrollContainerRef,
251
- ])
81
+ const UserQuestionWithSlots = UserQuestionBase as UserQuestionBaseType
252
82
 
253
- useUserQuestionKeyboard({
254
- isSubmitting,
255
- currentQuestion,
256
- currentOptions,
257
- currentQuestionIndex,
258
- questions,
259
- focusedOptionIndex,
260
- setFocusedOptionIndex,
261
- currentQuestionHasAnswer,
262
- getSelectedOptionIndex,
263
- shouldScrollToQuestionRef,
264
- setCurrentQuestionIndex,
265
- handleOptionClick,
266
- handleContinue,
267
- })
83
+ UserQuestionWithSlots.Root = UserQuestionRootProvider
84
+ UserQuestionWithSlots.Header = UserQuestionHeader
85
+ UserQuestionWithSlots.Card = UserQuestionCard
86
+ UserQuestionWithSlots.Cards = UserQuestionCards
87
+ UserQuestionWithSlots.Footer = UserQuestionFooter
88
+ UserQuestionWithSlots.DefaultLayout = UserQuestionDefaultLayout
268
89
 
269
- if (questions.length === 0) return null
90
+ export const UserQuestion = UserQuestionWithSlots as React.ForwardRefExoticComponent<UserQuestionProps & React.RefAttributes<UserQuestionHandle>> &
91
+ UserQuestionCompound
270
92
 
271
- return (
272
- <motion.div
273
- initial={{ opacity: 0, y: 8 }}
274
- animate={{ opacity: 1, y: 0 }}
275
- transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
276
- className={cn(
277
- 'border rounded-t-xl border-b-0 border-border border-border-tertiary bg-bg-base overflow-hidden pt-2 pb-2'
278
- )}
279
- >
280
- <UserQuestionHeader
281
- currentQuestion={currentQuestion}
282
- questionsLength={questions.length}
283
- labels={labels}
284
- chat4Icon={chat4Icon}
285
- arrowUpSIcon={arrowUpSIcon}
286
- arrowDownSIcon={arrowDownSIcon}
287
- currentQuestionIndex={currentQuestionIndex}
288
- onPrevious={handlePrevious}
289
- onNext={handleNext}
290
- />
291
- <Scrollbar
292
- ref={scrollContainerRef}
293
- scrollbarVisibility="auto"
294
- maxHeight={maxQuestionHeight ? maxQuestionHeight : undefined}
295
- className="scroll-smooth"
296
- >
297
- <div className="space-y-4 px-3">
298
- {questions.map((question, qIndex) => (
299
- <UserQuestionCard
300
- key={question.question}
301
- question={question}
302
- qIndex={qIndex}
303
- labels={labels}
304
- answers={answers}
305
- customInputs={customInputs}
306
- focusedOptionIndex={focusedOptionIndex}
307
- isCurrentQuestion={qIndex === currentQuestionIndex}
308
- isSubmitting={isSubmitting}
309
- allQuestionsAnswered={allQuestionsAnswered}
310
- questionsLength={questions.length}
311
- questionRefs={questionRefs}
312
- customInputRefs={customInputRefs}
313
- setCurrentQuestionIndex={setCurrentQuestionIndex}
314
- setFocusedOptionIndex={setFocusedOptionIndex}
315
- setAnswers={setAnswers}
316
- setCustomInputs={setCustomInputs}
317
- getSelectedOptionIndex={getSelectedOptionIndex}
318
- shouldScrollToQuestionRef={shouldScrollToQuestionRef}
319
- onOptionClick={handleOptionClick}
320
- onContinue={handleContinue}
321
- />
322
- ))}
323
- </div>
324
- </Scrollbar>
325
- <UserQuestionFooter
326
- labels={labels}
327
- sparklingIcon={sparklingIcon}
328
- isSubmitting={isSubmitting}
329
- hasCustomText={hasCustomText}
330
- allQuestionsAnswered={allQuestionsAnswered}
331
- onRecommend={handleRecommend}
332
- onSkip={handleSkipWithGuard}
333
- onContinue={handleContinue}
334
- />
335
- </motion.div>
336
- )
337
- })
338
- )
93
+ // ===== 导出 Context 和其他工具 =====
339
94
 
340
- UserQuestion.displayName = 'UserQuestion'
95
+ export { useUserQuestionContext, UserQuestionDefaultLayout }
@@ -1,5 +1,4 @@
1
1
  import { useEffect } from 'react'
2
- import type { MutableRefObject } from 'react'
3
2
  import type { UserQuestionItem, UserQuestionOption } from './types'
4
3
 
5
4
  export interface UseUserQuestionKeyboardProps {
@@ -12,7 +11,7 @@ export interface UseUserQuestionKeyboardProps {
12
11
  setFocusedOptionIndex: (n: number) => void
13
12
  currentQuestionHasAnswer: boolean
14
13
  getSelectedOptionIndex: (i: number) => number
15
- shouldScrollToQuestionRef: MutableRefObject<boolean>
14
+ setShouldScrollToQuestion: (v: boolean) => void
16
15
  setCurrentQuestionIndex: (i: number) => void
17
16
  handleOptionClick: (questionText: string, optionLabel: string, questionIndex: number) => void
18
17
  handleContinue: () => void
@@ -36,7 +35,7 @@ export function useUserQuestionKeyboard({
36
35
  setFocusedOptionIndex,
37
36
  currentQuestionHasAnswer,
38
37
  getSelectedOptionIndex,
39
- shouldScrollToQuestionRef,
38
+ setShouldScrollToQuestion,
40
39
  setCurrentQuestionIndex,
41
40
  handleOptionClick,
42
41
  handleContinue,
@@ -49,7 +48,7 @@ export function useUserQuestionKeyboard({
49
48
  setFocusedOptionIndex(focusedOptionIndex + 1)
50
49
  } else if (currentQuestionIndex < questions.length - 1) {
51
50
  const nextIndex = currentQuestionIndex + 1
52
- shouldScrollToQuestionRef.current = true
51
+ setShouldScrollToQuestion(true)
53
52
  setCurrentQuestionIndex(nextIndex)
54
53
  setFocusedOptionIndex(getSelectedOptionIndex(nextIndex))
55
54
  }
@@ -60,7 +59,7 @@ export function useUserQuestionKeyboard({
60
59
  setFocusedOptionIndex(focusedOptionIndex - 1)
61
60
  } else if (currentQuestionIndex > 0) {
62
61
  const prevIndex = currentQuestionIndex - 1
63
- shouldScrollToQuestionRef.current = true
62
+ setShouldScrollToQuestion(true)
64
63
  setCurrentQuestionIndex(prevIndex)
65
64
  setFocusedOptionIndex(getSelectedOptionIndex(prevIndex))
66
65
  }
@@ -122,6 +121,6 @@ export function useUserQuestionKeyboard({
122
121
  setCurrentQuestionIndex,
123
122
  handleOptionClick,
124
123
  handleContinue,
125
- shouldScrollToQuestionRef,
124
+ setShouldScrollToQuestion,
126
125
  ])
127
126
  }
@@ -0,0 +1,31 @@
1
+ /* ============================================
2
+ * Token 系统统一入口 (CLI 分发版)
3
+ *
4
+ * 引入方式:
5
+ * 1. 全量引入(默认): @import "qoder-tokens.css";
6
+ * 2. 按需引入:
7
+ * @import "./scale/index.css"; // 布局风格
8
+ * @import "./themes/light-qoder.css"; // 亮色主题
9
+ * @import "./themes/dark-qoder.css"; // 暗色主题
10
+ *
11
+ * 自定义主题:
12
+ * @import "./scale/index.css";
13
+ * @import "./my-custom-theme.css"; // 定义 [data-theme="xxx"] { --token-color-*: ... }
14
+ *
15
+ * 自定义布局配置:
16
+ * @import "./scale/config.css";
17
+ * @import "./scale/computed.css";
18
+ * :root { --config-radius-default: 10px; } // 覆盖配置
19
+ * ============================================ */
20
+
21
+ /* 布局风格 Token (data-style: neutral | compact | soft | sharp | dense) */
22
+ @import "./scale.css";
23
+
24
+ /* 内置主题 (data-theme) */
25
+ @import "./themes/light-qoder.css";
26
+ @import "./themes/dark-qoder.css";
27
+ @import "./themes/light-parchment.css";
28
+ @import "./themes/dark-parchment.css";
29
+
30
+ /* 滚动条工具样式 */
31
+ @import "./scrollbar-utility.css";
@@ -0,0 +1,103 @@
1
+ /* ============================================
2
+ * Scale Computed - 应用配置,产出实际变量
3
+ *
4
+ * 此文件将 --config-* 映射到 --spacing-* / --radius-* / --font-size-*
5
+ * 组件和样式使用这些实际变量,不直接使用 --config-*
6
+ * ============================================ */
7
+
8
+ :root,
9
+ [data-style="neutral"] {
10
+ /* ============================================
11
+ Spacing (从配置映射)
12
+ ============================================ */
13
+ --spacing: var(--config-spacing-1);
14
+ --spacing-0: var(--config-spacing-0);
15
+ --spacing-px: var(--config-spacing-px);
16
+ --spacing-0\.5: var(--config-spacing-0\.5);
17
+ --spacing-1: var(--config-spacing-1);
18
+ --spacing-1\.5: var(--config-spacing-1\.5);
19
+ --spacing-2: var(--config-spacing-2);
20
+ --spacing-2\.5: var(--config-spacing-2\.5);
21
+ --spacing-3: var(--config-spacing-3);
22
+ --spacing-3\.5: var(--config-spacing-3\.5);
23
+ --spacing-4: var(--config-spacing-4);
24
+ --spacing-5: var(--config-spacing-5);
25
+ --spacing-6: var(--config-spacing-6);
26
+ --spacing-7: var(--config-spacing-7);
27
+ --spacing-8: var(--config-spacing-8);
28
+ --spacing-9: var(--config-spacing-9);
29
+ --spacing-10: var(--config-spacing-10);
30
+ --spacing-11: var(--config-spacing-11);
31
+ --spacing-12: var(--config-spacing-12);
32
+ --spacing-14: var(--config-spacing-14);
33
+ --spacing-16: var(--config-spacing-16);
34
+ --spacing-20: var(--config-spacing-20);
35
+ --spacing-24: var(--config-spacing-24);
36
+ --spacing-28: var(--config-spacing-28);
37
+ --spacing-32: var(--config-spacing-32);
38
+ --spacing-36: var(--config-spacing-36);
39
+ --spacing-40: var(--config-spacing-40);
40
+ --spacing-44: var(--config-spacing-44);
41
+ --spacing-48: var(--config-spacing-48);
42
+ --spacing-52: var(--config-spacing-52);
43
+ --spacing-56: var(--config-spacing-56);
44
+ --spacing-60: var(--config-spacing-60);
45
+ --spacing-64: var(--config-spacing-64);
46
+ --spacing-72: var(--config-spacing-72);
47
+ --spacing-80: var(--config-spacing-80);
48
+ --spacing-96: var(--config-spacing-96);
49
+
50
+ /* ============================================
51
+ Radius (从配置映射)
52
+ ============================================ */
53
+ --radius-none: var(--config-radius-none);
54
+ --radius-sm: var(--config-radius-sm);
55
+ --radius-DEFAULT: var(--config-radius-default);
56
+ --radius-md: var(--config-radius-md);
57
+ --radius-lg: var(--config-radius-lg);
58
+ --radius-xl: var(--config-radius-xl);
59
+ --radius-2xl: var(--config-radius-2xl);
60
+ --radius-3xl: var(--config-radius-3xl);
61
+ --radius-full: var(--config-radius-full);
62
+ --radius: var(--radius-DEFAULT);
63
+
64
+ /* ============================================
65
+ Font Size (从配置映射)
66
+ ============================================ */
67
+ --font-size-xs: var(--config-font-size-xs);
68
+ --font-size-xs--line-height: var(--config-font-size-xs-lh);
69
+ --font-size-sm: var(--config-font-size-sm);
70
+ --font-size-sm--line-height: var(--config-font-size-sm-lh);
71
+ --font-size-base: var(--config-font-size-base);
72
+ --font-size-base--line-height: var(--config-font-size-base-lh);
73
+ --font-size-lg: var(--config-font-size-lg);
74
+ --font-size-lg--line-height: var(--config-font-size-lg-lh);
75
+ --font-size-xl: var(--config-font-size-xl);
76
+ --font-size-xl--line-height: var(--config-font-size-xl-lh);
77
+ --font-size-2xl: var(--config-font-size-2xl);
78
+ --font-size-2xl--line-height: var(--config-font-size-2xl-lh);
79
+ --font-size-3xl: var(--config-font-size-3xl);
80
+ --font-size-3xl--line-height: var(--config-font-size-3xl-lh);
81
+ --font-size-4xl: var(--config-font-size-4xl);
82
+ --font-size-4xl--line-height: var(--config-font-size-4xl-lh);
83
+ --font-size-5xl: var(--config-font-size-5xl);
84
+ --font-size-5xl--line-height: var(--config-font-size-5xl-lh);
85
+ --font-size-6xl: var(--config-font-size-6xl);
86
+ --font-size-6xl--line-height: var(--config-font-size-6xl-lh);
87
+ --font-size-7xl: var(--config-font-size-7xl);
88
+ --font-size-7xl--line-height: var(--config-font-size-7xl-lh);
89
+ --font-size-8xl: var(--config-font-size-8xl);
90
+ --font-size-8xl--line-height: var(--config-font-size-8xl-lh);
91
+ --font-size-9xl: var(--config-font-size-9xl);
92
+ --font-size-9xl--line-height: var(--config-font-size-9xl-lh);
93
+
94
+ /* ============================================
95
+ Motion (从配置映射)
96
+ ============================================ */
97
+ --motion-duration-fast: var(--config-motion-duration-fast);
98
+ --motion-duration-base: var(--config-motion-duration-base);
99
+ --motion-duration-slow: var(--config-motion-duration-slow);
100
+ --motion-ease-standard: var(--config-motion-ease-standard);
101
+ --motion-ease-emphasized: var(--config-motion-ease-emphasized);
102
+ --motion-ease-out: var(--config-motion-ease-out);
103
+ }