@wealthx/shadcn 1.5.37 → 1.5.38
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/.turbo/turbo-build.log +99 -93
- package/CHANGELOG.md +6 -0
- package/dist/{chunk-734FOOJC.mjs → chunk-B5PSUONN.mjs} +25 -58
- package/dist/chunk-EFHPSKVF.mjs +192 -0
- package/dist/{chunk-NB3ZL36B.mjs → chunk-MZI77ZMX.mjs} +17 -2
- package/dist/chunk-R7M657QL.mjs +587 -0
- package/dist/{chunk-DIH2NZZ3.mjs → chunk-RRROLESJ.mjs} +33 -23
- package/dist/components/ui/ai-assistant-drawer.js +269 -121
- package/dist/components/ui/ai-assistant-drawer.mjs +2 -1
- package/dist/components/ui/ai-conversations/index.js +474 -286
- package/dist/components/ui/ai-conversations/index.mjs +2 -1
- package/dist/components/ui/chat-input-area.js +429 -0
- package/dist/components/ui/chat-input-area.mjs +11 -0
- package/dist/components/ui/page-top-bar.js +182 -5
- package/dist/components/ui/page-top-bar.mjs +3 -1
- package/dist/components/ui/support-agent/index.js +1131 -0
- package/dist/components/ui/support-agent/index.mjs +27 -0
- package/dist/index.js +4760 -4027
- package/dist/index.mjs +54 -36
- package/dist/styles.css +1 -1
- package/package.json +11 -1
- package/src/components/index.tsx +24 -0
- package/src/components/ui/ai-assistant-drawer.tsx +24 -51
- package/src/components/ui/ai-conversations/index.tsx +16 -8
- package/src/components/ui/ai-conversations/thread.tsx +38 -27
- package/src/components/ui/chat-input-area.tsx +244 -0
- package/src/components/ui/page-top-bar.tsx +31 -5
- package/src/components/ui/support-agent/index.tsx +25 -0
- package/src/components/ui/support-agent/support-agent-fab.tsx +116 -0
- package/src/components/ui/support-agent/support-agent-panel.tsx +498 -0
- package/src/components/ui/support-agent/support-agent-primitives.tsx +354 -0
- package/src/styles/globals.css +1 -0
- package/src/styles/styles-css.ts +1 -1
- package/tsup.config.ts +2 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ImagePlus, Paperclip, Send } from "lucide-react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
import { Textarea } from "@/components/ui/textarea";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* ChatInputArea — WealthX Design System
|
|
9
|
+
*
|
|
10
|
+
* General-purpose chat input used across any feature that involves
|
|
11
|
+
* a conversation or messaging interface (Policy AI, Support Agent,
|
|
12
|
+
* AI Conversations, Website Chat Widget, etc.).
|
|
13
|
+
*
|
|
14
|
+
* Features:
|
|
15
|
+
* - Textarea with auto-resize up to `maxHeight`
|
|
16
|
+
* - Enter to send / Shift+Enter for new line
|
|
17
|
+
* - Optional file attachment (Paperclip) — shown only when `onAttachFile` is provided
|
|
18
|
+
* - Optional image upload (ImagePlus) — shown only when `onAttachImage` is provided
|
|
19
|
+
* - Focus ring on outer container via `focus-within`
|
|
20
|
+
* - Fully disabled during streaming / loading states
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* <ChatInputArea
|
|
24
|
+
* value={inputValue}
|
|
25
|
+
* onChange={setInputValue}
|
|
26
|
+
* onSend={handleSend}
|
|
27
|
+
* onAttachFile={handleFiles}
|
|
28
|
+
* onAttachImage={handleImages}
|
|
29
|
+
* placeholder="Ask anything…"
|
|
30
|
+
* />
|
|
31
|
+
*/
|
|
32
|
+
export interface ChatInputAreaProps {
|
|
33
|
+
/** Controlled text value. */
|
|
34
|
+
value: string;
|
|
35
|
+
/** Called on every keystroke. */
|
|
36
|
+
onChange: (value: string) => void;
|
|
37
|
+
/**
|
|
38
|
+
* Called when the user submits (Enter key or Send button click).
|
|
39
|
+
* Receives the trimmed text. Not called when value is empty or when disabled.
|
|
40
|
+
*/
|
|
41
|
+
onSend: (value: string) => void;
|
|
42
|
+
/**
|
|
43
|
+
* When provided, a Paperclip button appears and this callback is fired
|
|
44
|
+
* with the selected FileList. Hidden when omitted.
|
|
45
|
+
*/
|
|
46
|
+
onAttachFile?: (files: FileList) => void;
|
|
47
|
+
/**
|
|
48
|
+
* When provided, an ImagePlus button appears and this callback is fired
|
|
49
|
+
* with the selected image FileList. Hidden when omitted.
|
|
50
|
+
*/
|
|
51
|
+
onAttachImage?: (files: FileList) => void;
|
|
52
|
+
/** Disables all controls — use while streaming / waiting for a response. */
|
|
53
|
+
disabled?: boolean;
|
|
54
|
+
/** Textarea placeholder text. */
|
|
55
|
+
placeholder?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Hint text rendered below the input box.
|
|
58
|
+
* Pass `false` to hide it entirely.
|
|
59
|
+
* Defaults to "Enter to send · Shift+Enter for new line".
|
|
60
|
+
*/
|
|
61
|
+
hint?: string | false;
|
|
62
|
+
/**
|
|
63
|
+
* Maximum textarea height in pixels before scrolling kicks in.
|
|
64
|
+
* @default 160
|
|
65
|
+
*/
|
|
66
|
+
maxHeight?: number;
|
|
67
|
+
/** Focus the textarea on mount. */
|
|
68
|
+
autoFocus?: boolean;
|
|
69
|
+
className?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const DEFAULT_HINT = "Enter to send · Shift+Enter for new line";
|
|
73
|
+
|
|
74
|
+
export function ChatInputArea({
|
|
75
|
+
value,
|
|
76
|
+
onChange,
|
|
77
|
+
onSend,
|
|
78
|
+
onAttachFile,
|
|
79
|
+
onAttachImage,
|
|
80
|
+
disabled = false,
|
|
81
|
+
placeholder = "Type your message…",
|
|
82
|
+
hint = DEFAULT_HINT,
|
|
83
|
+
maxHeight = 160,
|
|
84
|
+
autoFocus = false,
|
|
85
|
+
className,
|
|
86
|
+
}: ChatInputAreaProps) {
|
|
87
|
+
const textareaRef = React.useRef<HTMLTextAreaElement>(null);
|
|
88
|
+
const fileInputRef = React.useRef<HTMLInputElement>(null);
|
|
89
|
+
const imageInputRef = React.useRef<HTMLInputElement>(null);
|
|
90
|
+
|
|
91
|
+
// Focus on mount when autoFocus is requested
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
if (autoFocus) {
|
|
94
|
+
setTimeout(() => textareaRef.current?.focus(), 50);
|
|
95
|
+
}
|
|
96
|
+
}, [autoFocus]);
|
|
97
|
+
|
|
98
|
+
const handleSend = React.useCallback(() => {
|
|
99
|
+
const text = value.trim();
|
|
100
|
+
if (!text || disabled) return;
|
|
101
|
+
onSend(text);
|
|
102
|
+
// Reset textarea height after clearing (caller is responsible for clearing value)
|
|
103
|
+
if (textareaRef.current) {
|
|
104
|
+
textareaRef.current.style.height = "auto";
|
|
105
|
+
}
|
|
106
|
+
}, [value, disabled, onSend]);
|
|
107
|
+
|
|
108
|
+
const handleKeyDown = React.useCallback(
|
|
109
|
+
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
110
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
111
|
+
e.preventDefault();
|
|
112
|
+
handleSend();
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
[handleSend],
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const handleTextareaChange = React.useCallback(
|
|
119
|
+
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
120
|
+
onChange(e.target.value);
|
|
121
|
+
// Auto-resize
|
|
122
|
+
const el = e.target;
|
|
123
|
+
el.style.height = "auto";
|
|
124
|
+
el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`;
|
|
125
|
+
},
|
|
126
|
+
[onChange, maxHeight],
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const handleFileChange = React.useCallback(
|
|
130
|
+
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
131
|
+
if (e.target.files?.length) {
|
|
132
|
+
onAttachFile?.(e.target.files);
|
|
133
|
+
e.target.value = "";
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
[onAttachFile],
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const handleImageChange = React.useCallback(
|
|
140
|
+
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
141
|
+
if (e.target.files?.length) {
|
|
142
|
+
onAttachImage?.(e.target.files);
|
|
143
|
+
e.target.value = "";
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
[onAttachImage],
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const showFileButton = typeof onAttachFile === "function";
|
|
150
|
+
const showImageButton = typeof onAttachImage === "function";
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<div
|
|
154
|
+
data-slot="chat-input-area"
|
|
155
|
+
className={cn("flex flex-col gap-1.5", className)}
|
|
156
|
+
>
|
|
157
|
+
{/* Unified input box — textarea + action bar share one border */}
|
|
158
|
+
<div className="border border-border bg-background flex flex-col focus-within:ring-1 focus-within:ring-ring">
|
|
159
|
+
<Textarea
|
|
160
|
+
ref={textareaRef}
|
|
161
|
+
value={value}
|
|
162
|
+
onChange={handleTextareaChange}
|
|
163
|
+
onKeyDown={handleKeyDown}
|
|
164
|
+
placeholder={placeholder}
|
|
165
|
+
disabled={disabled}
|
|
166
|
+
rows={3}
|
|
167
|
+
className="resize-none text-sm border-0 shadow-none focus-visible:ring-0 px-3 pt-3 pb-1 min-h-[72px]"
|
|
168
|
+
aria-label={placeholder}
|
|
169
|
+
/>
|
|
170
|
+
|
|
171
|
+
{/* Action bar — attachment buttons on the left, Send on the right */}
|
|
172
|
+
<div className="flex items-center justify-between px-2 pb-2">
|
|
173
|
+
{/* Left: attachment buttons */}
|
|
174
|
+
<div className="flex items-center gap-0.5">
|
|
175
|
+
{showFileButton && (
|
|
176
|
+
<>
|
|
177
|
+
<Button
|
|
178
|
+
variant="ghost"
|
|
179
|
+
size="icon-sm"
|
|
180
|
+
type="button"
|
|
181
|
+
title="Attach file"
|
|
182
|
+
aria-label="Attach file"
|
|
183
|
+
disabled={disabled}
|
|
184
|
+
onClick={() => fileInputRef.current?.click()}
|
|
185
|
+
>
|
|
186
|
+
<Paperclip className="size-3.5" aria-hidden="true" />
|
|
187
|
+
</Button>
|
|
188
|
+
<input
|
|
189
|
+
ref={fileInputRef}
|
|
190
|
+
type="file"
|
|
191
|
+
multiple
|
|
192
|
+
className="sr-only"
|
|
193
|
+
tabIndex={-1}
|
|
194
|
+
onChange={handleFileChange}
|
|
195
|
+
/>
|
|
196
|
+
</>
|
|
197
|
+
)}
|
|
198
|
+
|
|
199
|
+
{showImageButton && (
|
|
200
|
+
<>
|
|
201
|
+
<Button
|
|
202
|
+
variant="ghost"
|
|
203
|
+
size="icon-sm"
|
|
204
|
+
type="button"
|
|
205
|
+
title="Upload image"
|
|
206
|
+
aria-label="Upload image"
|
|
207
|
+
disabled={disabled}
|
|
208
|
+
onClick={() => imageInputRef.current?.click()}
|
|
209
|
+
>
|
|
210
|
+
<ImagePlus className="size-3.5" aria-hidden="true" />
|
|
211
|
+
</Button>
|
|
212
|
+
<input
|
|
213
|
+
ref={imageInputRef}
|
|
214
|
+
type="file"
|
|
215
|
+
multiple
|
|
216
|
+
accept="image/*"
|
|
217
|
+
className="sr-only"
|
|
218
|
+
tabIndex={-1}
|
|
219
|
+
onChange={handleImageChange}
|
|
220
|
+
/>
|
|
221
|
+
</>
|
|
222
|
+
)}
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
{/* Right: Send */}
|
|
226
|
+
<Button
|
|
227
|
+
size="icon-sm"
|
|
228
|
+
type="button"
|
|
229
|
+
aria-label="Send message"
|
|
230
|
+
disabled={!value.trim() || disabled}
|
|
231
|
+
onClick={handleSend}
|
|
232
|
+
>
|
|
233
|
+
<Send className="size-3.5" aria-hidden="true" />
|
|
234
|
+
</Button>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
{/* Hint text */}
|
|
239
|
+
{hint !== false && (
|
|
240
|
+
<p className="text-xs text-muted-foreground">{hint}</p>
|
|
241
|
+
)}
|
|
242
|
+
</div>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { Bot } from "lucide-react";
|
|
3
|
+
import { Button } from "@/components/ui/button";
|
|
2
4
|
import { cn } from "@/lib/utils";
|
|
3
5
|
|
|
4
6
|
/**
|
|
@@ -10,6 +12,12 @@ import { cn } from "@/lib/utils";
|
|
|
10
12
|
*
|
|
11
13
|
* Used by: Loan CRM, Contact, AI Builder, and other data-heavy pages.
|
|
12
14
|
* For an in-page title block with a description, use PageHeader instead.
|
|
15
|
+
*
|
|
16
|
+
* Layout:
|
|
17
|
+
* [Title] [page-specific actions…] [Ask Support]
|
|
18
|
+
*
|
|
19
|
+
* "Ask Support" is always pinned to the far right when onAskSupport is
|
|
20
|
+
* provided — it is never passed through the actions slot.
|
|
13
21
|
*/
|
|
14
22
|
|
|
15
23
|
// ---------------------------------------------------------------------------
|
|
@@ -19,8 +27,13 @@ import { cn } from "@/lib/utils";
|
|
|
19
27
|
export interface PageTopBarProps {
|
|
20
28
|
/** Page title shown on the left */
|
|
21
29
|
title: string;
|
|
22
|
-
/**
|
|
30
|
+
/** Page-specific action elements (buttons, dropdowns, etc.) */
|
|
23
31
|
actions?: React.ReactNode;
|
|
32
|
+
/**
|
|
33
|
+
* When provided, renders the "Ask Support" button pinned to the far right,
|
|
34
|
+
* after all page-specific actions. The handler should open the SupportAgentPanel.
|
|
35
|
+
*/
|
|
36
|
+
onAskSupport?: () => void;
|
|
24
37
|
className?: string;
|
|
25
38
|
}
|
|
26
39
|
|
|
@@ -28,21 +41,34 @@ export interface PageTopBarProps {
|
|
|
28
41
|
// Component
|
|
29
42
|
// ---------------------------------------------------------------------------
|
|
30
43
|
|
|
31
|
-
export function PageTopBar({
|
|
44
|
+
export function PageTopBar({
|
|
45
|
+
title,
|
|
46
|
+
actions,
|
|
47
|
+
onAskSupport,
|
|
48
|
+
className,
|
|
49
|
+
}: PageTopBarProps) {
|
|
32
50
|
return (
|
|
33
51
|
<div
|
|
34
52
|
data-slot="page-top-bar"
|
|
35
53
|
className={cn(
|
|
36
54
|
"flex shrink-0 items-center justify-between gap-4",
|
|
37
55
|
"border-b border-border bg-background px-6 py-3",
|
|
38
|
-
className
|
|
56
|
+
className
|
|
39
57
|
)}
|
|
40
58
|
>
|
|
41
59
|
{/* Title */}
|
|
42
60
|
<h1 className="text-lg font-semibold leading-tight">{title}</h1>
|
|
43
61
|
|
|
44
|
-
{/*
|
|
45
|
-
|
|
62
|
+
{/* Right side: page actions + Ask Support (always last) */}
|
|
63
|
+
<div className="flex items-center gap-2">
|
|
64
|
+
{actions}
|
|
65
|
+
{onAskSupport && (
|
|
66
|
+
<Button variant="outline" size="sm" onClick={onAskSupport}>
|
|
67
|
+
<Bot className="size-3.5" aria-hidden="true" />
|
|
68
|
+
Ask Support
|
|
69
|
+
</Button>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
46
72
|
</div>
|
|
47
73
|
);
|
|
48
74
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export {
|
|
2
|
+
SupportContextChip,
|
|
3
|
+
SupportSuggestedQuestion,
|
|
4
|
+
SupportStepGuideCard,
|
|
5
|
+
SupportArticleCard,
|
|
6
|
+
} from "./support-agent-primitives";
|
|
7
|
+
export type {
|
|
8
|
+
SupportAgentContext,
|
|
9
|
+
SupportAgentStep,
|
|
10
|
+
SupportAgentRichContent,
|
|
11
|
+
SupportContextChipProps,
|
|
12
|
+
SupportSuggestedQuestionProps,
|
|
13
|
+
SupportStepGuideCardProps,
|
|
14
|
+
SupportArticleCardProps,
|
|
15
|
+
} from "./support-agent-primitives";
|
|
16
|
+
|
|
17
|
+
export { SupportAgentFAB } from "./support-agent-fab";
|
|
18
|
+
export type { SupportAgentFABProps } from "./support-agent-fab";
|
|
19
|
+
|
|
20
|
+
export { SupportAgentPanel } from "./support-agent-panel";
|
|
21
|
+
export type {
|
|
22
|
+
SupportAgentMessage,
|
|
23
|
+
SupportAgentRecentConversation,
|
|
24
|
+
SupportAgentPanelProps,
|
|
25
|
+
} from "./support-agent-panel";
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { HelpCircle, X } from "lucide-react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* SupportAgentFAB — WealthX DS
|
|
8
|
+
*
|
|
9
|
+
* Floating action button that opens/closes the Support Agent panel.
|
|
10
|
+
* Positioned fixed at bottom-right (or bottom-left) of the viewport.
|
|
11
|
+
*
|
|
12
|
+
* Features:
|
|
13
|
+
* - Nudge badge: red dot shown when the agent has a proactive suggestion
|
|
14
|
+
* - Nudge message: small dismissible tooltip bubble above the button
|
|
15
|
+
* - Toggles between HelpCircle (closed) and X (open) icon
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Types
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
export interface SupportAgentFABProps {
|
|
23
|
+
/** Whether the support panel is currently open. */
|
|
24
|
+
isOpen?: boolean;
|
|
25
|
+
/** Called when the FAB is clicked. */
|
|
26
|
+
onClick: () => void;
|
|
27
|
+
/** Show a red nudge dot on the button. */
|
|
28
|
+
hasNudge?: boolean;
|
|
29
|
+
/** Short message shown in a bubble above the FAB when hasNudge is true. */
|
|
30
|
+
nudgeMessage?: string;
|
|
31
|
+
/** Called when the user dismisses the nudge bubble. */
|
|
32
|
+
onDismissNudge?: () => void;
|
|
33
|
+
/** Position of the FAB on the screen. */
|
|
34
|
+
position?: "bottom-right" | "bottom-left";
|
|
35
|
+
className?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// SupportAgentFAB
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
export function SupportAgentFAB({
|
|
43
|
+
isOpen = false,
|
|
44
|
+
onClick,
|
|
45
|
+
hasNudge = false,
|
|
46
|
+
nudgeMessage,
|
|
47
|
+
onDismissNudge,
|
|
48
|
+
position = "bottom-right",
|
|
49
|
+
className,
|
|
50
|
+
}: SupportAgentFABProps) {
|
|
51
|
+
const showBubble = hasNudge && !!nudgeMessage && !isOpen;
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
className={cn(
|
|
56
|
+
"fixed bottom-6 z-50 flex flex-col items-end gap-2",
|
|
57
|
+
position === "bottom-right" ? "right-6" : "left-6",
|
|
58
|
+
className
|
|
59
|
+
)}
|
|
60
|
+
data-slot="support-agent-fab"
|
|
61
|
+
>
|
|
62
|
+
{/* Nudge message bubble */}
|
|
63
|
+
{showBubble && (
|
|
64
|
+
<div className="relative flex max-w-[220px] items-start gap-2 border border-border bg-background px-3 py-2 shadow-md">
|
|
65
|
+
<p className="flex-1 text-xs text-foreground leading-relaxed">
|
|
66
|
+
{nudgeMessage}
|
|
67
|
+
</p>
|
|
68
|
+
{onDismissNudge && (
|
|
69
|
+
<button
|
|
70
|
+
type="button"
|
|
71
|
+
onClick={onDismissNudge}
|
|
72
|
+
className="mt-0.5 shrink-0 text-muted-foreground hover:text-foreground"
|
|
73
|
+
aria-label="Dismiss"
|
|
74
|
+
>
|
|
75
|
+
<X className="size-3" />
|
|
76
|
+
</button>
|
|
77
|
+
)}
|
|
78
|
+
{/* Bubble tail pointing down */}
|
|
79
|
+
<span
|
|
80
|
+
className="absolute -bottom-[5px] right-4 size-2.5 rotate-45 border-b border-r border-border bg-background"
|
|
81
|
+
aria-hidden="true"
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
)}
|
|
85
|
+
|
|
86
|
+
{/* FAB button */}
|
|
87
|
+
<div className="relative">
|
|
88
|
+
<Button
|
|
89
|
+
type="button"
|
|
90
|
+
size="icon"
|
|
91
|
+
onClick={onClick}
|
|
92
|
+
className="size-12 rounded-full shadow-lg"
|
|
93
|
+
aria-label={
|
|
94
|
+
isOpen ? "Close support assistant" : "Open support assistant"
|
|
95
|
+
}
|
|
96
|
+
data-state={isOpen ? "open" : "closed"}
|
|
97
|
+
>
|
|
98
|
+
{isOpen ? (
|
|
99
|
+
<X className="size-5" aria-hidden="true" />
|
|
100
|
+
) : (
|
|
101
|
+
<HelpCircle className="size-5" aria-hidden="true" />
|
|
102
|
+
)}
|
|
103
|
+
</Button>
|
|
104
|
+
|
|
105
|
+
{/* Nudge dot */}
|
|
106
|
+
{hasNudge && !isOpen && (
|
|
107
|
+
<span
|
|
108
|
+
className="absolute -right-0.5 -top-0.5 size-3 rounded-full border-2 border-background bg-destructive"
|
|
109
|
+
aria-label="New suggestion"
|
|
110
|
+
role="status"
|
|
111
|
+
/>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
}
|