Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -202,24 +202,6 @@
color: currentColor;
}

.addTooltip {
position: fixed;
z-index: var(--z-tooltip);
max-width: 180px;
padding: 5px 8px;
border: 1px solid var(--gray-200);
border-radius: 6px;
background: var(--white);
color: var(--gray-700);
box-shadow: var(--shadow-sm);
font-family: var(--font-sans);
font-size: 12px;
font-weight: 500;
line-height: 18px;
pointer-events: none;
white-space: nowrap;
}

.groupItems {
padding: 4px 8px 8px;
display: grid;
Expand Down Expand Up @@ -460,12 +442,17 @@

:global(:root[data-theme="dark"]) .sortable .groupHead:hover,
:global(:root[data-theme="dark"]) .dragOver .groupHead,
:global(:root[data-theme="dark"]) .row:hover,
:global(:root[data-theme="dark"]) .active {
background: var(--panel-strong);
:global(:root[data-theme="dark"]) .row:hover {
background: var(--panel-soft);
color: var(--gray-100);
}

:global(:root[data-theme="dark"]) .row.active {
background: var(--gray-800);
color: var(--white);
box-shadow: none;
}

:global(:root[data-theme="dark"]) .groupToggle,
:global(:root[data-theme="dark"]) .groupTitle,
:global(:root[data-theme="dark"]) .groupTitle span {
Expand Down Expand Up @@ -528,12 +515,6 @@
color: var(--gray-25);
}

:global(:root[data-theme="dark"]) .addTooltip {
border-color: color-mix(in srgb, var(--white) 12%, transparent);
background: var(--gray-900);
color: var(--white);
}

@media (max-width: 720px) {
.groupTitle,
.groupTitle span {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ import { AgentAvatarContent } from "@/components/business/AgentAvatar";
import { avatarFallbackText } from "@/shared/avatar";
import { localizeRole } from "@/shared/i18n";
import { RoomAvatar, resolveRoomAvatarMembers } from "@/components/business/RoomAvatar";
import { useEffect, useId, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { classNames } from "@/shared/lib/classNames";
import styles from "./WorkspaceRows.module.css";
import type { DragEvent, ReactNode } from "react";
Expand Down Expand Up @@ -64,86 +62,25 @@ export type WorkspaceGroupProps = {
};

function WorkspaceAddAction({ icon, label, onAdd }: { icon: ReactNode; label: string; onAdd: () => void }) {
const triggerRef = useRef<HTMLButtonElement | null>(null);
const tooltipId = useId();
const [open, setOpen] = useState(false);
const [position, setPosition] = useState({ left: 0, top: 0 });

useEffect(() => {
if (!open) {
return undefined;
}

function updatePosition() {
const trigger = triggerRef.current;
if (!trigger) {
return;
}

const rect = trigger.getBoundingClientRect();
const gap = 10;
const tooltipWidth = 180;
const tooltipHeight = 32;
const margin = 12;
const fitsRight = rect.right + gap + tooltipWidth <= window.innerWidth - margin;
const left = fitsRight ? rect.right + gap : Math.max(margin, rect.left - gap - tooltipWidth);
const top = Math.min(
window.innerHeight - tooltipHeight - margin,
Math.max(margin, rect.top + rect.height / 2 - tooltipHeight / 2),
);

setPosition({ left, top });
}

updatePosition();
window.addEventListener("resize", updatePosition);
window.addEventListener("scroll", updatePosition, true);
return () => {
window.removeEventListener("resize", updatePosition);
window.removeEventListener("scroll", updatePosition, true);
};
}, [open]);

return (
<>
<Button
ref={triggerRef}
variant="ghost"
size="sm"
iconOnly
className={styles.addButton}
draggable={false}
aria-label={label}
aria-describedby={open ? tooltipId : undefined}
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
onFocus={() => setOpen(true)}
onBlur={() => setOpen(false)}
onDragStart={(event) => event.stopPropagation()}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
onAdd();
}}
>
<span className="icon-button-mark" aria-hidden="true">
{icon}
</span>
</Button>
{open
? createPortal(
<div
id={tooltipId}
className={styles.addTooltip}
role="tooltip"
style={{ left: `${position.left}px`, top: `${position.top}px` }}
>
{label}
</div>,
document.body,
)
: null}
</>
<Button
variant="ghost"
size="sm"
iconOnly
className={styles.addButton}
draggable={false}
aria-label={label}
onDragStart={(event) => event.stopPropagation()}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
onAdd();
}}
>
<span className="icon-button-mark" aria-hidden="true">
{icon}
</span>
</Button>
);
}

Expand Down Expand Up @@ -202,7 +139,7 @@ export function WorkspaceGroup({
<small className={styles.countBadge}>{count}</small>
</span>
</button>
<div className={styles.groupActions}>
<div className={styles.groupActions} data-tooltip={onAdd ? addLabel || title : undefined}>
{onAdd ? (
<WorkspaceAddAction icon={addIcon || <RoomPlusIcon />} label={addLabel || title} onAdd={onAdd} />
) : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,6 @@ export function WorkspaceSidebar({
activeContextSectionId,
onCreateAgent,
onCreateNotificationParticipant,
onCreateRoom,
onCreateModelProvider,
onOpenCreateTeam,
onOpenCreateTask,
Expand Down Expand Up @@ -739,7 +738,6 @@ function contextCreateActionForSection({
hub,
onCreateAgent,
onCreateNotificationParticipant,
onCreateRoom,
onCreateModelProvider,
onOpenCreateTeam,
onOpenCreateTask,
Expand All @@ -751,7 +749,6 @@ function contextCreateActionForSection({
| "onCreateAgent"
| "onCreateModelProvider"
| "onCreateNotificationParticipant"
| "onCreateRoom"
| "onOpenCreateScheduledTask"
| "onOpenCreateTask"
| "onOpenCreateTeam"
Expand All @@ -762,12 +759,6 @@ function contextCreateActionForSection({
hub: WorkspaceSidebarProps["hub"];
setSkillUploadOpen: (open: boolean) => void;
}) {
if (activeContextSectionId === WorkspaceContextSectionIds.messages) {
return {
label: t("createRoom"),
onClick: onCreateRoom,
};
}
if (activeContextSectionId === WorkspaceContextSectionIds.agents) {
return {
label: t("createAgent"),
Expand Down
19 changes: 19 additions & 0 deletions web/app/tests/components/WorkspaceSidebar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const labels: Record<string, string> = {
computerOverview: "Computer overview",
computersSection: "Computers",
configSettingsMenu: "Configuration",
createRoom: "Create room",
directMessagesSection: "Direct messages",
expandSidebar: "Expand sidebar",
humanSection: "Human",
Expand Down Expand Up @@ -169,6 +170,24 @@ function renderSidebar(overrides: Partial<WorkspaceSidebarProps> = {}) {
}

describe("WorkspaceSidebar", () => {
it("keeps room creation only in the Rooms section", () => {
const onCreateRoom = vi.fn();

renderSidebar({
activePane: { type: WorkspacePaneTypes.conversation, id: "" },
currentWorkspaceLabel: "Messages",
onCreateRoom,
workspaceTab: WorkspaceTabs.messages,
});

const createRoomButtons = screen.getAllByRole("button", { name: "Create room" });
expect(createRoomButtons).toHaveLength(1);
expect(createRoomButtons[0].parentElement).toHaveAttribute("data-tooltip", "Create room");

fireEvent.click(createRoomButtons[0]);
expect(onCreateRoom).toHaveBeenCalledTimes(1);
});

it("selects the MCP resource type when the MCP list is empty", () => {
const onSelectMCPServer = vi.fn();

Expand Down
Loading