diff --git a/packages/ui/src/features/canvas/components/ActivityTimeline.tsx b/packages/ui/src/features/canvas/components/ActivityTimeline.tsx index 482aa35c50..c8846bd6ed 100644 --- a/packages/ui/src/features/canvas/components/ActivityTimeline.tsx +++ b/packages/ui/src/features/canvas/components/ActivityTimeline.tsx @@ -72,7 +72,7 @@ function UserMessageRow({ timestamp: string; }) { return ( - + diff --git a/packages/ui/src/features/canvas/components/ChannelBackRow.test.tsx b/packages/ui/src/features/canvas/components/ChannelBackRow.test.tsx index ad1298628d..705d724870 100644 --- a/packages/ui/src/features/canvas/components/ChannelBackRow.test.tsx +++ b/packages/ui/src/features/canvas/components/ChannelBackRow.test.tsx @@ -59,8 +59,9 @@ describe("ChannelBackRow", () => { expect(useChannelPaneStore.getState().pane).toBe("list"); }); - // #me can't be unstarred, but the well stays filled so the row doesn't - // change height (and everything below it shift) when you switch channels. + // #me can't be starred, so its well is empty — but the well is still there, + // so the row doesn't change height (and everything below it shift) when you + // switch spaces. it("offers a star on shared channels only", () => { renderRow(ENG.id); expect(screen.getByRole("button", { name: "Star space" })).toBeTruthy(); diff --git a/packages/ui/src/features/canvas/components/ChannelBackRow.tsx b/packages/ui/src/features/canvas/components/ChannelBackRow.tsx index e9a0712b07..575441d56b 100644 --- a/packages/ui/src/features/canvas/components/ChannelBackRow.tsx +++ b/packages/ui/src/features/canvas/components/ChannelBackRow.tsx @@ -1,5 +1,10 @@ import { CaretLeftIcon, StarIcon } from "@phosphor-icons/react"; -import { Skeleton } from "@posthog/quill"; +import { + Skeleton, + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@posthog/quill"; import { ANALYTICS_EVENTS } from "@posthog/shared/analytics-events"; import { channelGlyph } from "@posthog/ui/features/canvas/components/channelGlyph"; import { useChannelStarToggle } from "@posthog/ui/features/canvas/hooks/useChannelStars"; @@ -10,7 +15,6 @@ import { import { useChannelsLayout } from "@posthog/ui/features/canvas/hooks/useChannelsLayout"; import { PERSONAL_CHANNEL_NAME } from "@posthog/ui/features/canvas/hooks/useTaskChannels"; import { showChannelList } from "@posthog/ui/features/canvas/stores/channelPaneStore"; -import { Tooltip } from "@posthog/ui/primitives/Tooltip"; import { track } from "@posthog/ui/shell/analytics"; // An overlay rather than a sibling: the back button fills the row, and nesting @@ -53,60 +57,61 @@ export function ChannelBackRow({ channelId }: { channelId: string }) { return (
- - + + { + track(ANALYTICS_EVENTS.CHANNEL_ACTION, { + action_type: "browse_channels", + surface: "sidebar", + channel_id: channelId, + }); + showChannelList(); + }} + // Fixed height with an unconditional star well: sized off its + // contents, a starrable channel ran 4px taller than #me and + // everything below shifted on switch. No border — it's a row in + // the sidebar like the ones under it, not a control sitting on + // top. + className="flex h-8 w-full items-center gap-1.5 rounded-md px-2 text-left transition-colors hover:bg-fill-hover" + > + + + {channelGlyph(current?.name, { + size: 14, + space: spacesLayout, + className: "text-muted-foreground", + })} + + + {current ? ( + current.name + ) : isLoading ? ( + // A placeholder word here would read as a real channel named + // "channel"; a skeleton says "still loading" honestly. + + ) : ( + "Unavailable" + )} + + + + } + /> + Back to spaces + {/* #me can't be starred, so its well stays empty — a greyed-out star read + as a control you were being denied. The well itself is unconditional + (see the button's reserved span), which is what keeps the row the same + height on every space. */} {showStar && current && } - {/* Inert star for #me, so the row reads the same on every channel. */} - {current && !showStar && ( - - - - )}
); } diff --git a/packages/ui/src/features/canvas/components/ChannelNav.tsx b/packages/ui/src/features/canvas/components/ChannelNav.tsx index 5089bd5fbd..019acc7b7c 100644 --- a/packages/ui/src/features/canvas/components/ChannelNav.tsx +++ b/packages/ui/src/features/canvas/components/ChannelNav.tsx @@ -5,7 +5,14 @@ import { RepeatIcon, SlidersHorizontal, } from "@phosphor-icons/react"; -import { cn } from "@posthog/quill"; +import { + Button, + Kbd, + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@posthog/quill"; import { LOOPS_FLAG } from "@posthog/shared"; import { ANALYTICS_EVENTS, @@ -21,7 +28,6 @@ import { useFeatureFlag } from "@posthog/ui/features/feature-flags/useFeatureFla import { useInboxAllReports } from "@posthog/ui/features/inbox/hooks/useInboxAllReports"; import { openSettings } from "@posthog/ui/features/settings/hooks/useOpenSettings"; import { CountBadge } from "@posthog/ui/primitives/CountBadge"; -import { Tooltip } from "@posthog/ui/primitives/Tooltip"; import { navigateToActivity, navigateToInbox, @@ -53,21 +59,30 @@ function NavIcon({ badge?: ReactNode; }) { return ( - - + + {/* quill's Button, with the sidebar's selected-row treatment — the same + `data-selected` pairing the channel rows use, so the nav and the list + below it read as one control set in either theme. `relative` is for + the count badge, which pins to the button's corner. */} + + {icon} + {badge} + + } + /> + + {label} + {shortcut && {shortcut}} + ); } @@ -92,62 +107,77 @@ export function ChannelNav() { action(); }; + const isInbox = view.type === "inbox"; const isActivity = view.type === "activity"; const isCommandCenter = view.type === "command-center"; return ( -
- } - label="Inbox" - shortcut={formatHotkey(SHORTCUTS.INBOX)} - isActive={view.type === "inbox"} - onClick={withTrack("inbox", navigateToInbox)} - badge={} - /> - } - label="Activity" - isActive={isActivity} - onClick={withTrack("activity", navigateToActivity)} - badge={ - - } - /> - - } - label="Command Center" - isActive={isCommandCenter} - onClick={withTrack("command_center", navigateToWebsiteCommandCenter)} - badge={ - - } - /> - {loopsEnabled ? ( + // One provider for the row: once any tooltip is up, moving to its + // neighbour reveals that one immediately instead of serving the warm-up + // delay again. Per-tooltip providers (the old primitive mounted its own) + // cannot do that — the skip window is provider state, and isolated + // providers never share it. + +
+ + } + label="Inbox" + shortcut={formatHotkey(SHORTCUTS.INBOX)} + isActive={isInbox} + onClick={withTrack("inbox", navigateToInbox)} + badge={ + + } + /> + } + label="Activity" + isActive={isActivity} + onClick={withTrack("activity", navigateToActivity)} + badge={ + + } + /> + } + label="Command Center" + isActive={isCommandCenter} + onClick={withTrack("command_center", navigateToWebsiteCommandCenter)} + badge={ + } - label="Loops" - isActive={view.type === "loops"} - onClick={withTrack("loops", navigateToLoops)} /> - ) : null} - } - label="Configure" - isActive={false} - onClick={withTrack("configure", () => openSettings("agents"))} - /> -
+ {loopsEnabled ? ( + + } + label="Loops" + isActive={view.type === "loops"} + onClick={withTrack("loops", navigateToLoops)} + /> + ) : null} + } + label="Configure" + isActive={false} + onClick={withTrack("configure", () => openSettings("agents"))} + /> +
+ ); } diff --git a/packages/ui/src/features/canvas/components/ChannelsFab.tsx b/packages/ui/src/features/canvas/components/ChannelsFab.tsx index 0596061a2d..cb654896f4 100644 --- a/packages/ui/src/features/canvas/components/ChannelsFab.tsx +++ b/packages/ui/src/features/canvas/components/ChannelsFab.tsx @@ -12,6 +12,7 @@ import { DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, + Kbd, Tooltip, TooltipContent, TooltipTrigger, @@ -27,7 +28,6 @@ import { } from "@posthog/ui/features/command/keyboard-shortcuts"; import { isContentEmpty } from "@posthog/ui/features/message-editor/content"; import { useDraftStore } from "@posthog/ui/features/message-editor/draftStore"; -import { Tooltip as ShortcutTooltip } from "@posthog/ui/primitives/Tooltip"; import { openTaskInput } from "@posthog/ui/router/useOpenTask"; import { track } from "@posthog/ui/shell/analytics"; import { useRouterState } from "@tanstack/react-router"; @@ -96,24 +96,21 @@ export function ChannelsFab({ channelId }: { channelId?: string }) { return ( <> - {channelsLayout ? ( - // The draft dot needs saying out loud, and the button is where the - // create shortcut is worth advertising. - - - - ) : ( - - } /> - - Create something new - - - )} + + } /> + + {channelsLayout ? ( + <> + {/* The draft dot needs saying out loud, and the button is where + the create shortcut is worth advertising. */} + {hasDraft ? "Create — you have a draft" : "Create"} + {formatHotkey(SHORTCUTS.NEW_TASK)} + + ) : ( + "Create something new" + )} + + span]:w-full [&>span]:gap-2", + // quill highlights an option with an offset focus ring, which suits a + // popup listbox but reads as a stray outline on a sidebar row — and at + // dark-theme contrast it outshouts the selected row it sits next to. + // Same fill the rows already hover to, matching ProjectSwitcher's list. + "ring-offset-0 data-highlighted:border-transparent data-highlighted:bg-fill-hover data-highlighted:ring-0", className, )} // The two branches take the same handlers typed against different diff --git a/packages/ui/src/features/command/CommandSearchBar.tsx b/packages/ui/src/features/command/CommandSearchBar.tsx new file mode 100644 index 0000000000..9b905e4b62 --- /dev/null +++ b/packages/ui/src/features/command/CommandSearchBar.tsx @@ -0,0 +1,38 @@ +import { MagnifyingGlass } from "@phosphor-icons/react"; +import { Kbd } from "@posthog/quill"; +import { + formatHotkey, + SHORTCUTS, +} from "@posthog/ui/features/command/keyboard-shortcuts"; + +/** + * The search field over the content pane — a target you aim at, not an icon you + * hunt for. It opens the command menu rather than typing into anything, so it is + * a button wearing a field's clothes: the placeholder and the shortcut hint are + * what tell you the menu is behind it. + * + * It fills the title bar's middle, which is the content column's width, and caps + * itself so a wide window leaves it centred rather than stretched. + */ +export function CommandSearchBar({ onClick }: { onClick: () => void }) { + return ( + // The row is a window-drag region; the field has to opt out of it or the + // press that should open the menu drags the window instead. +
+ +
+ ); +} diff --git a/packages/ui/src/router/routes/__root.tsx b/packages/ui/src/router/routes/__root.tsx index 7cc2b195db..ff418538cc 100644 --- a/packages/ui/src/router/routes/__root.tsx +++ b/packages/ui/src/router/routes/__root.tsx @@ -2,7 +2,6 @@ import { ArrowSquareOut, CaretLeftIcon, CaretRightIcon, - MagnifyingGlass, } from "@phosphor-icons/react"; import { useHostTRPC, useHostTRPCClient } from "@posthog/host-router/react"; import { Button, ButtonGroup } from "@posthog/quill"; @@ -35,12 +34,9 @@ import { useCanvasDeepLink } from "@posthog/ui/features/canvas/hooks/useCanvasDe import { useChannelDeepLink } from "@posthog/ui/features/canvas/hooks/useChannelDeepLink"; import { useChannelsLayout } from "@posthog/ui/features/canvas/hooks/useChannelsLayout"; import { CommandMenu } from "@posthog/ui/features/command/CommandMenu"; +import { CommandSearchBar } from "@posthog/ui/features/command/CommandSearchBar"; import { GlobalFilePicker } from "@posthog/ui/features/command/GlobalFilePicker"; import { KeyboardShortcutsSheet } from "@posthog/ui/features/command/KeyboardShortcutsSheet"; -import { - formatHotkey, - SHORTCUTS, -} from "@posthog/ui/features/command/keyboard-shortcuts"; import { ConnectivityBanner } from "@posthog/ui/features/connectivity/ConnectivityBanner"; import { useNewTaskDeepLink } from "@posthog/ui/features/deep-links/useNewTaskDeepLink"; import { useOpenTargetDeepLink } from "@posthog/ui/features/deep-links/useOpenTargetDeepLink"; @@ -66,7 +62,6 @@ import { UpdateAvailableModal } from "@posthog/ui/features/updates/UpdateAvailab import { WhatsNewModal } from "@posthog/ui/features/updates/WhatsNewModal"; import { useWorkspaces } from "@posthog/ui/features/workspace/useWorkspace"; import LogosLandscape from "@posthog/ui/primitives/Logo"; -import { Tooltip } from "@posthog/ui/primitives/Tooltip"; import { useAppView } from "@posthog/ui/router/useAppView"; import { openTask, openTaskInput } from "@posthog/ui/router/useOpenTask"; import { track } from "@posthog/ui/shell/analytics"; @@ -399,58 +394,38 @@ function RootLayout() { )} - {(localWorkspaces || channelsLayout) && ( - - {/* Search rides the title bar rather than the sidebar nav — - it's chrome, not a destination — and leads the history - controls so those stay against the sidebar edge. */} - {channelsLayout && ( - - - - )} - {localWorkspaces && ( - - - - - )} - + {localWorkspaces && ( + + + + )} {/* The new layout has no global tab strip (tabs live inside the - task view); search/inbox/activity live in the sidebar nav. The - strip is also the only global owner of Cmd+W, so something has to - keep holding that key when it isn't mounted. */} + task view); inbox/activity live in the sidebar nav. The strip is + also the only global owner of Cmd+W, so something has to keep + holding that key when it isn't mounted. */} {channelsLayout ? ( - + <> + + + ) : ( )}