@agent-native/toolkit 0.19.4 → 0.19.5
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/agent-native.eject.json +3 -0
- package/dist/collab-ui/PresenceBar.d.ts +6 -2
- package/dist/collab-ui/PresenceBar.d.ts.map +1 -1
- package/dist/collab-ui/PresenceBar.js +44 -15
- package/dist/collab-ui/PresenceBar.js.map +1 -1
- package/dist/collab-ui/PresenceBar.spec.js +57 -0
- package/dist/collab-ui/PresenceBar.spec.js.map +1 -1
- package/package.json +1 -1
- package/src/collab-ui/PresenceBar.spec.tsx +108 -0
- package/src/collab-ui/PresenceBar.tsx +116 -25
package/agent-native.eject.json
CHANGED
|
@@ -109,7 +109,9 @@
|
|
|
109
109
|
"sourceEntries": ["src/collab-ui"],
|
|
110
110
|
"targetRoot": "app/components/agent-native/toolkit",
|
|
111
111
|
"dependencies": [
|
|
112
|
+
"@radix-ui/react-dropdown-menu",
|
|
112
113
|
"@radix-ui/react-tooltip",
|
|
114
|
+
"@tabler/icons-react",
|
|
113
115
|
"clsx",
|
|
114
116
|
"react",
|
|
115
117
|
"tailwind-merge",
|
|
@@ -383,6 +385,7 @@
|
|
|
383
385
|
}
|
|
384
386
|
],
|
|
385
387
|
"dependencies": [
|
|
388
|
+
"@radix-ui/react-dropdown-menu",
|
|
386
389
|
"@tabler/icons-react",
|
|
387
390
|
"@radix-ui/react-popover",
|
|
388
391
|
"@radix-ui/react-tooltip",
|
|
@@ -8,8 +8,10 @@ export interface PresenceBarProps {
|
|
|
8
8
|
agentActive?: boolean;
|
|
9
9
|
/** @deprecated Agent editing is represented by the AI presence circle and tooltip. */
|
|
10
10
|
showAgentEditingDot?: boolean;
|
|
11
|
-
/** Current user's email
|
|
11
|
+
/** Current user's email, excluded unless showCurrentUser is true. */
|
|
12
12
|
currentUserEmail?: string;
|
|
13
|
+
/** Include the current user in the roster as a non-followable avatar. */
|
|
14
|
+
showCurrentUser?: boolean;
|
|
13
15
|
/** Max visible avatars before "+N" overflow. Default: 5 */
|
|
14
16
|
maxVisible?: number;
|
|
15
17
|
/** Additional CSS classes. */
|
|
@@ -19,11 +21,13 @@ export interface PresenceBarProps {
|
|
|
19
21
|
* (or null for the agent avatar). Use this to start/stop follow mode.
|
|
20
22
|
*/
|
|
21
23
|
onAvatarClick?: (user: CollabUser | null) => void;
|
|
24
|
+
/** Keep the AI avatar display-only when no agent viewport can be followed. */
|
|
25
|
+
disableAgentClick?: boolean;
|
|
22
26
|
/**
|
|
23
27
|
* The email of the user currently being followed. Highlighted with a
|
|
24
28
|
* blue ring to indicate active follow mode.
|
|
25
29
|
*/
|
|
26
30
|
followingEmail?: string | null;
|
|
27
31
|
}
|
|
28
|
-
export declare function PresenceBar({ activeUsers, agentPresent, agentActive, currentUserEmail, maxVisible, className, onAvatarClick, followingEmail, }: PresenceBarProps): import("react").JSX.Element | null;
|
|
32
|
+
export declare function PresenceBar({ activeUsers, agentPresent, agentActive, currentUserEmail, showCurrentUser, maxVisible, className, onAvatarClick, disableAgentClick, followingEmail, }: PresenceBarProps): import("react").JSX.Element | null;
|
|
29
33
|
//# sourceMappingURL=PresenceBar.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PresenceBar.d.ts","sourceRoot":"","sources":["../../src/collab-ui/PresenceBar.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PresenceBar.d.ts","sourceRoot":"","sources":["../../src/collab-ui/PresenceBar.tsx"],"names":[],"mappings":"AAeA,OAAO,EACL,KAAK,UAAU,EAIhB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,gBAAgB;IAC/B,6CAA6C;IAC7C,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,sDAAsD;IACtD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,sFAAsF;IACtF,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,yEAAyE;IACzE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8BAA8B;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,KAAK,IAAI,CAAC;IAClD,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAsQD,wBAAgB,WAAW,CAAC,EAC1B,WAAW,EACX,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,UAAc,EACd,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,cAAc,GACf,EAAE,gBAAgB,sCAsFlB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo } from "react";
|
|
3
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, } from "../ui/dropdown-menu.js";
|
|
3
4
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "../ui/tooltip.js";
|
|
4
5
|
import { dedupeCollabUsersByEmail, emailToColor, emailToName, } from "./types.js";
|
|
5
6
|
const AVATAR_SIZE = 28;
|
|
@@ -106,40 +107,68 @@ function AgentAvatar({ active, onClick, isFollowing, }) {
|
|
|
106
107
|
whiteSpace: "nowrap",
|
|
107
108
|
}, children: "Following AI" }))] }));
|
|
108
109
|
}
|
|
109
|
-
function
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
110
|
+
function OverflowMenu({ users, followingEmail, currentUserEmail, onSelect, }) {
|
|
111
|
+
const followingLower = followingEmail?.trim().toLowerCase() ?? null;
|
|
112
|
+
const currentLower = currentUserEmail?.trim().toLowerCase() ?? null;
|
|
113
|
+
return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs("button", { type: "button", style: {
|
|
114
|
+
...baseAvatarStyle,
|
|
115
|
+
backgroundColor: "hsl(var(--muted))",
|
|
116
|
+
color: "hsl(var(--muted-foreground))",
|
|
117
|
+
marginLeft: OVERLAP,
|
|
118
|
+
fontSize: 10,
|
|
119
|
+
cursor: "pointer",
|
|
120
|
+
}, "aria-label": `${users.length} more collaborator${users.length === 1 ? "" : "s"}`, children: ["+", users.length] }) }), _jsxs(DropdownMenuContent, { align: "start", className: "w-64", children: [_jsx(DropdownMenuLabel, { children: "More collaborators" }), users.map((user) => {
|
|
121
|
+
const name = user.name || emailToName(user.email);
|
|
122
|
+
const isFollowing = followingLower != null &&
|
|
123
|
+
user.email.trim().toLowerCase() === followingLower;
|
|
124
|
+
const isCurrent = currentLower != null &&
|
|
125
|
+
user.email.trim().toLowerCase() === currentLower;
|
|
126
|
+
return (_jsxs(DropdownMenuItem, { onSelect: () => onSelect?.(user), disabled: isCurrent, children: [_jsx("span", { style: {
|
|
127
|
+
...baseAvatarStyle,
|
|
128
|
+
width: 24,
|
|
129
|
+
height: 24,
|
|
130
|
+
backgroundColor: user.color || emailToColor(user.email),
|
|
131
|
+
fontSize: 10,
|
|
132
|
+
}, children: name.charAt(0).toUpperCase() }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate text-sm font-medium", children: name }), _jsx("span", { className: "block truncate text-xs text-muted-foreground", children: user.email })] }), isFollowing ? (_jsx("span", { className: "text-xs text-muted-foreground", children: "Following" })) : null] }, user.email));
|
|
133
|
+
})] })] }));
|
|
117
134
|
}
|
|
118
|
-
export function PresenceBar({ activeUsers, agentPresent, agentActive, currentUserEmail, maxVisible = 5, className, onAvatarClick, followingEmail, }) {
|
|
135
|
+
export function PresenceBar({ activeUsers, agentPresent, agentActive, currentUserEmail, showCurrentUser, maxVisible = 5, className, onAvatarClick, disableAgentClick, followingEmail, }) {
|
|
119
136
|
const { humanUsers, showAgent } = useMemo(() => {
|
|
120
137
|
const currentEmail = currentUserEmail?.trim().toLowerCase();
|
|
121
138
|
const uniqueUsers = dedupeCollabUsersByEmail(activeUsers);
|
|
122
139
|
const humans = uniqueUsers.filter((u) => {
|
|
123
140
|
const email = u.email.trim().toLowerCase();
|
|
124
|
-
return email !==
|
|
141
|
+
return (email !== "agent@system" &&
|
|
142
|
+
(showCurrentUser === true || email !== currentEmail));
|
|
125
143
|
});
|
|
126
144
|
const hasAgentUser = uniqueUsers.some((u) => u.email.trim().toLowerCase() === "agent@system");
|
|
127
145
|
return {
|
|
128
146
|
humanUsers: humans,
|
|
129
147
|
showAgent: agentPresent || agentActive || hasAgentUser,
|
|
130
148
|
};
|
|
131
|
-
}, [
|
|
149
|
+
}, [
|
|
150
|
+
activeUsers,
|
|
151
|
+
currentUserEmail,
|
|
152
|
+
showCurrentUser,
|
|
153
|
+
agentPresent,
|
|
154
|
+
agentActive,
|
|
155
|
+
]);
|
|
132
156
|
const visibleUsers = humanUsers.slice(0, maxVisible);
|
|
133
|
-
const
|
|
157
|
+
const overflowUsers = humanUsers.slice(maxVisible);
|
|
158
|
+
const currentLower = currentUserEmail?.trim().toLowerCase() ?? null;
|
|
134
159
|
if (!showAgent && humanUsers.length === 0)
|
|
135
160
|
return null;
|
|
136
161
|
const followingLower = followingEmail?.trim().toLowerCase() ?? null;
|
|
137
162
|
const isFollowingAgent = followingLower === "agent@system";
|
|
138
|
-
return (_jsx(TooltipProvider, { delayDuration: 150, children: _jsxs("div", { style: containerStyle, className: className, children: [showAgent && (_jsx(AgentAvatar, { active: !!agentActive, onClick:
|
|
163
|
+
return (_jsx(TooltipProvider, { delayDuration: 150, children: _jsxs("div", { style: containerStyle, className: className, children: [showAgent && (_jsx(AgentAvatar, { active: !!agentActive, onClick: !disableAgentClick && onAvatarClick
|
|
164
|
+
? () => onAvatarClick(null)
|
|
165
|
+
: undefined, isFollowing: isFollowingAgent })), visibleUsers.length > 0 && (_jsxs("div", { style: {
|
|
139
166
|
display: "flex",
|
|
140
167
|
alignItems: "center",
|
|
141
168
|
marginLeft: showAgent ? 6 : 0,
|
|
142
|
-
}, children: [visibleUsers.map((u, i) => (_jsx(UserAvatar, { user: u, isFirst: i === 0, onClick: onAvatarClick
|
|
143
|
-
|
|
169
|
+
}, children: [visibleUsers.map((u, i) => (_jsx(UserAvatar, { user: u, isFirst: i === 0, onClick: onAvatarClick && u.email.trim().toLowerCase() !== currentLower
|
|
170
|
+
? () => onAvatarClick(u)
|
|
171
|
+
: undefined, isFollowing: followingLower != null &&
|
|
172
|
+
u.email.trim().toLowerCase() === followingLower }, u.email))), overflowUsers.length > 0 && (_jsx(OverflowMenu, { users: overflowUsers, followingEmail: followingEmail, currentUserEmail: currentUserEmail, onSelect: onAvatarClick ?? undefined }))] }))] }) }));
|
|
144
173
|
}
|
|
145
174
|
//# sourceMappingURL=PresenceBar.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PresenceBar.js","sourceRoot":"","sources":["../../src/collab-ui/PresenceBar.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAsB,MAAM,OAAO,CAAC;AAEpD,OAAO,EACL,OAAO,EACP,cAAc,EACd,eAAe,EACf,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAEL,wBAAwB,EACxB,YAAY,EACZ,WAAW,GACZ,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"PresenceBar.js","sourceRoot":"","sources":["../../src/collab-ui/PresenceBar.tsx"],"names":[],"mappings":"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"}
|
|
@@ -40,5 +40,62 @@ describe("PresenceBar", () => {
|
|
|
40
40
|
});
|
|
41
41
|
expect(container.querySelector('[aria-label="AI agent"]')).not.toBeNull();
|
|
42
42
|
});
|
|
43
|
+
it("keeps the AI avatar display-only when agent follow is disabled", () => {
|
|
44
|
+
const onAvatarClick = vi.fn();
|
|
45
|
+
act(() => {
|
|
46
|
+
root.render(_jsx(PresenceBar, { activeUsers: [], agentPresent: true, onAvatarClick: onAvatarClick, disableAgentClick: true }));
|
|
47
|
+
});
|
|
48
|
+
const avatar = container.querySelector('[aria-label="AI agent"]');
|
|
49
|
+
expect(avatar?.getAttribute("role")).toBeNull();
|
|
50
|
+
expect(avatar?.style.cursor).toBe("default");
|
|
51
|
+
act(() => avatar?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
52
|
+
expect(onAvatarClick).not.toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
it("can show the current user without allowing self-follow", () => {
|
|
55
|
+
const onAvatarClick = vi.fn();
|
|
56
|
+
const currentUser = {
|
|
57
|
+
email: "steve@example.com",
|
|
58
|
+
name: "Steve",
|
|
59
|
+
color: "#123456",
|
|
60
|
+
};
|
|
61
|
+
const otherUser = {
|
|
62
|
+
email: "other@example.com",
|
|
63
|
+
name: "Other",
|
|
64
|
+
color: "#654321",
|
|
65
|
+
};
|
|
66
|
+
act(() => {
|
|
67
|
+
root.render(_jsx(PresenceBar, { activeUsers: [currentUser, otherUser], currentUserEmail: currentUser.email, showCurrentUser: true, onAvatarClick: onAvatarClick }));
|
|
68
|
+
});
|
|
69
|
+
const currentAvatar = container.querySelector('[aria-label="Steve (steve@example.com)"]');
|
|
70
|
+
expect(currentAvatar?.getAttribute("role")).toBeNull();
|
|
71
|
+
act(() => currentAvatar?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
72
|
+
expect(onAvatarClick).not.toHaveBeenCalled();
|
|
73
|
+
const otherAvatar = container.querySelector('[aria-label="Other (other@example.com)"]');
|
|
74
|
+
act(() => otherAvatar?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
75
|
+
expect(onAvatarClick).toHaveBeenCalledWith(otherUser);
|
|
76
|
+
});
|
|
77
|
+
it("keeps overflow collaborators available for follow mode", () => {
|
|
78
|
+
const onAvatarClick = vi.fn();
|
|
79
|
+
const activeUsers = Array.from({ length: 6 }, (_, index) => ({
|
|
80
|
+
email: `user-${index}@example.com`,
|
|
81
|
+
name: `User ${index}`,
|
|
82
|
+
color: "#123456",
|
|
83
|
+
}));
|
|
84
|
+
act(() => {
|
|
85
|
+
root.render(_jsx(PresenceBar, { activeUsers: activeUsers, onAvatarClick: onAvatarClick }));
|
|
86
|
+
});
|
|
87
|
+
const overflow = container.querySelector('[aria-label="1 more collaborator"]');
|
|
88
|
+
expect(overflow).not.toBeNull();
|
|
89
|
+
act(() => {
|
|
90
|
+
overflow?.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, button: 0 }));
|
|
91
|
+
});
|
|
92
|
+
const hiddenUser = Array.from(document.querySelectorAll('[role="menuitem"]')).find((item) => item.textContent?.includes("User 5"));
|
|
93
|
+
expect(hiddenUser).not.toBeUndefined();
|
|
94
|
+
act(() => {
|
|
95
|
+
hiddenUser?.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true }));
|
|
96
|
+
hiddenUser?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
97
|
+
});
|
|
98
|
+
expect(onAvatarClick).toHaveBeenCalledWith(activeUsers[5]);
|
|
99
|
+
});
|
|
43
100
|
});
|
|
44
101
|
//# sourceMappingURL=PresenceBar.spec.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PresenceBar.spec.js","sourceRoot":"","sources":["../../src/collab-ui/PresenceBar.spec.tsx"],"names":[],"mappings":";AAAA,gCAAgC;AAEhC,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAa,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAEzE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,IAAI,SAAyB,CAAC;IAC9B,IAAI,IAAU,CAAC;IAEf,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,UAAU,CAAC,0BAA0B,EAAE,IAAI,CAAC,CAAC;QAChD,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC1C,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;QACrC,IAAI,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1B,SAAS,CAAC,MAAM,EAAE,CAAC;QACnB,EAAE,CAAC,eAAe,EAAE,CAAC;QACrB,EAAE,CAAC,aAAa,EAAE,CAAC;QACnB,EAAE,CAAC,gBAAgB,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,EAAE,CAAC,aAAa,EAAE,CAAC;QACnB,GAAG,CAAC,GAAG,EAAE;YACP,IAAI,CAAC,MAAM,CAAC,KAAC,WAAW,IAAC,WAAW,EAAE,EAAE,EAAE,WAAW,QAAC,YAAY,SAAG,CAAC,CAAC;QACzE,CAAC,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,SAAS,CAAC,aAAa,CACpC,8BAA8B,CAC/B,CAAC;QACF,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEzC,GAAG,CAAC,GAAG,EAAE;YACP,MAAM,EAAE,aAAa,CAAC,IAAI,KAAK,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YACnE,EAAE,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;QAEH,MAAM,CACJ,QAAQ;aACL,aAAa,CAAC,oCAAoC,CAAC;YACpD,EAAE,WAAW,EAAE,QAAQ,CAAC,eAAe,CAAC,CAC3C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEb,GAAG,CAAC,GAAG,EAAE;YACP,IAAI,CAAC,MAAM,CAAC,KAAC,WAAW,IAAC,WAAW,EAAE,EAAE,EAAE,YAAY,SAAG,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,yBAAyB,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;IAC5E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"PresenceBar.spec.js","sourceRoot":"","sources":["../../src/collab-ui/PresenceBar.spec.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -53,4 +53,112 @@ describe("PresenceBar", () => {
|
|
|
53
53
|
});
|
|
54
54
|
expect(container.querySelector('[aria-label="AI agent"]')).not.toBeNull();
|
|
55
55
|
});
|
|
56
|
+
|
|
57
|
+
it("keeps the AI avatar display-only when agent follow is disabled", () => {
|
|
58
|
+
const onAvatarClick = vi.fn();
|
|
59
|
+
act(() => {
|
|
60
|
+
root.render(
|
|
61
|
+
<PresenceBar
|
|
62
|
+
activeUsers={[]}
|
|
63
|
+
agentPresent
|
|
64
|
+
onAvatarClick={onAvatarClick}
|
|
65
|
+
disableAgentClick
|
|
66
|
+
/>,
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const avatar = container.querySelector<HTMLElement>(
|
|
71
|
+
'[aria-label="AI agent"]',
|
|
72
|
+
);
|
|
73
|
+
expect(avatar?.getAttribute("role")).toBeNull();
|
|
74
|
+
expect(avatar?.style.cursor).toBe("default");
|
|
75
|
+
|
|
76
|
+
act(() =>
|
|
77
|
+
avatar?.dispatchEvent(new MouseEvent("click", { bubbles: true })),
|
|
78
|
+
);
|
|
79
|
+
expect(onAvatarClick).not.toHaveBeenCalled();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("can show the current user without allowing self-follow", () => {
|
|
83
|
+
const onAvatarClick = vi.fn();
|
|
84
|
+
const currentUser = {
|
|
85
|
+
email: "steve@example.com",
|
|
86
|
+
name: "Steve",
|
|
87
|
+
color: "#123456",
|
|
88
|
+
};
|
|
89
|
+
const otherUser = {
|
|
90
|
+
email: "other@example.com",
|
|
91
|
+
name: "Other",
|
|
92
|
+
color: "#654321",
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
act(() => {
|
|
96
|
+
root.render(
|
|
97
|
+
<PresenceBar
|
|
98
|
+
activeUsers={[currentUser, otherUser]}
|
|
99
|
+
currentUserEmail={currentUser.email}
|
|
100
|
+
showCurrentUser
|
|
101
|
+
onAvatarClick={onAvatarClick}
|
|
102
|
+
/>,
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const currentAvatar = container.querySelector<HTMLElement>(
|
|
107
|
+
'[aria-label="Steve (steve@example.com)"]',
|
|
108
|
+
);
|
|
109
|
+
expect(currentAvatar?.getAttribute("role")).toBeNull();
|
|
110
|
+
|
|
111
|
+
act(() =>
|
|
112
|
+
currentAvatar?.dispatchEvent(new MouseEvent("click", { bubbles: true })),
|
|
113
|
+
);
|
|
114
|
+
expect(onAvatarClick).not.toHaveBeenCalled();
|
|
115
|
+
|
|
116
|
+
const otherAvatar = container.querySelector<HTMLElement>(
|
|
117
|
+
'[aria-label="Other (other@example.com)"]',
|
|
118
|
+
);
|
|
119
|
+
act(() =>
|
|
120
|
+
otherAvatar?.dispatchEvent(new MouseEvent("click", { bubbles: true })),
|
|
121
|
+
);
|
|
122
|
+
expect(onAvatarClick).toHaveBeenCalledWith(otherUser);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("keeps overflow collaborators available for follow mode", () => {
|
|
126
|
+
const onAvatarClick = vi.fn();
|
|
127
|
+
const activeUsers = Array.from({ length: 6 }, (_, index) => ({
|
|
128
|
+
email: `user-${index}@example.com`,
|
|
129
|
+
name: `User ${index}`,
|
|
130
|
+
color: "#123456",
|
|
131
|
+
}));
|
|
132
|
+
|
|
133
|
+
act(() => {
|
|
134
|
+
root.render(
|
|
135
|
+
<PresenceBar activeUsers={activeUsers} onAvatarClick={onAvatarClick} />,
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const overflow = container.querySelector<HTMLButtonElement>(
|
|
140
|
+
'[aria-label="1 more collaborator"]',
|
|
141
|
+
);
|
|
142
|
+
expect(overflow).not.toBeNull();
|
|
143
|
+
|
|
144
|
+
act(() => {
|
|
145
|
+
overflow?.dispatchEvent(
|
|
146
|
+
new PointerEvent("pointerdown", { bubbles: true, button: 0 }),
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const hiddenUser = Array.from(
|
|
151
|
+
document.querySelectorAll('[role="menuitem"]'),
|
|
152
|
+
).find((item) => item.textContent?.includes("User 5"));
|
|
153
|
+
expect(hiddenUser).not.toBeUndefined();
|
|
154
|
+
|
|
155
|
+
act(() => {
|
|
156
|
+
hiddenUser?.dispatchEvent(
|
|
157
|
+
new PointerEvent("pointerdown", { bubbles: true }),
|
|
158
|
+
);
|
|
159
|
+
hiddenUser?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(onAvatarClick).toHaveBeenCalledWith(activeUsers[5]);
|
|
163
|
+
});
|
|
56
164
|
});
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { useMemo, type CSSProperties } from "react";
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
DropdownMenu,
|
|
5
|
+
DropdownMenuContent,
|
|
6
|
+
DropdownMenuItem,
|
|
7
|
+
DropdownMenuLabel,
|
|
8
|
+
DropdownMenuTrigger,
|
|
9
|
+
} from "../ui/dropdown-menu.js";
|
|
3
10
|
import {
|
|
4
11
|
Tooltip,
|
|
5
12
|
TooltipContent,
|
|
@@ -22,8 +29,10 @@ export interface PresenceBarProps {
|
|
|
22
29
|
agentActive?: boolean;
|
|
23
30
|
/** @deprecated Agent editing is represented by the AI presence circle and tooltip. */
|
|
24
31
|
showAgentEditingDot?: boolean;
|
|
25
|
-
/** Current user's email
|
|
32
|
+
/** Current user's email, excluded unless showCurrentUser is true. */
|
|
26
33
|
currentUserEmail?: string;
|
|
34
|
+
/** Include the current user in the roster as a non-followable avatar. */
|
|
35
|
+
showCurrentUser?: boolean;
|
|
27
36
|
/** Max visible avatars before "+N" overflow. Default: 5 */
|
|
28
37
|
maxVisible?: number;
|
|
29
38
|
/** Additional CSS classes. */
|
|
@@ -33,6 +42,8 @@ export interface PresenceBarProps {
|
|
|
33
42
|
* (or null for the agent avatar). Use this to start/stop follow mode.
|
|
34
43
|
*/
|
|
35
44
|
onAvatarClick?: (user: CollabUser | null) => void;
|
|
45
|
+
/** Keep the AI avatar display-only when no agent viewport can be followed. */
|
|
46
|
+
disableAgentClick?: boolean;
|
|
36
47
|
/**
|
|
37
48
|
* The email of the user currently being followed. Highlighted with a
|
|
38
49
|
* blue ring to indicate active follow mode.
|
|
@@ -222,26 +233,81 @@ function AgentAvatar({
|
|
|
222
233
|
);
|
|
223
234
|
}
|
|
224
235
|
|
|
225
|
-
function
|
|
226
|
-
|
|
227
|
-
|
|
236
|
+
function OverflowMenu({
|
|
237
|
+
users,
|
|
238
|
+
followingEmail,
|
|
239
|
+
currentUserEmail,
|
|
240
|
+
onSelect,
|
|
228
241
|
}: {
|
|
229
|
-
|
|
230
|
-
|
|
242
|
+
users: CollabUser[];
|
|
243
|
+
followingEmail?: string | null;
|
|
244
|
+
currentUserEmail?: string;
|
|
245
|
+
onSelect?: (user: CollabUser) => void;
|
|
231
246
|
}) {
|
|
247
|
+
const followingLower = followingEmail?.trim().toLowerCase() ?? null;
|
|
248
|
+
const currentLower = currentUserEmail?.trim().toLowerCase() ?? null;
|
|
249
|
+
|
|
232
250
|
return (
|
|
233
|
-
<
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
251
|
+
<DropdownMenu>
|
|
252
|
+
<DropdownMenuTrigger asChild>
|
|
253
|
+
<button
|
|
254
|
+
type="button"
|
|
255
|
+
style={{
|
|
256
|
+
...baseAvatarStyle,
|
|
257
|
+
backgroundColor: "hsl(var(--muted))",
|
|
258
|
+
color: "hsl(var(--muted-foreground))",
|
|
259
|
+
marginLeft: OVERLAP,
|
|
260
|
+
fontSize: 10,
|
|
261
|
+
cursor: "pointer",
|
|
262
|
+
}}
|
|
263
|
+
aria-label={`${users.length} more collaborator${users.length === 1 ? "" : "s"}`}
|
|
264
|
+
>
|
|
265
|
+
+{users.length}
|
|
266
|
+
</button>
|
|
267
|
+
</DropdownMenuTrigger>
|
|
268
|
+
<DropdownMenuContent align="start" className="w-64">
|
|
269
|
+
<DropdownMenuLabel>More collaborators</DropdownMenuLabel>
|
|
270
|
+
{users.map((user) => {
|
|
271
|
+
const name = user.name || emailToName(user.email);
|
|
272
|
+
const isFollowing =
|
|
273
|
+
followingLower != null &&
|
|
274
|
+
user.email.trim().toLowerCase() === followingLower;
|
|
275
|
+
const isCurrent =
|
|
276
|
+
currentLower != null &&
|
|
277
|
+
user.email.trim().toLowerCase() === currentLower;
|
|
278
|
+
return (
|
|
279
|
+
<DropdownMenuItem
|
|
280
|
+
key={user.email}
|
|
281
|
+
onSelect={() => onSelect?.(user)}
|
|
282
|
+
disabled={isCurrent}
|
|
283
|
+
>
|
|
284
|
+
<span
|
|
285
|
+
style={{
|
|
286
|
+
...baseAvatarStyle,
|
|
287
|
+
width: 24,
|
|
288
|
+
height: 24,
|
|
289
|
+
backgroundColor: user.color || emailToColor(user.email),
|
|
290
|
+
fontSize: 10,
|
|
291
|
+
}}
|
|
292
|
+
>
|
|
293
|
+
{name.charAt(0).toUpperCase()}
|
|
294
|
+
</span>
|
|
295
|
+
<span className="min-w-0 flex-1">
|
|
296
|
+
<span className="block truncate text-sm font-medium">
|
|
297
|
+
{name}
|
|
298
|
+
</span>
|
|
299
|
+
<span className="block truncate text-xs text-muted-foreground">
|
|
300
|
+
{user.email}
|
|
301
|
+
</span>
|
|
302
|
+
</span>
|
|
303
|
+
{isFollowing ? (
|
|
304
|
+
<span className="text-xs text-muted-foreground">Following</span>
|
|
305
|
+
) : null}
|
|
306
|
+
</DropdownMenuItem>
|
|
307
|
+
);
|
|
308
|
+
})}
|
|
309
|
+
</DropdownMenuContent>
|
|
310
|
+
</DropdownMenu>
|
|
245
311
|
);
|
|
246
312
|
}
|
|
247
313
|
|
|
@@ -250,9 +316,11 @@ export function PresenceBar({
|
|
|
250
316
|
agentPresent,
|
|
251
317
|
agentActive,
|
|
252
318
|
currentUserEmail,
|
|
319
|
+
showCurrentUser,
|
|
253
320
|
maxVisible = 5,
|
|
254
321
|
className,
|
|
255
322
|
onAvatarClick,
|
|
323
|
+
disableAgentClick,
|
|
256
324
|
followingEmail,
|
|
257
325
|
}: PresenceBarProps) {
|
|
258
326
|
const { humanUsers, showAgent } = useMemo(() => {
|
|
@@ -260,7 +328,10 @@ export function PresenceBar({
|
|
|
260
328
|
const uniqueUsers = dedupeCollabUsersByEmail(activeUsers);
|
|
261
329
|
const humans = uniqueUsers.filter((u) => {
|
|
262
330
|
const email = u.email.trim().toLowerCase();
|
|
263
|
-
return
|
|
331
|
+
return (
|
|
332
|
+
email !== "agent@system" &&
|
|
333
|
+
(showCurrentUser === true || email !== currentEmail)
|
|
334
|
+
);
|
|
264
335
|
});
|
|
265
336
|
const hasAgentUser = uniqueUsers.some(
|
|
266
337
|
(u) => u.email.trim().toLowerCase() === "agent@system",
|
|
@@ -269,10 +340,17 @@ export function PresenceBar({
|
|
|
269
340
|
humanUsers: humans,
|
|
270
341
|
showAgent: agentPresent || agentActive || hasAgentUser,
|
|
271
342
|
};
|
|
272
|
-
}, [
|
|
343
|
+
}, [
|
|
344
|
+
activeUsers,
|
|
345
|
+
currentUserEmail,
|
|
346
|
+
showCurrentUser,
|
|
347
|
+
agentPresent,
|
|
348
|
+
agentActive,
|
|
349
|
+
]);
|
|
273
350
|
|
|
274
351
|
const visibleUsers = humanUsers.slice(0, maxVisible);
|
|
275
|
-
const
|
|
352
|
+
const overflowUsers = humanUsers.slice(maxVisible);
|
|
353
|
+
const currentLower = currentUserEmail?.trim().toLowerCase() ?? null;
|
|
276
354
|
|
|
277
355
|
if (!showAgent && humanUsers.length === 0) return null;
|
|
278
356
|
|
|
@@ -285,7 +363,11 @@ export function PresenceBar({
|
|
|
285
363
|
{showAgent && (
|
|
286
364
|
<AgentAvatar
|
|
287
365
|
active={!!agentActive}
|
|
288
|
-
onClick={
|
|
366
|
+
onClick={
|
|
367
|
+
!disableAgentClick && onAvatarClick
|
|
368
|
+
? () => onAvatarClick(null)
|
|
369
|
+
: undefined
|
|
370
|
+
}
|
|
289
371
|
isFollowing={isFollowingAgent}
|
|
290
372
|
/>
|
|
291
373
|
)}
|
|
@@ -302,15 +384,24 @@ export function PresenceBar({
|
|
|
302
384
|
key={u.email}
|
|
303
385
|
user={u}
|
|
304
386
|
isFirst={i === 0}
|
|
305
|
-
onClick={
|
|
387
|
+
onClick={
|
|
388
|
+
onAvatarClick && u.email.trim().toLowerCase() !== currentLower
|
|
389
|
+
? () => onAvatarClick(u)
|
|
390
|
+
: undefined
|
|
391
|
+
}
|
|
306
392
|
isFollowing={
|
|
307
393
|
followingLower != null &&
|
|
308
394
|
u.email.trim().toLowerCase() === followingLower
|
|
309
395
|
}
|
|
310
396
|
/>
|
|
311
397
|
))}
|
|
312
|
-
{
|
|
313
|
-
<
|
|
398
|
+
{overflowUsers.length > 0 && (
|
|
399
|
+
<OverflowMenu
|
|
400
|
+
users={overflowUsers}
|
|
401
|
+
followingEmail={followingEmail}
|
|
402
|
+
currentUserEmail={currentUserEmail}
|
|
403
|
+
onSelect={onAvatarClick ?? undefined}
|
|
404
|
+
/>
|
|
314
405
|
)}
|
|
315
406
|
</div>
|
|
316
407
|
)}
|