sparkdesign 0.0.1 → 0.1.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/README.md +188 -4
- package/dist/commands/add.js +93 -0
- package/dist/commands/diff.js +54 -0
- package/dist/commands/init.js +96 -0
- package/dist/commands/list.js +25 -0
- package/dist/index.js +37 -0
- package/dist/utils/config.js +53 -0
- package/dist/utils/registry.js +34 -0
- package/dist/utils/tokens.js +176 -0
- package/dist/utils/transform.js +19 -0
- package/package.json +33 -10
- package/registry/__tests__/basic/button.test.tsx +333 -0
- package/registry/__tests__/chat/markdown.test.tsx +387 -0
- package/registry/__tests__/chat/thinking-indicator.test.tsx +244 -0
- package/registry/__tests__/chat/tool-invocation-card.test.tsx +346 -0
- package/registry/basic/alert-dialog.tsx +180 -0
- package/registry/basic/avatar.tsx +120 -0
- package/registry/basic/button.tsx +100 -0
- package/registry/basic/collapse.tsx +94 -0
- package/registry/basic/collapsible-card.tsx +230 -0
- package/registry/basic/collapsible.tsx +21 -0
- package/registry/basic/dropdown-menu.tsx +254 -0
- package/registry/basic/icon-button.tsx +66 -0
- package/registry/basic/icons-inline.tsx +206 -0
- package/registry/basic/kbd.tsx +50 -0
- package/registry/basic/option-list.tsx +125 -0
- package/registry/basic/pagination.tsx +132 -0
- package/registry/basic/progress.tsx +42 -0
- package/registry/basic/radio-group.tsx +69 -0
- package/registry/basic/resizable.tsx +67 -0
- package/registry/basic/scrollbar.tsx +114 -0
- package/registry/basic/select.tsx +177 -0
- package/registry/basic/shimmering-text.tsx +115 -0
- package/registry/basic/sidebar-menu.tsx +177 -0
- package/registry/basic/skeleton.tsx +33 -0
- package/registry/basic/slider.tsx +55 -0
- package/registry/basic/sonner.tsx +104 -0
- package/registry/basic/spinner.tsx +17 -0
- package/registry/basic/switch.tsx +49 -0
- package/registry/basic/table.tsx +117 -0
- package/registry/basic/tabs.tsx +85 -0
- package/registry/basic/tag.tsx +161 -0
- package/registry/basic/theme-from-document.ts +10 -0
- package/registry/basic/toggle.tsx +223 -0
- package/registry/basic/tooltip.tsx +80 -0
- package/registry/basic/typography.tsx +201 -0
- package/registry/chat/ask-user-part.tsx +70 -0
- package/registry/chat/browser-action-part.tsx +166 -0
- package/registry/chat/chat-input/chat-input-folder-selector.tsx +185 -0
- package/registry/chat/chat-input/chat-input-model-switcher.tsx +131 -0
- package/registry/chat/chat-input/chat-input-textarea.tsx +67 -0
- package/registry/chat/chat-input/compound.tsx +334 -0
- package/registry/chat/chat-input/context.tsx +189 -0
- package/registry/chat/chat-input/folder-permission-dialog.tsx +61 -0
- package/registry/chat/chat-input/index.tsx +123 -0
- package/registry/chat/chat-input/types.ts +77 -0
- package/registry/chat/chat-input/useAutoResizeTextarea.ts +20 -0
- package/registry/chat/code-block-part.tsx +151 -0
- package/registry/chat/file-attachment.tsx +44 -0
- package/registry/chat/file-card.tsx +68 -0
- package/registry/chat/file-review-part.tsx +259 -0
- package/registry/chat/folder-button.tsx +169 -0
- package/registry/chat/generated-images-grid.tsx +56 -0
- package/registry/chat/generation-status-bar.tsx +72 -0
- package/registry/chat/hint-banner.tsx +165 -0
- package/registry/chat/image-attachment.tsx +166 -0
- package/registry/chat/image-generating.tsx +281 -0
- package/registry/chat/markdown.tsx +146 -0
- package/registry/chat/mermaid-part.tsx +90 -0
- package/registry/chat/permission-card.tsx +178 -0
- package/registry/chat/plan-part.tsx +168 -0
- package/registry/chat/queue-indicator.tsx +234 -0
- package/registry/chat/reasoning-step/compound.tsx +336 -0
- package/registry/chat/reasoning-step/context.tsx +114 -0
- package/registry/chat/reasoning-step/index.tsx +45 -0
- package/registry/chat/reasoning-step/types.ts +109 -0
- package/registry/chat/related-prompts.tsx +91 -0
- package/registry/chat/response/compound.tsx +210 -0
- package/registry/chat/response/context.tsx +200 -0
- package/registry/chat/response/index.tsx +87 -0
- package/registry/chat/response/types.ts +123 -0
- package/registry/chat/send-button.tsx +94 -0
- package/registry/chat/streaming-markdown-block.tsx +111 -0
- package/registry/chat/task-part.tsx +109 -0
- package/registry/chat/terminal-code-block-part.tsx +69 -0
- package/registry/chat/thinking-indicator.tsx +91 -0
- package/registry/chat/tool-invocation-card.tsx +132 -0
- package/registry/chat/user-message.tsx +38 -0
- package/registry/chat/user-question/UserQuestionCard.tsx +198 -0
- package/registry/chat/user-question/UserQuestionFooter.tsx +66 -0
- package/registry/chat/user-question/UserQuestionHeader.tsx +64 -0
- package/registry/chat/user-question/compound.tsx +324 -0
- package/registry/chat/user-question/context.tsx +456 -0
- package/registry/chat/user-question/index.tsx +95 -0
- package/registry/chat/user-question/types.ts +61 -0
- package/registry/chat/user-question/useUserQuestionKeyboard.ts +126 -0
- package/registry/chat/user-question/useUserQuestionState.ts +165 -0
- package/registry/chat/user-question-answer.tsx +62 -0
- package/registry/lib/file-icon-maps.ts +150 -0
- package/registry/lib/use-mermaid-render.ts +76 -0
- package/registry/lib/utils.ts +6 -0
- package/registry/meta.json +1 -0
- package/registry/tokens/index.css +31 -0
- package/registry/tokens/scale/computed.css +103 -0
- package/registry/tokens/scale/config.css +110 -0
- package/registry/tokens/scale/index.css +30 -0
- package/registry/tokens/scale/presets/compact.css +30 -0
- package/registry/tokens/scale/presets/dense.css +64 -0
- package/registry/tokens/scale/presets/sharp.css +40 -0
- package/registry/tokens/scale/presets/soft.css +16 -0
- package/registry/tokens/scale.css +13 -0
- package/registry/tokens/scrollbar-utility.css +35 -0
- package/registry/tokens/theme.css +633 -0
- package/registry/tokens/themes/dark-parchment.css +132 -0
- package/registry/tokens/themes/dark-qoder.css +132 -0
- package/registry/tokens/themes/light-parchment.css +123 -0
- package/registry/tokens/themes/light-qoder.css +131 -0
- package/index.js +0 -5
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
+
import { ToolInvocationCard } from '../../chat/tool-invocation-card'
|
|
4
|
+
|
|
5
|
+
describe('ToolInvocationCard', () => {
|
|
6
|
+
// ============================================================
|
|
7
|
+
// 基础渲染测试
|
|
8
|
+
// ============================================================
|
|
9
|
+
describe('基础渲染', () => {
|
|
10
|
+
it('应正确渲染工具名称', () => {
|
|
11
|
+
render(<ToolInvocationCard toolName="read_file" status="success" />)
|
|
12
|
+
expect(screen.getByText('read_file')).toBeInTheDocument()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('应设置正确的 displayName', () => {
|
|
16
|
+
expect(ToolInvocationCard.displayName).toBe('ToolInvocationCard')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('应渲染参数文本', () => {
|
|
20
|
+
render(
|
|
21
|
+
<ToolInvocationCard
|
|
22
|
+
toolName="search"
|
|
23
|
+
params="query: hello world"
|
|
24
|
+
status="success"
|
|
25
|
+
/>
|
|
26
|
+
)
|
|
27
|
+
expect(screen.getByText('query: hello world')).toBeInTheDocument()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('无参数时不应渲染参数区域', () => {
|
|
31
|
+
const { container } = render(
|
|
32
|
+
<ToolInvocationCard toolName="list_dir" status="success" />
|
|
33
|
+
)
|
|
34
|
+
// 只有工具名和状态文本
|
|
35
|
+
expect(container.textContent).toContain('list_dir')
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// ============================================================
|
|
40
|
+
// 状态图标测试
|
|
41
|
+
// ============================================================
|
|
42
|
+
describe('状态图标', () => {
|
|
43
|
+
it('running 状态应显示时间图标', () => {
|
|
44
|
+
const { container } = render(
|
|
45
|
+
<ToolInvocationCard toolName="test" status="running" />
|
|
46
|
+
)
|
|
47
|
+
// 默认 timeIcon 使用 TimeLine 组件
|
|
48
|
+
expect(container.querySelector('svg')).toBeInTheDocument()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('success 状态应显示勾选图标', () => {
|
|
52
|
+
const { container } = render(
|
|
53
|
+
<ToolInvocationCard toolName="test" status="success" />
|
|
54
|
+
)
|
|
55
|
+
expect(container.querySelector('svg')).toBeInTheDocument()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('error 状态应显示关闭图标', () => {
|
|
59
|
+
const { container } = render(
|
|
60
|
+
<ToolInvocationCard toolName="test" status="error" />
|
|
61
|
+
)
|
|
62
|
+
expect(container.querySelector('svg')).toBeInTheDocument()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('应接受自定义图标', () => {
|
|
66
|
+
render(
|
|
67
|
+
<ToolInvocationCard
|
|
68
|
+
toolName="test"
|
|
69
|
+
status="success"
|
|
70
|
+
checkboxCircleIcon={<span data-testid="custom-check">✓</span>}
|
|
71
|
+
/>
|
|
72
|
+
)
|
|
73
|
+
expect(screen.getByTestId('custom-check')).toBeInTheDocument()
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
// ============================================================
|
|
78
|
+
// 状态文本测试
|
|
79
|
+
// ============================================================
|
|
80
|
+
describe('状态文本', () => {
|
|
81
|
+
it('running 状态应显示默认文本 "Running"', () => {
|
|
82
|
+
render(<ToolInvocationCard toolName="test" status="running" />)
|
|
83
|
+
expect(screen.getByText('Running')).toBeInTheDocument()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('success 状态应显示默认文本 "Ran"', () => {
|
|
87
|
+
render(<ToolInvocationCard toolName="test" status="success" />)
|
|
88
|
+
expect(screen.getByText('Ran')).toBeInTheDocument()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('error 状态应显示默认文本 "Failed"', () => {
|
|
92
|
+
render(<ToolInvocationCard toolName="test" status="error" />)
|
|
93
|
+
expect(screen.getByText('Failed')).toBeInTheDocument()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('应接受自定义 statusText', () => {
|
|
97
|
+
render(
|
|
98
|
+
<ToolInvocationCard
|
|
99
|
+
toolName="test"
|
|
100
|
+
status="success"
|
|
101
|
+
statusText="执行完成"
|
|
102
|
+
/>
|
|
103
|
+
)
|
|
104
|
+
expect(screen.getByText('执行完成')).toBeInTheDocument()
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('statusText 为空字符串时不应显示状态文本', () => {
|
|
108
|
+
render(
|
|
109
|
+
<ToolInvocationCard
|
|
110
|
+
toolName="test"
|
|
111
|
+
status="success"
|
|
112
|
+
statusText=""
|
|
113
|
+
/>
|
|
114
|
+
)
|
|
115
|
+
expect(screen.queryByText('Ran')).not.toBeInTheDocument()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('应接受自定义状态文本 props', () => {
|
|
119
|
+
render(
|
|
120
|
+
<ToolInvocationCard
|
|
121
|
+
toolName="test"
|
|
122
|
+
status="running"
|
|
123
|
+
runningText="执行中"
|
|
124
|
+
/>
|
|
125
|
+
)
|
|
126
|
+
expect(screen.getByText('执行中')).toBeInTheDocument()
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// ============================================================
|
|
131
|
+
// 响应展示测试
|
|
132
|
+
// ============================================================
|
|
133
|
+
describe('响应展示', () => {
|
|
134
|
+
it('有响应时应可展开', () => {
|
|
135
|
+
render(
|
|
136
|
+
<ToolInvocationCard
|
|
137
|
+
toolName="test"
|
|
138
|
+
status="success"
|
|
139
|
+
response="Tool output result"
|
|
140
|
+
/>
|
|
141
|
+
)
|
|
142
|
+
// 点击展开
|
|
143
|
+
fireEvent.click(screen.getByText('test'))
|
|
144
|
+
expect(screen.getByText('Tool output result')).toBeInTheDocument()
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('defaultExpanded=true 时应默认展开', () => {
|
|
148
|
+
render(
|
|
149
|
+
<ToolInvocationCard
|
|
150
|
+
toolName="test"
|
|
151
|
+
status="success"
|
|
152
|
+
response="Already expanded"
|
|
153
|
+
defaultExpanded={true}
|
|
154
|
+
/>
|
|
155
|
+
)
|
|
156
|
+
expect(screen.getByText('Already expanded')).toBeInTheDocument()
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('无响应时不应显示展开箭头', () => {
|
|
160
|
+
const { container } = render(
|
|
161
|
+
<ToolInvocationCard toolName="test" status="success" />
|
|
162
|
+
)
|
|
163
|
+
// 没有箭头图标(ArrowRightLine 的 SVG)
|
|
164
|
+
const arrows = container.querySelectorAll('[class*="rotate"]')
|
|
165
|
+
expect(arrows).toHaveLength(0)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('running 状态时不应可展开', () => {
|
|
169
|
+
render(
|
|
170
|
+
<ToolInvocationCard
|
|
171
|
+
toolName="test"
|
|
172
|
+
status="running"
|
|
173
|
+
response="Some response"
|
|
174
|
+
/>
|
|
175
|
+
)
|
|
176
|
+
// running 状态下点击不应展开
|
|
177
|
+
fireEvent.click(screen.getByText('test'))
|
|
178
|
+
expect(screen.queryByText('Some response')).not.toBeInTheDocument()
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('点击应切换展开/收起状态', () => {
|
|
182
|
+
render(
|
|
183
|
+
<ToolInvocationCard
|
|
184
|
+
toolName="toggle-test"
|
|
185
|
+
status="success"
|
|
186
|
+
response="Toggle content"
|
|
187
|
+
/>
|
|
188
|
+
)
|
|
189
|
+
const trigger = screen.getByText('toggle-test')
|
|
190
|
+
|
|
191
|
+
// 第一次点击展开
|
|
192
|
+
fireEvent.click(trigger)
|
|
193
|
+
expect(screen.getByText('Toggle content')).toBeInTheDocument()
|
|
194
|
+
|
|
195
|
+
// 第二次点击收起
|
|
196
|
+
fireEvent.click(trigger)
|
|
197
|
+
expect(screen.queryByText('Toggle content')).not.toBeInTheDocument()
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
// ============================================================
|
|
202
|
+
// 响应标签测试
|
|
203
|
+
// ============================================================
|
|
204
|
+
describe('响应标签', () => {
|
|
205
|
+
it('展开时应显示默认标签 "Response"', () => {
|
|
206
|
+
render(
|
|
207
|
+
<ToolInvocationCard
|
|
208
|
+
toolName="test"
|
|
209
|
+
status="success"
|
|
210
|
+
response="Content"
|
|
211
|
+
defaultExpanded
|
|
212
|
+
/>
|
|
213
|
+
)
|
|
214
|
+
expect(screen.getByText('Response')).toBeInTheDocument()
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('应接受自定义 responseLabel', () => {
|
|
218
|
+
render(
|
|
219
|
+
<ToolInvocationCard
|
|
220
|
+
toolName="test"
|
|
221
|
+
status="success"
|
|
222
|
+
response="Content"
|
|
223
|
+
responseLabel="输出结果"
|
|
224
|
+
defaultExpanded
|
|
225
|
+
/>
|
|
226
|
+
)
|
|
227
|
+
expect(screen.getByText('输出结果')).toBeInTheDocument()
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
// ============================================================
|
|
232
|
+
// 错误状态样式测试
|
|
233
|
+
// ============================================================
|
|
234
|
+
describe('错误状态', () => {
|
|
235
|
+
it('error 状态响应应应用错误样式', () => {
|
|
236
|
+
render(
|
|
237
|
+
<ToolInvocationCard
|
|
238
|
+
toolName="test"
|
|
239
|
+
status="error"
|
|
240
|
+
response="Error message"
|
|
241
|
+
defaultExpanded
|
|
242
|
+
/>
|
|
243
|
+
)
|
|
244
|
+
const errorText = screen.getByText('Error message')
|
|
245
|
+
expect(errorText).toHaveClass('text-error')
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it('success 状态响应不应有错误样式', () => {
|
|
249
|
+
render(
|
|
250
|
+
<ToolInvocationCard
|
|
251
|
+
toolName="test"
|
|
252
|
+
status="success"
|
|
253
|
+
response="Success message"
|
|
254
|
+
defaultExpanded
|
|
255
|
+
/>
|
|
256
|
+
)
|
|
257
|
+
const successText = screen.getByText('Success message')
|
|
258
|
+
expect(successText).not.toHaveClass('text-error')
|
|
259
|
+
})
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
// ============================================================
|
|
263
|
+
// className 测试
|
|
264
|
+
// ============================================================
|
|
265
|
+
describe('className', () => {
|
|
266
|
+
it('应合并自定义 className', () => {
|
|
267
|
+
const { container } = render(
|
|
268
|
+
<ToolInvocationCard
|
|
269
|
+
toolName="test"
|
|
270
|
+
status="success"
|
|
271
|
+
className="custom-card"
|
|
272
|
+
/>
|
|
273
|
+
)
|
|
274
|
+
expect(container.firstChild).toHaveClass('custom-card')
|
|
275
|
+
})
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
// ============================================================
|
|
279
|
+
// 自定义图标 props 测试
|
|
280
|
+
// ============================================================
|
|
281
|
+
describe('自定义图标 props', () => {
|
|
282
|
+
it('应接受自定义 timeIcon', () => {
|
|
283
|
+
render(
|
|
284
|
+
<ToolInvocationCard
|
|
285
|
+
toolName="test"
|
|
286
|
+
status="running"
|
|
287
|
+
timeIcon={<span data-testid="time-icon">⏱</span>}
|
|
288
|
+
/>
|
|
289
|
+
)
|
|
290
|
+
expect(screen.getByTestId('time-icon')).toBeInTheDocument()
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
it('应接受自定义 closeCircleIcon', () => {
|
|
294
|
+
render(
|
|
295
|
+
<ToolInvocationCard
|
|
296
|
+
toolName="test"
|
|
297
|
+
status="error"
|
|
298
|
+
closeCircleIcon={<span data-testid="close-icon">✗</span>}
|
|
299
|
+
/>
|
|
300
|
+
)
|
|
301
|
+
expect(screen.getByTestId('close-icon')).toBeInTheDocument()
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
it('应接受自定义 arrowRightIcon', () => {
|
|
305
|
+
render(
|
|
306
|
+
<ToolInvocationCard
|
|
307
|
+
toolName="test"
|
|
308
|
+
status="success"
|
|
309
|
+
response="Content"
|
|
310
|
+
arrowRightIcon={<span data-testid="arrow-icon">→</span>}
|
|
311
|
+
/>
|
|
312
|
+
)
|
|
313
|
+
expect(screen.getByTestId('arrow-icon')).toBeInTheDocument()
|
|
314
|
+
})
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
// ============================================================
|
|
318
|
+
// 组合场景测试
|
|
319
|
+
// ============================================================
|
|
320
|
+
describe('组合场景', () => {
|
|
321
|
+
it('应支持所有 props 组合', () => {
|
|
322
|
+
render(
|
|
323
|
+
<ToolInvocationCard
|
|
324
|
+
toolName="full_test"
|
|
325
|
+
params="/path/to/file.ts"
|
|
326
|
+
status="success"
|
|
327
|
+
response="File content here..."
|
|
328
|
+
defaultExpanded
|
|
329
|
+
statusText="已完成"
|
|
330
|
+
responseLabel="文件内容"
|
|
331
|
+
className="test-class"
|
|
332
|
+
checkboxCircleIcon={<span data-testid="check">✓</span>}
|
|
333
|
+
arrowRightIcon={<span data-testid="arrow">→</span>}
|
|
334
|
+
/>
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
expect(screen.getByText('full_test')).toBeInTheDocument()
|
|
338
|
+
expect(screen.getByText('/path/to/file.ts')).toBeInTheDocument()
|
|
339
|
+
expect(screen.getByText('已完成')).toBeInTheDocument()
|
|
340
|
+
expect(screen.getByText('文件内容')).toBeInTheDocument()
|
|
341
|
+
expect(screen.getByText('File content here...')).toBeInTheDocument()
|
|
342
|
+
expect(screen.getByTestId('check')).toBeInTheDocument()
|
|
343
|
+
expect(screen.getByTestId('arrow')).toBeInTheDocument()
|
|
344
|
+
})
|
|
345
|
+
})
|
|
346
|
+
})
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { motion } from 'framer-motion'
|
|
3
|
+
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
|
|
4
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
5
|
+
import { cn } from '@/lib/utils'
|
|
6
|
+
import { getThemeFromDocument } from './theme-from-document'
|
|
7
|
+
|
|
8
|
+
const AlertDialog = AlertDialogPrimitive.Root
|
|
9
|
+
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
|
|
10
|
+
const AlertDialogPortal = AlertDialogPrimitive.Portal
|
|
11
|
+
|
|
12
|
+
const AlertDialogOverlay = React.forwardRef<
|
|
13
|
+
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
|
14
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
|
15
|
+
>(({ className, ...props }, ref) => (
|
|
16
|
+
<AlertDialogPrimitive.Overlay ref={ref} asChild {...props}>
|
|
17
|
+
<motion.div
|
|
18
|
+
className={cn('fixed inset-0 z-50 bg-bg-mask/60', className)}
|
|
19
|
+
initial={{ opacity: 0 }}
|
|
20
|
+
animate={{ opacity: 1 }}
|
|
21
|
+
transition={{ duration: 0.15 }}
|
|
22
|
+
/>
|
|
23
|
+
</AlertDialogPrimitive.Overlay>
|
|
24
|
+
))
|
|
25
|
+
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
|
|
26
|
+
|
|
27
|
+
const contentVariants = cva(
|
|
28
|
+
'font-sans fixed left-1/2 top-1/2 z-50 w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 rounded-xl border border-border bg-bg-base text-text shadow-lg p-6',
|
|
29
|
+
{
|
|
30
|
+
variants: {
|
|
31
|
+
size: { default: 'max-w-lg', sm: 'max-w-sm' },
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: { size: 'default' },
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
export interface AlertDialogContentProps
|
|
38
|
+
extends React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>,
|
|
39
|
+
VariantProps<typeof contentVariants> {
|
|
40
|
+
size?: 'default' | 'sm'
|
|
41
|
+
/** When provided (e.g. from ThemeStyleProvider), used for portal wrapper */
|
|
42
|
+
dataStyle?: string
|
|
43
|
+
dataTheme?: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const AlertDialogContent = React.forwardRef<
|
|
47
|
+
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
|
48
|
+
AlertDialogContentProps
|
|
49
|
+
>(({ className, children, size = 'default', dataStyle, dataTheme, ...props }, ref) => {
|
|
50
|
+
const fromDoc = getThemeFromDocument()
|
|
51
|
+
const dataProps =
|
|
52
|
+
dataStyle !== undefined
|
|
53
|
+
? { 'data-style': dataStyle, 'data-theme': dataTheme ?? fromDoc['data-theme'] ?? '' }
|
|
54
|
+
: fromDoc
|
|
55
|
+
return (
|
|
56
|
+
<AlertDialogPortal>
|
|
57
|
+
<div style={{ display: 'contents' }} {...dataProps}>
|
|
58
|
+
<AlertDialogOverlay />
|
|
59
|
+
<AlertDialogPrimitive.Content ref={ref} asChild {...props}>
|
|
60
|
+
<motion.div
|
|
61
|
+
className={cn(contentVariants({ size }), className)}
|
|
62
|
+
initial={{ opacity: 0, y: 8 }}
|
|
63
|
+
animate={{ opacity: 1, y: 0 }}
|
|
64
|
+
transition={{ duration: 0.2, ease: [0.4, 0, 0.2, 1] }}
|
|
65
|
+
>
|
|
66
|
+
{children}
|
|
67
|
+
</motion.div>
|
|
68
|
+
</AlertDialogPrimitive.Content>
|
|
69
|
+
</div>
|
|
70
|
+
</AlertDialogPortal>
|
|
71
|
+
)
|
|
72
|
+
})
|
|
73
|
+
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
|
|
74
|
+
|
|
75
|
+
const AlertDialogHeader = React.forwardRef<
|
|
76
|
+
HTMLDivElement,
|
|
77
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
78
|
+
>(({ className, ...props }, ref) => (
|
|
79
|
+
<div ref={ref} className={cn('flex flex-col gap-2', className)} {...props} />
|
|
80
|
+
))
|
|
81
|
+
AlertDialogHeader.displayName = 'AlertDialogHeader'
|
|
82
|
+
|
|
83
|
+
const AlertDialogFooter = React.forwardRef<
|
|
84
|
+
HTMLDivElement,
|
|
85
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
86
|
+
>(({ className, ...props }, ref) => (
|
|
87
|
+
<div
|
|
88
|
+
ref={ref}
|
|
89
|
+
className={cn('mt-6 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
))
|
|
93
|
+
AlertDialogFooter.displayName = 'AlertDialogFooter'
|
|
94
|
+
|
|
95
|
+
const AlertDialogTitle = React.forwardRef<
|
|
96
|
+
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
|
97
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
|
98
|
+
>(({ className, ...props }, ref) => (
|
|
99
|
+
<AlertDialogPrimitive.Title
|
|
100
|
+
ref={ref}
|
|
101
|
+
className={cn('font-sans text-base font-medium leading-6 text-text', className)}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
))
|
|
105
|
+
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
|
|
106
|
+
|
|
107
|
+
const AlertDialogDescription = React.forwardRef<
|
|
108
|
+
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
|
109
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
|
110
|
+
>(({ className, ...props }, ref) => (
|
|
111
|
+
<AlertDialogPrimitive.Description
|
|
112
|
+
ref={ref}
|
|
113
|
+
className={cn('text-sm leading-6 text-text-tertiary', className)}
|
|
114
|
+
{...props}
|
|
115
|
+
/>
|
|
116
|
+
))
|
|
117
|
+
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName
|
|
118
|
+
|
|
119
|
+
const actionVariants = cva(
|
|
120
|
+
'inline-flex h-9 items-center justify-center rounded px-4 text-sm font-medium transition-colors focus:outline-none disabled:pointer-events-none disabled:opacity-50 cursor-pointer',
|
|
121
|
+
{
|
|
122
|
+
variants: {
|
|
123
|
+
destructive: {
|
|
124
|
+
true: 'bg-error text-text-on-primary hover:bg-error-hover active:bg-error',
|
|
125
|
+
false: 'bg-bg-highlight text-text-on-primary hover:bg-bg-highlight-hover',
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
defaultVariants: { destructive: false },
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
export interface AlertDialogActionProps
|
|
133
|
+
extends React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>,
|
|
134
|
+
VariantProps<typeof actionVariants> {
|
|
135
|
+
destructive?: boolean
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const AlertDialogAction = React.forwardRef<
|
|
139
|
+
React.ElementRef<typeof AlertDialogPrimitive.Action>,
|
|
140
|
+
AlertDialogActionProps
|
|
141
|
+
>(({ className, destructive = false, ...props }, ref) => (
|
|
142
|
+
<AlertDialogPrimitive.Action
|
|
143
|
+
ref={ref}
|
|
144
|
+
className={cn(actionVariants({ destructive }), className)}
|
|
145
|
+
{...props}
|
|
146
|
+
/>
|
|
147
|
+
))
|
|
148
|
+
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
|
|
149
|
+
|
|
150
|
+
const AlertDialogCancel = React.forwardRef<
|
|
151
|
+
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
|
152
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
|
153
|
+
>(({ className, ...props }, ref) => (
|
|
154
|
+
<AlertDialogPrimitive.Cancel
|
|
155
|
+
ref={ref}
|
|
156
|
+
className={cn(
|
|
157
|
+
'inline-flex h-9 items-center justify-center rounded px-4 text-sm font-medium cursor-pointer',
|
|
158
|
+
'bg-transparent text-text-secondary hover:bg-fill-secondary hover:text-text',
|
|
159
|
+
'transition-colors focus:outline-none',
|
|
160
|
+
className
|
|
161
|
+
)}
|
|
162
|
+
{...props}
|
|
163
|
+
/>
|
|
164
|
+
))
|
|
165
|
+
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
|
|
166
|
+
|
|
167
|
+
export {
|
|
168
|
+
AlertDialog,
|
|
169
|
+
AlertDialogTrigger,
|
|
170
|
+
AlertDialogPortal,
|
|
171
|
+
AlertDialogOverlay,
|
|
172
|
+
AlertDialogContent,
|
|
173
|
+
AlertDialogHeader,
|
|
174
|
+
AlertDialogFooter,
|
|
175
|
+
AlertDialogTitle,
|
|
176
|
+
AlertDialogDescription,
|
|
177
|
+
AlertDialogAction,
|
|
178
|
+
AlertDialogCancel,
|
|
179
|
+
}
|
|
180
|
+
export { contentVariants as alertDialogContentVariants, actionVariants as alertDialogActionVariants }
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import * as AvatarPrimitive from '@radix-ui/react-avatar'
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
|
|
6
|
+
const avatarVariants = cva('relative flex shrink-0 rounded-full', {
|
|
7
|
+
variants: {
|
|
8
|
+
size: {
|
|
9
|
+
sm: 'h-8 w-8',
|
|
10
|
+
default: 'h-10 w-10',
|
|
11
|
+
lg: 'h-12 w-12',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
defaultVariants: { size: 'default' },
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export interface AvatarProps
|
|
18
|
+
extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>,
|
|
19
|
+
VariantProps<typeof avatarVariants> {}
|
|
20
|
+
|
|
21
|
+
const Avatar = React.forwardRef<
|
|
22
|
+
React.ElementRef<typeof AvatarPrimitive.Root>,
|
|
23
|
+
AvatarProps
|
|
24
|
+
>(({ className, size = 'default', ...props }, ref) => (
|
|
25
|
+
<AvatarPrimitive.Root
|
|
26
|
+
ref={ref}
|
|
27
|
+
className={cn(avatarVariants({ size }), className)}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
))
|
|
31
|
+
Avatar.displayName = AvatarPrimitive.Root.displayName
|
|
32
|
+
|
|
33
|
+
export type AvatarImageProps = React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
|
34
|
+
|
|
35
|
+
const AvatarImage = React.forwardRef<
|
|
36
|
+
React.ElementRef<typeof AvatarPrimitive.Image>,
|
|
37
|
+
AvatarImageProps
|
|
38
|
+
>(({ className, ...props }, ref) => (
|
|
39
|
+
<AvatarPrimitive.Image
|
|
40
|
+
ref={ref}
|
|
41
|
+
className={cn(
|
|
42
|
+
'aspect-square h-full w-full overflow-hidden rounded-full object-cover',
|
|
43
|
+
className
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
))
|
|
48
|
+
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
|
49
|
+
|
|
50
|
+
export type AvatarFallbackProps = React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
|
51
|
+
|
|
52
|
+
const AvatarFallback = React.forwardRef<
|
|
53
|
+
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
|
54
|
+
AvatarFallbackProps
|
|
55
|
+
>(({ className, ...props }, ref) => (
|
|
56
|
+
<AvatarPrimitive.Fallback
|
|
57
|
+
ref={ref}
|
|
58
|
+
className={cn(
|
|
59
|
+
'flex h-full w-full items-center justify-center overflow-hidden rounded-full bg-fill-tertiary text-sm font-medium text-text',
|
|
60
|
+
className
|
|
61
|
+
)}
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
))
|
|
65
|
+
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
|
66
|
+
|
|
67
|
+
export type AvatarBadgeProps = React.HTMLAttributes<HTMLSpanElement>
|
|
68
|
+
|
|
69
|
+
const AvatarBadge = React.forwardRef<HTMLSpanElement, AvatarBadgeProps>(
|
|
70
|
+
({ className, ...props }, ref) => (
|
|
71
|
+
<span
|
|
72
|
+
ref={ref}
|
|
73
|
+
className={cn(
|
|
74
|
+
'absolute bottom-0 right-0 z-10 block h-3 w-3 rounded-full border-2 border-bg-base bg-success',
|
|
75
|
+
className
|
|
76
|
+
)}
|
|
77
|
+
{...props}
|
|
78
|
+
/>
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
AvatarBadge.displayName = 'AvatarBadge'
|
|
82
|
+
|
|
83
|
+
export type AvatarGroupProps = React.HTMLAttributes<HTMLDivElement>
|
|
84
|
+
|
|
85
|
+
const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
|
|
86
|
+
({ className, ...props }, ref) => (
|
|
87
|
+
<div
|
|
88
|
+
ref={ref}
|
|
89
|
+
className={cn('flex -space-x-2 [&>*]:ring-2 [&>*]:ring-bg-base', className)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
AvatarGroup.displayName = 'AvatarGroup'
|
|
95
|
+
|
|
96
|
+
export type AvatarGroupCountProps = React.HTMLAttributes<HTMLSpanElement>
|
|
97
|
+
|
|
98
|
+
const AvatarGroupCount = React.forwardRef<HTMLSpanElement, AvatarGroupCountProps>(
|
|
99
|
+
({ className, ...props }, ref) => (
|
|
100
|
+
<span
|
|
101
|
+
ref={ref}
|
|
102
|
+
className={cn(
|
|
103
|
+
'inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-full border-2 border-bg-base bg-fill-tertiary text-sm font-medium text-text',
|
|
104
|
+
className
|
|
105
|
+
)}
|
|
106
|
+
{...props}
|
|
107
|
+
/>
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
AvatarGroupCount.displayName = 'AvatarGroupCount'
|
|
111
|
+
|
|
112
|
+
export {
|
|
113
|
+
Avatar,
|
|
114
|
+
AvatarImage,
|
|
115
|
+
AvatarFallback,
|
|
116
|
+
AvatarBadge,
|
|
117
|
+
AvatarGroup,
|
|
118
|
+
AvatarGroupCount,
|
|
119
|
+
}
|
|
120
|
+
export { avatarVariants }
|