@carto/ps-react-ui 4.9.0 → 4.11.0
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/dist/category-Dnd2_j0x.js +719 -0
- package/dist/category-Dnd2_j0x.js.map +1 -0
- package/dist/change-column-BiuuHCDN.js +1156 -0
- package/dist/change-column-BiuuHCDN.js.map +1 -0
- package/dist/chat.js +1507 -0
- package/dist/chat.js.map +1 -0
- package/dist/components.js +125 -122
- package/dist/components.js.map +1 -1
- package/dist/copy-button-DGL1tyli.js +26 -0
- package/dist/copy-button-DGL1tyli.js.map +1 -0
- package/dist/{data-zoom-layout-BH0LPwSy.js → data-zoom-layout--YiY6ko_.js} +5 -4
- package/dist/{data-zoom-layout-BH0LPwSy.js.map → data-zoom-layout--YiY6ko_.js.map} +1 -1
- package/dist/{download-config-DNLkypdN.js → download-config-oJIFZ2WC.js} +10 -9
- package/dist/{download-config-DNLkypdN.js.map → download-config-oJIFZ2WC.js.map} +1 -1
- package/dist/{lasso-tool-BYbxrJ-7.js → lasso-tool-CDFj4zKY.js} +2 -1
- package/dist/lasso-tool-CDFj4zKY.js.map +1 -0
- package/dist/{spread-CTuIXZSM.js → spread-CPis22AE.js} +5 -4
- package/dist/{spread-CTuIXZSM.js.map → spread-CPis22AE.js.map} +1 -1
- package/dist/types/chat/bubbles/chat-error-message.d.ts +2 -0
- package/dist/types/chat/bubbles/chat-suggestion-button.d.ts +2 -0
- package/dist/types/chat/bubbles/chat-user-message.d.ts +2 -0
- package/dist/types/chat/bubbles/index.d.ts +4 -0
- package/dist/types/chat/const.d.ts +4 -0
- package/dist/types/chat/containers/chat-content.d.ts +2 -0
- package/dist/types/chat/containers/chat-footer.d.ts +2 -0
- package/dist/types/chat/containers/chat-header.d.ts +2 -0
- package/dist/types/chat/containers/chat-starter.d.ts +2 -0
- package/dist/types/chat/containers/index.d.ts +4 -0
- package/dist/types/chat/containers/styles.d.ts +93 -0
- package/dist/types/chat/feedback/chat-loader.d.ts +2 -0
- package/dist/types/chat/feedback/chat-rating-action.d.ts +2 -0
- package/dist/types/chat/feedback/chat-thinking.d.ts +2 -0
- package/dist/types/chat/feedback/chat-tool-code-area.d.ts +2 -0
- package/dist/types/chat/feedback/chat-tool-full-view-dialog.d.ts +2 -0
- package/dist/types/chat/feedback/chat-tool-group.d.ts +2 -0
- package/dist/types/chat/feedback/chat-tool-trace.d.ts +3 -0
- package/dist/types/chat/feedback/get-tool-label.d.ts +2 -0
- package/dist/types/chat/feedback/index.d.ts +8 -0
- package/dist/types/chat/feedback/styles.d.ts +211 -0
- package/dist/types/chat/index.d.ts +20 -0
- package/dist/types/chat/types.d.ts +184 -0
- package/dist/types/chat/use-typewriter.d.ts +30 -0
- package/dist/types/components/copy-button/copy-button.d.ts +2 -0
- package/dist/types/components/copy-button/types.d.ts +6 -0
- package/dist/types/components/index.d.ts +2 -0
- package/dist/types/components/lasso-tool/styles.d.ts +1 -0
- package/dist/types/components/measurement-tools/styles.d.ts +1 -0
- package/dist/types/widgets-v2/actions/index.d.ts +1 -0
- package/dist/types/widgets-v2/actions/show-all/index.d.ts +2 -0
- package/dist/types/widgets-v2/actions/show-all/labels.d.ts +5 -0
- package/dist/types/widgets-v2/actions/show-all/show-all.d.ts +33 -0
- package/dist/types/widgets-v2/actions/show-all/style.d.ts +8 -0
- package/dist/types/widgets-v2/category/category-ui.d.ts +9 -2
- package/dist/types/widgets-v2/category/category.d.ts +9 -2
- package/dist/types/widgets-v2/category/components/category-row-other.d.ts +19 -6
- package/dist/types/widgets-v2/category/style.d.ts +21 -2
- package/dist/types/widgets-v2/category/types.d.ts +2 -0
- package/dist/types/widgets-v2/index.d.ts +3 -2
- package/dist/types/widgets-v2/selection-summary/labels.d.ts +7 -2
- package/dist/types/widgets-v2/selection-summary/selection-summary.d.ts +13 -6
- package/dist/types/widgets-v2/selection-summary/style.d.ts +15 -0
- package/dist/types/widgets-v2/wrapper/style.d.ts +1 -2
- package/dist/types/widgets-v2/wrapper/widget-wrapper.d.ts +6 -1
- package/dist/widgets/actions.js +116 -115
- package/dist/widgets/actions.js.map +1 -1
- package/dist/widgets/bar.js +1 -1
- package/dist/widgets/category.js +10 -9
- package/dist/widgets/category.js.map +1 -1
- package/dist/widgets/formula.js +12 -11
- package/dist/widgets/formula.js.map +1 -1
- package/dist/widgets/histogram.js +8 -7
- package/dist/widgets/histogram.js.map +1 -1
- package/dist/widgets/markdown.js +10 -9
- package/dist/widgets/markdown.js.map +1 -1
- package/dist/widgets/pie.js +1 -1
- package/dist/widgets/scatterplot.js +1 -1
- package/dist/widgets/spread.js +10 -9
- package/dist/widgets/spread.js.map +1 -1
- package/dist/widgets/table.js +18 -17
- package/dist/widgets/table.js.map +1 -1
- package/dist/widgets/timeseries.js +1 -1
- package/dist/widgets/utils.js +1 -1
- package/dist/widgets/wrapper.js +4 -3
- package/dist/widgets/wrapper.js.map +1 -1
- package/dist/widgets-v2/actions.js +41 -37
- package/dist/widgets-v2/bar.js +9 -8
- package/dist/widgets-v2/bar.js.map +1 -1
- package/dist/widgets-v2/category.js +23 -22
- package/dist/widgets-v2/category.js.map +1 -1
- package/dist/widgets-v2/formula.js +24 -23
- package/dist/widgets-v2/formula.js.map +1 -1
- package/dist/widgets-v2/histogram.js +11 -10
- package/dist/widgets-v2/histogram.js.map +1 -1
- package/dist/widgets-v2/markdown.js +10 -9
- package/dist/widgets-v2/markdown.js.map +1 -1
- package/dist/widgets-v2/pie.js +8 -7
- package/dist/widgets-v2/pie.js.map +1 -1
- package/dist/widgets-v2/scatterplot.js +10 -9
- package/dist/widgets-v2/scatterplot.js.map +1 -1
- package/dist/widgets-v2/spread.js +10 -9
- package/dist/widgets-v2/spread.js.map +1 -1
- package/dist/widgets-v2/table.js +17 -16
- package/dist/widgets-v2/table.js.map +1 -1
- package/dist/widgets-v2/timeseries.js +9 -8
- package/dist/widgets-v2/timeseries.js.map +1 -1
- package/dist/widgets-v2/utils.js +1 -1
- package/dist/widgets-v2.js +343 -338
- package/dist/widgets-v2.js.map +1 -1
- package/package.json +9 -3
- package/src/chat/bubbles/chat-agent-message.test.tsx +30 -0
- package/src/chat/bubbles/chat-agent-message.tsx +11 -0
- package/src/chat/bubbles/chat-error-message.test.tsx +40 -0
- package/src/chat/bubbles/chat-error-message.tsx +47 -0
- package/src/chat/bubbles/chat-suggestion-button.test.tsx +24 -0
- package/src/chat/bubbles/chat-suggestion-button.tsx +27 -0
- package/src/chat/bubbles/chat-user-message.test.tsx +27 -0
- package/src/chat/bubbles/chat-user-message.tsx +27 -0
- package/src/chat/bubbles/index.ts +4 -0
- package/src/chat/bubbles/styles.ts +148 -0
- package/src/chat/const.ts +4 -0
- package/src/chat/containers/chat-content.test.tsx +269 -0
- package/src/chat/containers/chat-content.tsx +142 -0
- package/src/chat/containers/chat-footer.test.tsx +34 -0
- package/src/chat/containers/chat-footer.tsx +78 -0
- package/src/chat/containers/chat-header.test.tsx +28 -0
- package/src/chat/containers/chat-header.tsx +29 -0
- package/src/chat/containers/chat-starter.test.tsx +32 -0
- package/src/chat/containers/chat-starter.tsx +75 -0
- package/src/chat/containers/index.ts +4 -0
- package/src/chat/containers/styles.ts +96 -0
- package/src/chat/feedback/chat-actions-container.test.tsx +64 -0
- package/src/chat/feedback/chat-actions-container.tsx +7 -0
- package/src/chat/feedback/chat-loader.test.tsx +10 -0
- package/src/chat/feedback/chat-loader.tsx +31 -0
- package/src/chat/feedback/chat-rating-action.tsx +43 -0
- package/src/chat/feedback/chat-thinking.test.tsx +15 -0
- package/src/chat/feedback/chat-thinking.tsx +23 -0
- package/src/chat/feedback/chat-tool-code-area.test.tsx +23 -0
- package/src/chat/feedback/chat-tool-code-area.tsx +71 -0
- package/src/chat/feedback/chat-tool-full-view-dialog.test.tsx +39 -0
- package/src/chat/feedback/chat-tool-full-view-dialog.tsx +121 -0
- package/src/chat/feedback/chat-tool-group.test.tsx +84 -0
- package/src/chat/feedback/chat-tool-group.tsx +156 -0
- package/src/chat/feedback/chat-tool-trace.test.tsx +81 -0
- package/src/chat/feedback/chat-tool-trace.tsx +192 -0
- package/src/chat/feedback/get-tool-label.test.tsx +91 -0
- package/src/chat/feedback/get-tool-label.ts +13 -0
- package/src/chat/feedback/index.ts +8 -0
- package/src/chat/feedback/styles.ts +229 -0
- package/src/chat/index.ts +59 -0
- package/src/chat/types.ts +215 -0
- package/src/chat/use-typewriter.test.tsx +38 -0
- package/src/chat/use-typewriter.ts +82 -0
- package/src/components/copy-button/copy-button.test.tsx +41 -0
- package/src/components/copy-button/copy-button.tsx +31 -0
- package/src/components/copy-button/types.ts +10 -0
- package/src/components/index.ts +3 -0
- package/src/components/lasso-tool/styles.ts +1 -0
- package/src/components/measurement-tools/styles.ts +1 -0
- package/src/widgets-v2/actions/index.ts +8 -0
- package/src/widgets-v2/actions/show-all/index.ts +7 -0
- package/src/widgets-v2/actions/show-all/labels.ts +8 -0
- package/src/widgets-v2/actions/show-all/show-all.test.tsx +50 -0
- package/src/widgets-v2/actions/show-all/show-all.tsx +72 -0
- package/src/widgets-v2/actions/show-all/style.ts +8 -0
- package/src/widgets-v2/category/category-ui.test.tsx +26 -10
- package/src/widgets-v2/category/category-ui.tsx +13 -3
- package/src/widgets-v2/category/category.test.tsx +4 -4
- package/src/widgets-v2/category/category.tsx +10 -1
- package/src/widgets-v2/category/components/category-row-other.test.tsx +36 -7
- package/src/widgets-v2/category/components/category-row-other.tsx +64 -13
- package/src/widgets-v2/category/style.ts +35 -4
- package/src/widgets-v2/category/types.ts +2 -0
- package/src/widgets-v2/index.ts +3 -0
- package/src/widgets-v2/selection-summary/labels.ts +8 -4
- package/src/widgets-v2/selection-summary/selection-summary.test.tsx +15 -9
- package/src/widgets-v2/selection-summary/selection-summary.tsx +42 -22
- package/src/widgets-v2/selection-summary/style.ts +15 -0
- package/src/widgets-v2/wrapper/style.ts +1 -2
- package/src/widgets-v2/wrapper/widget-wrapper.test.tsx +30 -0
- package/src/widgets-v2/wrapper/widget-wrapper.tsx +11 -1
- package/dist/category-DwaeYjpX.js +0 -656
- package/dist/category-DwaeYjpX.js.map +0 -1
- package/dist/change-column-Cidl_M-4.js +0 -1110
- package/dist/change-column-Cidl_M-4.js.map +0 -1
- package/dist/lasso-tool-BYbxrJ-7.js.map +0 -1
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import type { ButtonBaseProps, SxProps, Theme } from '@mui/material'
|
|
3
|
+
|
|
4
|
+
// === Shared base props ===
|
|
5
|
+
export interface ChatSxProps {
|
|
6
|
+
sx?: SxProps<Theme>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// === Error types ===
|
|
10
|
+
export interface ChatErrorAction {
|
|
11
|
+
label: string
|
|
12
|
+
onClick: () => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// === Message props ===
|
|
16
|
+
export interface ChatUserMessageProps extends ChatSxProps {
|
|
17
|
+
children: ReactNode
|
|
18
|
+
/** enabled to render text with a lighter color for indicating things like an error sending the message */
|
|
19
|
+
muted?: boolean
|
|
20
|
+
/** content to render on top of the message for user attachments */
|
|
21
|
+
topContext?: ReactNode
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ChatAgentMessageProps extends ChatSxProps {
|
|
25
|
+
children: ReactNode
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ChatErrorMessageProps extends ChatSxProps {
|
|
29
|
+
errors: string[]
|
|
30
|
+
icon?: ReactNode
|
|
31
|
+
actions?: ChatErrorAction[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ChatSuggestionButtonProps
|
|
35
|
+
extends ChatSxProps, Omit<ButtonBaseProps, 'children'> {
|
|
36
|
+
label: ReactNode
|
|
37
|
+
color?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// === Feedback props ===
|
|
41
|
+
export interface ChatThinkingProps extends ChatSxProps {
|
|
42
|
+
duration?: number
|
|
43
|
+
children?: ReactNode
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ChatLoaderProps extends ChatSxProps {
|
|
47
|
+
size?: number
|
|
48
|
+
labels?: {
|
|
49
|
+
loading?: string
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// === Layout props ===
|
|
54
|
+
export interface ChatContentProps extends ChatSxProps {
|
|
55
|
+
children: ReactNode
|
|
56
|
+
/**
|
|
57
|
+
* Smooth-scrolls to the bottom whenever new content is added — but only if
|
|
58
|
+
* the user was already at (or near) the bottom. Readers who scrolled up to
|
|
59
|
+
* revisit older messages are left alone. Defaults to `true`; pass `false`
|
|
60
|
+
* to opt out and manage scroll yourself via the ref.
|
|
61
|
+
*/
|
|
62
|
+
autoScroll?: boolean
|
|
63
|
+
labels?: {
|
|
64
|
+
jumpToLatest?: string
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Imperative handle exposed by `ChatContent` via `ref`. Use it to drive scroll
|
|
70
|
+
* from the parent — for example, calling `scrollToBottom()` when a new agent
|
|
71
|
+
* message arrives.
|
|
72
|
+
*/
|
|
73
|
+
export interface ChatContentRef {
|
|
74
|
+
/** Smooth-scrolls the content area to the bottom. */
|
|
75
|
+
scrollToBottom: () => void
|
|
76
|
+
/** Smooth-scrolls the content area to the top. */
|
|
77
|
+
scrollToTop: () => void
|
|
78
|
+
/** `true` when the content area is scrolled to (or near) its bottom edge. */
|
|
79
|
+
isAtBottom: boolean
|
|
80
|
+
/** `true` when the content area is scrolled to (or near) its top edge. */
|
|
81
|
+
isAtTop: boolean
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// === Container props ===
|
|
85
|
+
export interface ChatHeaderProps extends ChatSxProps {
|
|
86
|
+
leftSlot?: ReactNode
|
|
87
|
+
title: ReactNode
|
|
88
|
+
rightSlot?: ReactNode
|
|
89
|
+
onClose?: () => void
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface ChatFooterProps extends ChatSxProps {
|
|
93
|
+
/** Current value of the chat message area. */
|
|
94
|
+
value: string
|
|
95
|
+
/** Called with the new textarea value on every keystroke. */
|
|
96
|
+
onChange: (value: string) => void
|
|
97
|
+
/** Called when the send button is clicked or Enter is pressed (without Shift). */
|
|
98
|
+
onSend: () => void
|
|
99
|
+
/** Called when the stop button is clicked. Only shown while `isGenerating` is true. */
|
|
100
|
+
onStop?: () => void
|
|
101
|
+
/** When true, swaps the send button for a stop button and disables the textarea. */
|
|
102
|
+
isGenerating?: boolean
|
|
103
|
+
/** Disables the textarea and both send/stop buttons. */
|
|
104
|
+
disabled?: boolean
|
|
105
|
+
/** Placeholder text for the textarea. Defaults to `'Type a message...'`. */
|
|
106
|
+
placeholder?: string
|
|
107
|
+
/** Accessible labels for the send and stop buttons (used as `aria-label`). */
|
|
108
|
+
labels?: {
|
|
109
|
+
/** Defaults to `'Send'`. */
|
|
110
|
+
send?: string
|
|
111
|
+
/** Defaults to `'Stop'`. */
|
|
112
|
+
stop?: string
|
|
113
|
+
}
|
|
114
|
+
/** Helper text rendered under the input. Defaults to an AI disclaimer; pass `null` to hide. */
|
|
115
|
+
caption?: ReactNode
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// === Extras props ===
|
|
119
|
+
export interface ChatStarterItem {
|
|
120
|
+
label: string
|
|
121
|
+
color?: string
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface ChatStarterProps extends ChatSxProps {
|
|
125
|
+
icon?: ReactNode
|
|
126
|
+
title?: ReactNode
|
|
127
|
+
description?: ReactNode
|
|
128
|
+
items: string[] | ChatStarterItem[]
|
|
129
|
+
size?: 'small' | 'medium'
|
|
130
|
+
onSelect?: (prompt: string) => void
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface ChatRatingActionProps {
|
|
134
|
+
onRatingChange?: (rating: 'up' | 'down' | null) => void
|
|
135
|
+
rating?: 'up' | 'down' | null
|
|
136
|
+
labels?: {
|
|
137
|
+
thumbUp?: string
|
|
138
|
+
thumbDown?: string
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface ChatToolItem {
|
|
143
|
+
id: string
|
|
144
|
+
name: string
|
|
145
|
+
status: 'running' | 'complete' | 'error'
|
|
146
|
+
/** Display label shown while status is 'running'. Falls back to a capitalized `name`. */
|
|
147
|
+
runningLabel?: string
|
|
148
|
+
/** Display label shown for non-running statuses. Falls back to a capitalized `name`. */
|
|
149
|
+
label?: string
|
|
150
|
+
/** Friendly reference name for the tool (e.g. "add_marker"). Displayed with icon. */
|
|
151
|
+
reference?: string
|
|
152
|
+
/** Execution duration in seconds (e.g. 1.8) */
|
|
153
|
+
duration?: number
|
|
154
|
+
/** Input arguments as a JSON string or plain text */
|
|
155
|
+
inputArguments?: string
|
|
156
|
+
/** Output as a JSON string or plain text */
|
|
157
|
+
output?: string
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface ChatToolTraceProps extends ChatSxProps {
|
|
161
|
+
tool: ChatToolItem
|
|
162
|
+
/** Whether the trace accordion is expanded */
|
|
163
|
+
expanded?: boolean
|
|
164
|
+
/** Callback when accordion expansion state changes */
|
|
165
|
+
onExpandedChange?: (expanded: boolean) => void
|
|
166
|
+
labels?: {
|
|
167
|
+
toolExecuted?: string
|
|
168
|
+
reference?: string
|
|
169
|
+
duration?: string
|
|
170
|
+
status?: string
|
|
171
|
+
inputArguments?: string
|
|
172
|
+
output?: string
|
|
173
|
+
fullView?: string
|
|
174
|
+
success?: string
|
|
175
|
+
error?: string
|
|
176
|
+
running?: string
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export interface ChatToolCodeAreaProps extends ChatSxProps {
|
|
181
|
+
/** Code content to display */
|
|
182
|
+
content: string
|
|
183
|
+
/** Label for the full view dialog title */
|
|
184
|
+
title?: string
|
|
185
|
+
/** Render with error styling (red left border, tinted background) */
|
|
186
|
+
isError?: boolean
|
|
187
|
+
labels?: {
|
|
188
|
+
fullView?: string
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface ChatToolFullViewDialogProps {
|
|
193
|
+
open: boolean
|
|
194
|
+
onClose: () => void
|
|
195
|
+
title: string
|
|
196
|
+
content: string
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export interface ChatToolGroupProps extends ChatSxProps {
|
|
200
|
+
tools: ChatToolItem[]
|
|
201
|
+
/** Whether the group accordion is expanded */
|
|
202
|
+
expanded?: boolean
|
|
203
|
+
/** Callback when group expansion state changes */
|
|
204
|
+
onExpandedChange?: (expanded: boolean) => void
|
|
205
|
+
/** Map of tool IDs to their individual expanded state. Used to preserve expansion state during grouping. */
|
|
206
|
+
expandedTools?: Record<string, boolean>
|
|
207
|
+
/** Callback when an individual tool's expansion state changes */
|
|
208
|
+
onToolExpandedChange?: (
|
|
209
|
+
value: Record<string, boolean>,
|
|
210
|
+
toolId?: string,
|
|
211
|
+
) => void
|
|
212
|
+
labels?: ChatToolTraceProps['labels'] & {
|
|
213
|
+
toolsUsed?: string
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
import { renderHook } from '@testing-library/react'
|
|
3
|
+
import { useTypewriter } from './use-typewriter'
|
|
4
|
+
|
|
5
|
+
describe('useTypewriter', () => {
|
|
6
|
+
test('starts with empty text and isTyping=true when fullText is non-empty', () => {
|
|
7
|
+
const { result } = renderHook(() => useTypewriter('hello'))
|
|
8
|
+
expect(result.current.displayedText).toBe('')
|
|
9
|
+
expect(result.current.isTyping).toBe(true)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('returns the full text immediately when skipAnimation is true', () => {
|
|
13
|
+
const { result } = renderHook(() =>
|
|
14
|
+
useTypewriter('hello', { skipAnimation: true }),
|
|
15
|
+
)
|
|
16
|
+
expect(result.current.displayedText).toBe('hello')
|
|
17
|
+
expect(result.current.isTyping).toBe(false)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('isTyping=false for empty fullText', () => {
|
|
21
|
+
const { result } = renderHook(() => useTypewriter(''))
|
|
22
|
+
expect(result.current.displayedText).toBe('')
|
|
23
|
+
expect(result.current.isTyping).toBe(false)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('skipAnimation captured at mount — toggling later does not retrigger reveal', () => {
|
|
27
|
+
const { result, rerender } = renderHook(
|
|
28
|
+
({ skip }: { skip: boolean }) =>
|
|
29
|
+
useTypewriter('hello', { skipAnimation: skip }),
|
|
30
|
+
{ initialProps: { skip: true } },
|
|
31
|
+
)
|
|
32
|
+
expect(result.current.displayedText).toBe('hello')
|
|
33
|
+
|
|
34
|
+
rerender({ skip: false })
|
|
35
|
+
expect(result.current.displayedText).toBe('hello')
|
|
36
|
+
expect(result.current.isTyping).toBe(false)
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
interface UseTypewriterOptions {
|
|
4
|
+
/** Characters revealed per second (default: `500`). */
|
|
5
|
+
speed?: number
|
|
6
|
+
/** When true on mount, skip the animation and reveal the full text immediately. */
|
|
7
|
+
skipAnimation?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface UseTypewriterResult {
|
|
11
|
+
/** The portion of `fullText` revealed so far. */
|
|
12
|
+
displayedText: string
|
|
13
|
+
/** `true` while characters are still being revealed. */
|
|
14
|
+
isTyping: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Reveals a string character-by-character at a steady rate via
|
|
19
|
+
* `requestAnimationFrame`. Useful for smoothing out bursty WebSocket-streamed
|
|
20
|
+
* agent message text — pair it with `ChatAgentMessage`.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* const { displayedText, isTyping } = useTypewriter(message)
|
|
25
|
+
* return (
|
|
26
|
+
* <ChatAgentMessage>
|
|
27
|
+
* <Markdown>{displayedText}</Markdown>
|
|
28
|
+
* {isTyping ? <Cursor /> : null}
|
|
29
|
+
* </ChatAgentMessage>
|
|
30
|
+
* )
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function useTypewriter(
|
|
34
|
+
fullText: string,
|
|
35
|
+
options: UseTypewriterOptions = {},
|
|
36
|
+
): UseTypewriterResult {
|
|
37
|
+
const { speed = 500, skipAnimation = false } = options
|
|
38
|
+
|
|
39
|
+
const skipRef = useRef(skipAnimation)
|
|
40
|
+
|
|
41
|
+
const [charIndex, setCharIndex] = useState(() =>
|
|
42
|
+
skipRef.current ? fullText.length : 0,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (skipRef.current) {
|
|
47
|
+
setCharIndex(fullText.length)
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (charIndex >= fullText.length) {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const msPerChar = 1000 / speed
|
|
56
|
+
let rafId: number
|
|
57
|
+
let lastTime: number | null = null
|
|
58
|
+
|
|
59
|
+
function tick(timestamp: number) {
|
|
60
|
+
lastTime ??= timestamp
|
|
61
|
+
|
|
62
|
+
const elapsed = timestamp - lastTime
|
|
63
|
+
const charsToAdd = Math.floor(elapsed / msPerChar)
|
|
64
|
+
|
|
65
|
+
if (charsToAdd > 0) {
|
|
66
|
+
lastTime = timestamp
|
|
67
|
+
setCharIndex((prev) => Math.min(prev + charsToAdd, fullText.length))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
rafId = requestAnimationFrame(tick)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
rafId = requestAnimationFrame(tick)
|
|
74
|
+
|
|
75
|
+
return () => cancelAnimationFrame(rafId)
|
|
76
|
+
}, [fullText, charIndex, speed])
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
displayedText: fullText.slice(0, charIndex),
|
|
80
|
+
isTyping: charIndex < fullText.length,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, test, expect, vi } from 'vitest'
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
+
import { CopyButton } from './copy-button'
|
|
4
|
+
|
|
5
|
+
vi.mock('@carto/ps-utils', () => ({
|
|
6
|
+
copy: vi.fn(() => Promise.resolve()),
|
|
7
|
+
}))
|
|
8
|
+
|
|
9
|
+
describe('CopyButton', () => {
|
|
10
|
+
test('renders copy button', () => {
|
|
11
|
+
render(
|
|
12
|
+
<CopyButton copyText='hello' onSuccess={vi.fn()} onError={vi.fn()} />,
|
|
13
|
+
)
|
|
14
|
+
expect(screen.getByRole('button')).toBeTruthy()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('renders with custom aria-label', () => {
|
|
18
|
+
render(
|
|
19
|
+
<CopyButton
|
|
20
|
+
copyText='hello'
|
|
21
|
+
onSuccess={vi.fn()}
|
|
22
|
+
onError={vi.fn()}
|
|
23
|
+
aria-label='Copy response'
|
|
24
|
+
/>,
|
|
25
|
+
)
|
|
26
|
+
expect(screen.getByLabelText('Copy response')).toBeTruthy()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('calls copy utility when clicked', async () => {
|
|
30
|
+
const { copy } = await import('@carto/ps-utils')
|
|
31
|
+
render(
|
|
32
|
+
<CopyButton
|
|
33
|
+
copyText='hello world'
|
|
34
|
+
onSuccess={vi.fn()}
|
|
35
|
+
onError={vi.fn()}
|
|
36
|
+
/>,
|
|
37
|
+
)
|
|
38
|
+
fireEvent.click(screen.getByRole('button'))
|
|
39
|
+
expect(copy).toHaveBeenCalledWith('hello world')
|
|
40
|
+
})
|
|
41
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { IconButton } from '@mui/material'
|
|
2
|
+
import { ContentCopyOutlined } from '@mui/icons-material'
|
|
3
|
+
import type { CopyButtonProps } from './types'
|
|
4
|
+
import { copy } from '@carto/ps-utils'
|
|
5
|
+
|
|
6
|
+
export function CopyButton({
|
|
7
|
+
copyText,
|
|
8
|
+
onSuccess,
|
|
9
|
+
onError,
|
|
10
|
+
'aria-label': ariaLabel,
|
|
11
|
+
...props
|
|
12
|
+
}: CopyButtonProps) {
|
|
13
|
+
async function onCopy() {
|
|
14
|
+
try {
|
|
15
|
+
await copy(copyText)
|
|
16
|
+
onSuccess?.()
|
|
17
|
+
} catch (err) {
|
|
18
|
+
onError?.(err as Error)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return (
|
|
22
|
+
<IconButton
|
|
23
|
+
size='small'
|
|
24
|
+
onClick={() => void onCopy()}
|
|
25
|
+
aria-label={ariaLabel ?? 'Copy'}
|
|
26
|
+
{...props}
|
|
27
|
+
>
|
|
28
|
+
<ContentCopyOutlined fontSize='small' />
|
|
29
|
+
</IconButton>
|
|
30
|
+
)
|
|
31
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -62,3 +62,6 @@ export type { BasemapsUIProps } from './basemaps/types'
|
|
|
62
62
|
|
|
63
63
|
export { Tooltip, setTooltipEnterDelay } from './tooltip/tooltip'
|
|
64
64
|
export { SmartTooltip } from './smart-tooltip/smart-tooltip'
|
|
65
|
+
|
|
66
|
+
export type { CopyButtonProps } from './copy-button/types'
|
|
67
|
+
export { CopyButton } from './copy-button/copy-button'
|
|
@@ -15,6 +15,14 @@ export {
|
|
|
15
15
|
type StackToggleProps,
|
|
16
16
|
type StackToggleLabels,
|
|
17
17
|
} from './stack-toggle'
|
|
18
|
+
export {
|
|
19
|
+
ShowAllToggle,
|
|
20
|
+
setShowAll,
|
|
21
|
+
SHOW_ALL_ID,
|
|
22
|
+
DEFAULT_SHOW_ALL_LABELS,
|
|
23
|
+
type ShowAllToggleProps,
|
|
24
|
+
type ShowAllLabels,
|
|
25
|
+
} from './show-all'
|
|
18
26
|
export {
|
|
19
27
|
ZoomToggle,
|
|
20
28
|
addZoom,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/react'
|
|
3
|
+
import { Provider } from '../../provider/widget-provider'
|
|
4
|
+
import { clearAllWidgetStores, getWidgetStore } from '../../stores'
|
|
5
|
+
import { ShowAllToggle, setShowAll, SHOW_ALL_ID } from './show-all'
|
|
6
|
+
|
|
7
|
+
beforeEach(() => clearAllWidgetStores())
|
|
8
|
+
afterEach(() => clearAllWidgetStores())
|
|
9
|
+
|
|
10
|
+
describe('setShowAll', () => {
|
|
11
|
+
it('writes the flag under transformStates[SHOW_ALL_ID]', () => {
|
|
12
|
+
render(
|
|
13
|
+
<Provider id='sa1' data={[]}>
|
|
14
|
+
<span />
|
|
15
|
+
</Provider>,
|
|
16
|
+
)
|
|
17
|
+
setShowAll('sa1', true)
|
|
18
|
+
expect(
|
|
19
|
+
getWidgetStore('sa1').getState().transformStates[SHOW_ALL_ID]?.enabled,
|
|
20
|
+
).toBe(true)
|
|
21
|
+
setShowAll('sa1', false)
|
|
22
|
+
expect(
|
|
23
|
+
getWidgetStore('sa1').getState().transformStates[SHOW_ALL_ID]?.enabled,
|
|
24
|
+
).toBe(false)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
describe('<ShowAllToggle>', () => {
|
|
29
|
+
it('renders a collapse (✕) button', () => {
|
|
30
|
+
render(
|
|
31
|
+
<Provider id='sa2' data={[]}>
|
|
32
|
+
<ShowAllToggle />
|
|
33
|
+
</Provider>,
|
|
34
|
+
)
|
|
35
|
+
expect(screen.getByRole('button', { name: 'Show less' })).toBeTruthy()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('clears the flag on click (collapses)', () => {
|
|
39
|
+
render(
|
|
40
|
+
<Provider id='sa3' data={[]}>
|
|
41
|
+
<ShowAllToggle />
|
|
42
|
+
</Provider>,
|
|
43
|
+
)
|
|
44
|
+
setShowAll('sa3', true)
|
|
45
|
+
fireEvent.click(screen.getByRole('button', { name: 'Show less' }))
|
|
46
|
+
expect(
|
|
47
|
+
getWidgetStore('sa3').getState().transformStates[SHOW_ALL_ID]?.enabled,
|
|
48
|
+
).toBe(false)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type ComponentType } from 'react'
|
|
2
|
+
import { IconButton, type SvgIconProps } from '@mui/material'
|
|
3
|
+
import CloseIcon from '@mui/icons-material/Close'
|
|
4
|
+
import { Tooltip } from '../../../components'
|
|
5
|
+
import { getWidgetStore, useWidgetId } from '../../stores'
|
|
6
|
+
import { DEFAULT_SHOW_ALL_LABELS, type ShowAllLabels } from './labels'
|
|
7
|
+
import { styles } from './style'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The `show-all` flag is a pure UI signal (no data/config transform): the
|
|
11
|
+
* Category overflow row writes it `true` via {@link setShowAll}, the
|
|
12
|
+
* composer reads it with `useTransformEnabled(id, SHOW_ALL_ID)` to drop the
|
|
13
|
+
* row cap, and this button writes it back `false` to collapse. Stored under
|
|
14
|
+
* `transformStates` so `useTransformEnabled` can observe it like any other
|
|
15
|
+
* action flag — but it never registers a pipeline transform, so toggling it
|
|
16
|
+
* never re-runs the data pipeline.
|
|
17
|
+
*/
|
|
18
|
+
export const SHOW_ALL_ID = 'show-all'
|
|
19
|
+
|
|
20
|
+
export interface ShowAllToggleProps {
|
|
21
|
+
labels?: Partial<ShowAllLabels>
|
|
22
|
+
icon?: ComponentType<SvgIconProps>
|
|
23
|
+
iconProps?: SvgIconProps
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Collapse (✕) affordance shown while a widget is expanded into its
|
|
28
|
+
* "show all" state. Clicking it clears the `show-all` flag, returning the
|
|
29
|
+
* widget to its capped view. Mount it conditionally (only while the flag is
|
|
30
|
+
* set) so it doesn't occupy a `Widget.Toolbox` visibility-budget slot when
|
|
31
|
+
* the widget is collapsed.
|
|
32
|
+
*/
|
|
33
|
+
export function ShowAllToggle({
|
|
34
|
+
labels,
|
|
35
|
+
icon: Icon = CloseIcon,
|
|
36
|
+
iconProps,
|
|
37
|
+
}: ShowAllToggleProps) {
|
|
38
|
+
const id = useWidgetId()
|
|
39
|
+
const _labels = { ...DEFAULT_SHOW_ALL_LABELS, ...labels }
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Tooltip title={_labels.toggle}>
|
|
43
|
+
<IconButton
|
|
44
|
+
size='small'
|
|
45
|
+
aria-label={_labels.toggle}
|
|
46
|
+
aria-pressed
|
|
47
|
+
onClick={() => setShowAll(id, false)}
|
|
48
|
+
sx={styles.toggle}
|
|
49
|
+
>
|
|
50
|
+
<Icon fontSize='small' {...iconProps} />
|
|
51
|
+
</IconButton>
|
|
52
|
+
</Tooltip>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Imperatively writes the `show-all` flag into the widget store. Mirrors
|
|
58
|
+
* {@link setSearcherText} — used by the Category overflow row (to expand)
|
|
59
|
+
* and the {@link ShowAllToggle} button (to collapse) without routing through
|
|
60
|
+
* a re-rendered subscription.
|
|
61
|
+
*/
|
|
62
|
+
export function setShowAll(widgetId: string, value: boolean): void {
|
|
63
|
+
getWidgetStore(widgetId).setState((s) => ({
|
|
64
|
+
transformStates: {
|
|
65
|
+
...s.transformStates,
|
|
66
|
+
[SHOW_ALL_ID]: {
|
|
67
|
+
...s.transformStates[SHOW_ALL_ID],
|
|
68
|
+
enabled: value,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
}))
|
|
72
|
+
}
|