@vllnt/ui 0.2.1-canary.9e6a7be → 0.2.1-canary.ab2c69a
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/components/conversation-thread/conversation-thread.js +348 -0
- package/dist/components/conversation-thread/index.js +20 -0
- package/dist/components/index.js +42 -0
- package/dist/components/infinite-plane/index.js +6 -0
- package/dist/components/infinite-plane/infinite-plane.js +75 -0
- package/dist/components/presence-stack/index.js +6 -0
- package/dist/components/presence-stack/presence-stack.js +108 -0
- package/dist/components/selection-presence/index.js +6 -0
- package/dist/components/selection-presence/selection-presence.js +50 -0
- package/dist/components/thread-bubble/index.js +6 -0
- package/dist/components/thread-bubble/thread-bubble.js +85 -0
- package/dist/components/viewport-bookmarks/index.js +6 -0
- package/dist/components/viewport-bookmarks/viewport-bookmarks.js +116 -0
- package/dist/components/world-breadcrumbs/index.js +6 -0
- package/dist/components/world-breadcrumbs/world-breadcrumbs.js +114 -0
- package/dist/index.d.ts +564 -2
- package/package.json +1 -1
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
forwardRef,
|
|
6
|
+
useCallback,
|
|
7
|
+
useContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
useState
|
|
12
|
+
} from "react";
|
|
13
|
+
import { ArrowDown, RefreshCw, ThumbsDown, ThumbsUp } from "lucide-react";
|
|
14
|
+
import { cn } from "../../lib/utils";
|
|
15
|
+
import { ThinkingBlock } from "../thinking-block";
|
|
16
|
+
const ConversationThreadContext = createContext(null);
|
|
17
|
+
function useConversationThreadContext() {
|
|
18
|
+
const ctx = useContext(ConversationThreadContext);
|
|
19
|
+
if (!ctx) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
"ConversationThread compound components must be used within <ConversationThread>"
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
return ctx;
|
|
25
|
+
}
|
|
26
|
+
function MessageActions({ messageId }) {
|
|
27
|
+
const { onFeedback, onRetry } = useConversationThreadContext();
|
|
28
|
+
return /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center gap-1", children: [
|
|
29
|
+
onRetry ? /* @__PURE__ */ jsx(
|
|
30
|
+
"button",
|
|
31
|
+
{
|
|
32
|
+
"aria-label": "Retry message",
|
|
33
|
+
className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
34
|
+
onClick: () => {
|
|
35
|
+
onRetry(messageId);
|
|
36
|
+
},
|
|
37
|
+
type: "button",
|
|
38
|
+
children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-3 w-3" })
|
|
39
|
+
}
|
|
40
|
+
) : null,
|
|
41
|
+
onFeedback ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
42
|
+
/* @__PURE__ */ jsx(
|
|
43
|
+
"button",
|
|
44
|
+
{
|
|
45
|
+
"aria-label": "Positive feedback",
|
|
46
|
+
className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
47
|
+
onClick: () => {
|
|
48
|
+
onFeedback(messageId, "positive");
|
|
49
|
+
},
|
|
50
|
+
type: "button",
|
|
51
|
+
children: /* @__PURE__ */ jsx(ThumbsUp, { className: "h-3 w-3" })
|
|
52
|
+
}
|
|
53
|
+
),
|
|
54
|
+
/* @__PURE__ */ jsx(
|
|
55
|
+
"button",
|
|
56
|
+
{
|
|
57
|
+
"aria-label": "Negative feedback",
|
|
58
|
+
className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
59
|
+
onClick: () => {
|
|
60
|
+
onFeedback(messageId, "negative");
|
|
61
|
+
},
|
|
62
|
+
type: "button",
|
|
63
|
+
children: /* @__PURE__ */ jsx(ThumbsDown, { className: "h-3 w-3" })
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
] }) : null
|
|
67
|
+
] });
|
|
68
|
+
}
|
|
69
|
+
function MessageItem({ message }) {
|
|
70
|
+
const isUser = message.role === "user";
|
|
71
|
+
return /* @__PURE__ */ jsx(
|
|
72
|
+
"div",
|
|
73
|
+
{
|
|
74
|
+
className: cn(
|
|
75
|
+
"mb-4 flex gap-3",
|
|
76
|
+
isUser ? "justify-end" : "justify-start"
|
|
77
|
+
),
|
|
78
|
+
children: /* @__PURE__ */ jsxs(
|
|
79
|
+
"div",
|
|
80
|
+
{
|
|
81
|
+
className: cn(
|
|
82
|
+
"max-w-[80%] rounded-2xl px-4 py-3 text-sm",
|
|
83
|
+
isUser ? "rounded-br-sm bg-primary text-primary-foreground" : "rounded-bl-sm bg-muted text-foreground"
|
|
84
|
+
),
|
|
85
|
+
children: [
|
|
86
|
+
!isUser && message.thinking ? /* @__PURE__ */ jsx(
|
|
87
|
+
ThinkingBlock,
|
|
88
|
+
{
|
|
89
|
+
isStreaming: message.isStreaming,
|
|
90
|
+
thinking: message.thinking
|
|
91
|
+
}
|
|
92
|
+
) : null,
|
|
93
|
+
message.toolCalls && message.toolCalls.length > 0 ? /* @__PURE__ */ jsx(
|
|
94
|
+
"ul",
|
|
95
|
+
{
|
|
96
|
+
"aria-label": "Tool calls",
|
|
97
|
+
className: "mb-2 flex flex-col gap-1 text-xs text-muted-foreground",
|
|
98
|
+
children: message.toolCalls.map((toolCall) => /* @__PURE__ */ jsx("li", { className: "font-mono", children: toolCall.name }, toolCall.id))
|
|
99
|
+
}
|
|
100
|
+
) : null,
|
|
101
|
+
/* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap leading-relaxed", children: message.content }),
|
|
102
|
+
isUser ? null : /* @__PURE__ */ jsx(MessageActions, { messageId: message.id })
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
function useConversationScroll(messages, isStreaming) {
|
|
110
|
+
const scrollContainerRef = useRef(null);
|
|
111
|
+
const messagesEndRef = useRef(null);
|
|
112
|
+
const isAtBottomRef = useRef(true);
|
|
113
|
+
const [isAtBottom, setIsAtBottom] = useState(true);
|
|
114
|
+
const scrollToBottom = useCallback(() => {
|
|
115
|
+
const element = messagesEndRef.current;
|
|
116
|
+
if (element && typeof element.scrollIntoView === "function") {
|
|
117
|
+
element.scrollIntoView({ behavior: "smooth" });
|
|
118
|
+
}
|
|
119
|
+
}, []);
|
|
120
|
+
const scrollToBottomInstant = useCallback(() => {
|
|
121
|
+
const element = messagesEndRef.current;
|
|
122
|
+
if (element && typeof element.scrollIntoView === "function") {
|
|
123
|
+
element.scrollIntoView({ behavior: "instant" });
|
|
124
|
+
}
|
|
125
|
+
}, []);
|
|
126
|
+
const handleScroll = useCallback(() => {
|
|
127
|
+
const container = scrollContainerRef.current;
|
|
128
|
+
if (!container) return;
|
|
129
|
+
const { clientHeight, scrollHeight, scrollTop } = container;
|
|
130
|
+
const nearBottom = scrollHeight - scrollTop - clientHeight <= 100;
|
|
131
|
+
isAtBottomRef.current = nearBottom;
|
|
132
|
+
setIsAtBottom(nearBottom);
|
|
133
|
+
}, []);
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
if (!isAtBottomRef.current) return;
|
|
136
|
+
scrollToBottomInstant();
|
|
137
|
+
}, [messages, scrollToBottomInstant]);
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
if (!isStreaming || !isAtBottomRef.current) return;
|
|
140
|
+
scrollToBottomInstant();
|
|
141
|
+
}, [isStreaming, scrollToBottomInstant]);
|
|
142
|
+
return {
|
|
143
|
+
handleScroll,
|
|
144
|
+
isAtBottom,
|
|
145
|
+
messagesEndRef,
|
|
146
|
+
scrollContainerRef,
|
|
147
|
+
scrollToBottom
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const ConversationThread = forwardRef(
|
|
151
|
+
({
|
|
152
|
+
children,
|
|
153
|
+
className,
|
|
154
|
+
isStreaming = false,
|
|
155
|
+
messages,
|
|
156
|
+
onFeedback,
|
|
157
|
+
onRetry,
|
|
158
|
+
onSend
|
|
159
|
+
}, reference) => {
|
|
160
|
+
const {
|
|
161
|
+
handleScroll,
|
|
162
|
+
isAtBottom,
|
|
163
|
+
messagesEndRef,
|
|
164
|
+
scrollContainerRef,
|
|
165
|
+
scrollToBottom
|
|
166
|
+
} = useConversationScroll(messages, isStreaming);
|
|
167
|
+
const contextValue = useMemo(
|
|
168
|
+
() => ({
|
|
169
|
+
handleScroll,
|
|
170
|
+
isAtBottom,
|
|
171
|
+
isStreaming,
|
|
172
|
+
messages,
|
|
173
|
+
messagesEndRef,
|
|
174
|
+
onFeedback,
|
|
175
|
+
onRetry,
|
|
176
|
+
onSend,
|
|
177
|
+
scrollContainerRef,
|
|
178
|
+
scrollToBottom
|
|
179
|
+
}),
|
|
180
|
+
[
|
|
181
|
+
handleScroll,
|
|
182
|
+
isAtBottom,
|
|
183
|
+
isStreaming,
|
|
184
|
+
messages,
|
|
185
|
+
messagesEndRef,
|
|
186
|
+
onFeedback,
|
|
187
|
+
onRetry,
|
|
188
|
+
onSend,
|
|
189
|
+
scrollContainerRef,
|
|
190
|
+
scrollToBottom
|
|
191
|
+
]
|
|
192
|
+
);
|
|
193
|
+
return /* @__PURE__ */ jsx(ConversationThreadContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
|
|
194
|
+
"div",
|
|
195
|
+
{
|
|
196
|
+
className: cn("flex h-full flex-col overflow-hidden", className),
|
|
197
|
+
ref: reference,
|
|
198
|
+
children
|
|
199
|
+
}
|
|
200
|
+
) });
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
ConversationThread.displayName = "ConversationThread";
|
|
204
|
+
const ConversationHeader = forwardRef(({ children, className }, reference) => {
|
|
205
|
+
return /* @__PURE__ */ jsx(
|
|
206
|
+
"div",
|
|
207
|
+
{
|
|
208
|
+
className: cn("flex shrink-0 items-center border-b px-4 py-3", className),
|
|
209
|
+
ref: reference,
|
|
210
|
+
children
|
|
211
|
+
}
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
ConversationHeader.displayName = "ConversationHeader";
|
|
215
|
+
const ConversationTitle = forwardRef(({ children, className }, reference) => {
|
|
216
|
+
return /* @__PURE__ */ jsx(
|
|
217
|
+
"h2",
|
|
218
|
+
{
|
|
219
|
+
className: cn("text-sm font-semibold leading-none", className),
|
|
220
|
+
ref: reference,
|
|
221
|
+
children
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
ConversationTitle.displayName = "ConversationTitle";
|
|
226
|
+
const ConversationMessages = forwardRef(({ children, className }, reference) => {
|
|
227
|
+
const { handleScroll, messages, messagesEndRef, scrollContainerRef } = useConversationThreadContext();
|
|
228
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("relative min-h-0 flex-1", className), ref: reference, children: [
|
|
229
|
+
/* @__PURE__ */ jsx(
|
|
230
|
+
"div",
|
|
231
|
+
{
|
|
232
|
+
"aria-label": "Conversation messages",
|
|
233
|
+
"aria-live": "polite",
|
|
234
|
+
className: "absolute inset-0 overflow-y-auto",
|
|
235
|
+
onScroll: handleScroll,
|
|
236
|
+
ref: scrollContainerRef,
|
|
237
|
+
role: "log",
|
|
238
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col p-4", children: [
|
|
239
|
+
messages.map((message) => /* @__PURE__ */ jsx(MessageItem, { message }, message.id)),
|
|
240
|
+
/* @__PURE__ */ jsx("div", { "aria-hidden": "true", ref: messagesEndRef })
|
|
241
|
+
] })
|
|
242
|
+
}
|
|
243
|
+
),
|
|
244
|
+
children
|
|
245
|
+
] });
|
|
246
|
+
});
|
|
247
|
+
ConversationMessages.displayName = "ConversationMessages";
|
|
248
|
+
const ConversationEmpty = forwardRef(({ children, className }, reference) => {
|
|
249
|
+
const { messages } = useConversationThreadContext();
|
|
250
|
+
if (messages.length > 0) return null;
|
|
251
|
+
return /* @__PURE__ */ jsx(
|
|
252
|
+
"div",
|
|
253
|
+
{
|
|
254
|
+
className: cn(
|
|
255
|
+
"pointer-events-none absolute inset-0 flex flex-col items-center justify-center gap-4 p-8",
|
|
256
|
+
className
|
|
257
|
+
),
|
|
258
|
+
ref: reference,
|
|
259
|
+
children: /* @__PURE__ */ jsx("div", { className: "pointer-events-auto flex flex-col items-center gap-4", children })
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
ConversationEmpty.displayName = "ConversationEmpty";
|
|
264
|
+
const ConversationSuggestions = forwardRef(({ className, suggestions = [] }, reference) => {
|
|
265
|
+
const { onSend } = useConversationThreadContext();
|
|
266
|
+
return /* @__PURE__ */ jsx(
|
|
267
|
+
"div",
|
|
268
|
+
{
|
|
269
|
+
className: cn("flex flex-wrap justify-center gap-2", className),
|
|
270
|
+
ref: reference,
|
|
271
|
+
children: suggestions.map((suggestion) => /* @__PURE__ */ jsx(
|
|
272
|
+
"button",
|
|
273
|
+
{
|
|
274
|
+
className: "rounded-full border bg-background px-4 py-2 text-sm transition-colors hover:bg-muted",
|
|
275
|
+
onClick: () => onSend?.(suggestion),
|
|
276
|
+
type: "button",
|
|
277
|
+
children: suggestion
|
|
278
|
+
},
|
|
279
|
+
suggestion
|
|
280
|
+
))
|
|
281
|
+
}
|
|
282
|
+
);
|
|
283
|
+
});
|
|
284
|
+
ConversationSuggestions.displayName = "ConversationSuggestions";
|
|
285
|
+
const ConversationScrollButton = forwardRef(({ className }, reference) => {
|
|
286
|
+
const { isAtBottom, scrollToBottom } = useConversationThreadContext();
|
|
287
|
+
if (isAtBottom) return null;
|
|
288
|
+
return /* @__PURE__ */ jsx(
|
|
289
|
+
"button",
|
|
290
|
+
{
|
|
291
|
+
"aria-label": "Scroll to bottom",
|
|
292
|
+
className: cn(
|
|
293
|
+
"absolute bottom-4 right-4 flex h-8 w-8 items-center justify-center rounded-full border bg-background shadow-md transition-colors hover:bg-muted",
|
|
294
|
+
className
|
|
295
|
+
),
|
|
296
|
+
onClick: scrollToBottom,
|
|
297
|
+
ref: reference,
|
|
298
|
+
type: "button",
|
|
299
|
+
children: /* @__PURE__ */ jsx(ArrowDown, { className: "h-4 w-4" })
|
|
300
|
+
}
|
|
301
|
+
);
|
|
302
|
+
});
|
|
303
|
+
ConversationScrollButton.displayName = "ConversationScrollButton";
|
|
304
|
+
const ConversationLoading = forwardRef(({ className }, reference) => {
|
|
305
|
+
const { isStreaming, messages } = useConversationThreadContext();
|
|
306
|
+
const lastMessage = messages.at(-1);
|
|
307
|
+
if (!isStreaming || lastMessage?.role !== "assistant") return null;
|
|
308
|
+
return /* @__PURE__ */ jsxs(
|
|
309
|
+
"div",
|
|
310
|
+
{
|
|
311
|
+
"aria-label": "Assistant is typing",
|
|
312
|
+
className: cn(
|
|
313
|
+
"absolute bottom-4 left-4 flex items-center gap-1",
|
|
314
|
+
className
|
|
315
|
+
),
|
|
316
|
+
ref: reference,
|
|
317
|
+
role: "status",
|
|
318
|
+
children: [
|
|
319
|
+
/* @__PURE__ */ jsx(
|
|
320
|
+
"span",
|
|
321
|
+
{
|
|
322
|
+
className: "h-2 w-2 animate-bounce rounded-full bg-muted-foreground",
|
|
323
|
+
style: { animationDelay: "-0.3s" }
|
|
324
|
+
}
|
|
325
|
+
),
|
|
326
|
+
/* @__PURE__ */ jsx(
|
|
327
|
+
"span",
|
|
328
|
+
{
|
|
329
|
+
className: "h-2 w-2 animate-bounce rounded-full bg-muted-foreground",
|
|
330
|
+
style: { animationDelay: "-0.15s" }
|
|
331
|
+
}
|
|
332
|
+
),
|
|
333
|
+
/* @__PURE__ */ jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-muted-foreground" })
|
|
334
|
+
]
|
|
335
|
+
}
|
|
336
|
+
);
|
|
337
|
+
});
|
|
338
|
+
ConversationLoading.displayName = "ConversationLoading";
|
|
339
|
+
export {
|
|
340
|
+
ConversationEmpty,
|
|
341
|
+
ConversationHeader,
|
|
342
|
+
ConversationLoading,
|
|
343
|
+
ConversationMessages,
|
|
344
|
+
ConversationScrollButton,
|
|
345
|
+
ConversationSuggestions,
|
|
346
|
+
ConversationThread,
|
|
347
|
+
ConversationTitle
|
|
348
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConversationEmpty,
|
|
3
|
+
ConversationHeader,
|
|
4
|
+
ConversationLoading,
|
|
5
|
+
ConversationMessages,
|
|
6
|
+
ConversationScrollButton,
|
|
7
|
+
ConversationSuggestions,
|
|
8
|
+
ConversationThread,
|
|
9
|
+
ConversationTitle
|
|
10
|
+
} from "./conversation-thread";
|
|
11
|
+
export {
|
|
12
|
+
ConversationEmpty,
|
|
13
|
+
ConversationHeader,
|
|
14
|
+
ConversationLoading,
|
|
15
|
+
ConversationMessages,
|
|
16
|
+
ConversationScrollButton,
|
|
17
|
+
ConversationSuggestions,
|
|
18
|
+
ConversationThread,
|
|
19
|
+
ConversationTitle
|
|
20
|
+
};
|
package/dist/components/index.js
CHANGED
|
@@ -306,6 +306,9 @@ import {
|
|
|
306
306
|
ChatDockSection
|
|
307
307
|
} from "./chat-dock-section";
|
|
308
308
|
import { GlassPanel } from "./glass-panel";
|
|
309
|
+
import {
|
|
310
|
+
InfinitePlane
|
|
311
|
+
} from "./infinite-plane";
|
|
309
312
|
import { LeftRail } from "./left-rail";
|
|
310
313
|
import {
|
|
311
314
|
MiniMapPanel
|
|
@@ -323,6 +326,12 @@ import { Sidebar } from "./sidebar";
|
|
|
323
326
|
import { SidebarProvider, useSidebar } from "./sidebar-provider";
|
|
324
327
|
import { TableOfContents } from "./table-of-contents";
|
|
325
328
|
import { TopBar } from "./top-bar";
|
|
329
|
+
import {
|
|
330
|
+
ViewportBookmarks
|
|
331
|
+
} from "./viewport-bookmarks";
|
|
332
|
+
import {
|
|
333
|
+
WorldBreadcrumbs
|
|
334
|
+
} from "./world-breadcrumbs";
|
|
326
335
|
import { ZoomHUD } from "./zoom-hud";
|
|
327
336
|
import {
|
|
328
337
|
ActivityLog
|
|
@@ -539,6 +548,25 @@ import {
|
|
|
539
548
|
ObjectCard
|
|
540
549
|
} from "./object-card";
|
|
541
550
|
import { ObjectHandle } from "./object-handle";
|
|
551
|
+
import {
|
|
552
|
+
PresenceStack
|
|
553
|
+
} from "./presence-stack";
|
|
554
|
+
import {
|
|
555
|
+
SelectionPresence
|
|
556
|
+
} from "./selection-presence";
|
|
557
|
+
import {
|
|
558
|
+
ThreadBubble
|
|
559
|
+
} from "./thread-bubble";
|
|
560
|
+
import {
|
|
561
|
+
ConversationEmpty,
|
|
562
|
+
ConversationHeader,
|
|
563
|
+
ConversationLoading,
|
|
564
|
+
ConversationMessages,
|
|
565
|
+
ConversationScrollButton,
|
|
566
|
+
ConversationSuggestions,
|
|
567
|
+
ConversationThread,
|
|
568
|
+
ConversationTitle
|
|
569
|
+
} from "./conversation-thread";
|
|
542
570
|
import { InlineInput } from "./inline-input";
|
|
543
571
|
import {
|
|
544
572
|
ModelSelector
|
|
@@ -644,6 +672,14 @@ export {
|
|
|
644
672
|
ContextMenuSubContent,
|
|
645
673
|
ContextMenuSubTrigger,
|
|
646
674
|
ContextMenuTrigger,
|
|
675
|
+
ConversationEmpty,
|
|
676
|
+
ConversationHeader,
|
|
677
|
+
ConversationLoading,
|
|
678
|
+
ConversationMessages,
|
|
679
|
+
ConversationScrollButton,
|
|
680
|
+
ConversationSuggestions,
|
|
681
|
+
ConversationThread,
|
|
682
|
+
ConversationTitle,
|
|
647
683
|
CookieConsent,
|
|
648
684
|
CountdownTimer,
|
|
649
685
|
CreditBadge,
|
|
@@ -719,6 +755,7 @@ export {
|
|
|
719
755
|
HoverCard,
|
|
720
756
|
HoverCardContent,
|
|
721
757
|
HoverCardTrigger,
|
|
758
|
+
InfinitePlane,
|
|
722
759
|
InlineInput,
|
|
723
760
|
Input,
|
|
724
761
|
InputOTP,
|
|
@@ -780,6 +817,7 @@ export {
|
|
|
780
817
|
PopoverContent,
|
|
781
818
|
PopoverTrigger,
|
|
782
819
|
Prerequisites,
|
|
820
|
+
PresenceStack,
|
|
783
821
|
ProTip,
|
|
784
822
|
ProfileSection,
|
|
785
823
|
ProgressBar,
|
|
@@ -817,6 +855,7 @@ export {
|
|
|
817
855
|
SelectSeparator,
|
|
818
856
|
SelectTrigger,
|
|
819
857
|
SelectValue,
|
|
858
|
+
SelectionPresence,
|
|
820
859
|
Separator,
|
|
821
860
|
SeverityBadge,
|
|
822
861
|
ShareDialog,
|
|
@@ -872,6 +911,7 @@ export {
|
|
|
872
911
|
ThemeProvider,
|
|
873
912
|
ThemeToggle,
|
|
874
913
|
ThinkingBlock,
|
|
914
|
+
ThreadBubble,
|
|
875
915
|
TickerTape,
|
|
876
916
|
Toast,
|
|
877
917
|
ToastAction,
|
|
@@ -898,9 +938,11 @@ export {
|
|
|
898
938
|
UsageBreakdown,
|
|
899
939
|
VideoEmbed,
|
|
900
940
|
ViewSwitcher,
|
|
941
|
+
ViewportBookmarks,
|
|
901
942
|
WalletCard,
|
|
902
943
|
Watchlist,
|
|
903
944
|
WorkspaceSwitcher,
|
|
945
|
+
WorldBreadcrumbs,
|
|
904
946
|
WorldClockBar,
|
|
905
947
|
ZoomHUD,
|
|
906
948
|
alertVariants,
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import {
|
|
4
|
+
forwardRef
|
|
5
|
+
} from "react";
|
|
6
|
+
import { cn } from "../../lib/utils";
|
|
7
|
+
const DEFAULT_LABELS = {
|
|
8
|
+
region: "Infinite plane"
|
|
9
|
+
};
|
|
10
|
+
const safeSpacing = (value) => value < 4 ? 4 : value;
|
|
11
|
+
const safeZoom = (value) => {
|
|
12
|
+
if (value < 0.1) {
|
|
13
|
+
return 0.1;
|
|
14
|
+
}
|
|
15
|
+
if (value > 10) {
|
|
16
|
+
return 10;
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
};
|
|
20
|
+
const buildBackground = (input) => {
|
|
21
|
+
if (input.pattern === "blank") {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
const size = safeSpacing(input.spacing) * safeZoom(input.zoom);
|
|
25
|
+
const pos = `${input.translate.x}px ${input.translate.y}px`;
|
|
26
|
+
if (input.pattern === "grid") {
|
|
27
|
+
return {
|
|
28
|
+
backgroundImage: "linear-gradient(to right, hsl(var(--border)) 1px, transparent 1px), linear-gradient(to bottom, hsl(var(--border)) 1px, transparent 1px)",
|
|
29
|
+
backgroundPosition: pos,
|
|
30
|
+
backgroundSize: `${size}px ${size}px`
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
backgroundImage: "radial-gradient(circle, hsl(var(--border)) 1px, transparent 1px)",
|
|
35
|
+
backgroundPosition: pos,
|
|
36
|
+
backgroundSize: `${size}px ${size}px`
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
const InfinitePlane = forwardRef(
|
|
40
|
+
(props, ref) => {
|
|
41
|
+
const {
|
|
42
|
+
children,
|
|
43
|
+
className,
|
|
44
|
+
labels,
|
|
45
|
+
pattern = "dot",
|
|
46
|
+
spacing = 32,
|
|
47
|
+
translate = { x: 0, y: 0 },
|
|
48
|
+
zoom = 1,
|
|
49
|
+
...rest
|
|
50
|
+
} = props;
|
|
51
|
+
const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
|
|
52
|
+
const background = buildBackground({ pattern, spacing, translate, zoom });
|
|
53
|
+
return /* @__PURE__ */ jsx(
|
|
54
|
+
"div",
|
|
55
|
+
{
|
|
56
|
+
"aria-label": resolvedLabels.region,
|
|
57
|
+
className: cn(
|
|
58
|
+
"relative h-full w-full overflow-hidden bg-background",
|
|
59
|
+
className
|
|
60
|
+
),
|
|
61
|
+
"data-infinite-plane": true,
|
|
62
|
+
"data-infinite-plane-pattern": pattern,
|
|
63
|
+
ref,
|
|
64
|
+
role: "region",
|
|
65
|
+
style: background,
|
|
66
|
+
...rest,
|
|
67
|
+
children
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
InfinitePlane.displayName = "InfinitePlane";
|
|
73
|
+
export {
|
|
74
|
+
InfinitePlane
|
|
75
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import {
|
|
4
|
+
forwardRef
|
|
5
|
+
} from "react";
|
|
6
|
+
import { cn } from "../../lib/utils";
|
|
7
|
+
const STATUS_DOT = {
|
|
8
|
+
active: "bg-emerald-500",
|
|
9
|
+
away: "bg-amber-500",
|
|
10
|
+
idle: "bg-muted-foreground",
|
|
11
|
+
offline: "bg-muted-foreground/40"
|
|
12
|
+
};
|
|
13
|
+
const DEFAULT_LABELS = {
|
|
14
|
+
overflowSuffix: "more",
|
|
15
|
+
region: "Live presence"
|
|
16
|
+
};
|
|
17
|
+
const Avatar = (props) => {
|
|
18
|
+
const { user } = props;
|
|
19
|
+
const status = user.status ?? "active";
|
|
20
|
+
return /* @__PURE__ */ jsxs(
|
|
21
|
+
"span",
|
|
22
|
+
{
|
|
23
|
+
className: "relative -ml-2 inline-flex h-7 w-7 items-center justify-center rounded-full border-2 border-background text-[11px] font-semibold text-white shadow-sm first:ml-0",
|
|
24
|
+
"data-presence-stack-status": status,
|
|
25
|
+
"data-presence-stack-user": user.id,
|
|
26
|
+
style: { backgroundColor: user.color ?? "var(--foreground)" },
|
|
27
|
+
title: user.name,
|
|
28
|
+
children: [
|
|
29
|
+
user.initial,
|
|
30
|
+
/* @__PURE__ */ jsx(
|
|
31
|
+
"span",
|
|
32
|
+
{
|
|
33
|
+
"aria-hidden": "true",
|
|
34
|
+
className: cn(
|
|
35
|
+
"absolute -bottom-0.5 -right-0.5 h-2 w-2 rounded-full border border-background",
|
|
36
|
+
STATUS_DOT[status]
|
|
37
|
+
),
|
|
38
|
+
"data-presence-stack-dot": true
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
const PresenceStack = forwardRef(
|
|
46
|
+
(props, ref) => {
|
|
47
|
+
const {
|
|
48
|
+
className,
|
|
49
|
+
labels,
|
|
50
|
+
max = 5,
|
|
51
|
+
onOverflowActivate,
|
|
52
|
+
users,
|
|
53
|
+
...rest
|
|
54
|
+
} = props;
|
|
55
|
+
const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
|
|
56
|
+
const visible = max >= users.length ? users : users.slice(0, max);
|
|
57
|
+
const hidden = users.length - visible.length;
|
|
58
|
+
const handleOverflow = () => {
|
|
59
|
+
onOverflowActivate?.();
|
|
60
|
+
};
|
|
61
|
+
return /* @__PURE__ */ jsxs(
|
|
62
|
+
"div",
|
|
63
|
+
{
|
|
64
|
+
"aria-label": resolvedLabels.region,
|
|
65
|
+
className: cn("inline-flex items-center pl-2", className),
|
|
66
|
+
"data-presence-stack": true,
|
|
67
|
+
ref,
|
|
68
|
+
role: "group",
|
|
69
|
+
...rest,
|
|
70
|
+
children: [
|
|
71
|
+
visible.map((user) => /* @__PURE__ */ jsx(Avatar, { user }, user.id)),
|
|
72
|
+
hidden > 0 ? renderOverflow({
|
|
73
|
+
count: hidden,
|
|
74
|
+
handleClick: handleOverflow,
|
|
75
|
+
handlerProvided: Boolean(onOverflowActivate),
|
|
76
|
+
labels: resolvedLabels
|
|
77
|
+
}) : null
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
PresenceStack.displayName = "PresenceStack";
|
|
84
|
+
const renderOverflow = (input) => {
|
|
85
|
+
const text = `+${input.count}`;
|
|
86
|
+
const aria = `${input.count} ${input.labels.overflowSuffix}`;
|
|
87
|
+
const className = "relative -ml-2 inline-flex h-7 min-w-7 items-center justify-center rounded-full border-2 border-background bg-muted px-1.5 text-[10px] font-semibold text-muted-foreground shadow-sm";
|
|
88
|
+
if (input.handlerProvided) {
|
|
89
|
+
return /* @__PURE__ */ jsx(
|
|
90
|
+
"button",
|
|
91
|
+
{
|
|
92
|
+
"aria-label": aria,
|
|
93
|
+
className: cn(
|
|
94
|
+
className,
|
|
95
|
+
"transition-colors hover:bg-muted/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
96
|
+
),
|
|
97
|
+
"data-presence-stack-overflow": true,
|
|
98
|
+
onClick: input.handleClick,
|
|
99
|
+
type: "button",
|
|
100
|
+
children: text
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return /* @__PURE__ */ jsx("span", { "aria-label": aria, className, "data-presence-stack-overflow": true, children: text });
|
|
105
|
+
};
|
|
106
|
+
export {
|
|
107
|
+
PresenceStack
|
|
108
|
+
};
|