From f44a8994dadea998e8573265e7f5c76ae44c913d Mon Sep 17 00:00:00 2001 From: code-qtzl <122120619+code-qtzl@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:10:33 -0500 Subject: [PATCH] fix(site): improve keyboard navigation in help popovers (#23374) --- .../ActiveUserChart/ActiveUserChart.tsx | 28 ++++----- .../HelpPopover/HelpPopover.stories.tsx | 36 +++++++++++ .../HelpPopover.tsx} | 48 +++++++-------- .../HelpTooltip/HelpTooltip.stories.tsx | 38 ------------ .../components/InfoTooltip/InfoTooltip.tsx | 32 +++++----- site/src/components/Popover/Popover.tsx | 60 +++++++++++-------- site/src/components/Tooltip/Tooltip.tsx | 4 +- .../DeploymentBanner/DeploymentBannerView.tsx | 6 +- site/src/modules/resources/AgentLatency.tsx | 30 +++++----- .../resources/AgentOutdatedTooltip.tsx | 40 ++++++------- site/src/modules/resources/AgentStatus.tsx | 30 +++++----- .../modules/resources/PortForwardButton.tsx | 28 ++++----- .../modules/resources/SSHButton/SSHButton.tsx | 40 ++++++------- .../resources/SubAgentOutdatedTooltip.tsx | 36 +++++------ .../WorkspaceOutdatedTooltip.tsx | 48 +++++++-------- .../AIBridgePage/AIBridgeHelpPopover.tsx | 32 ++++++++++ .../AIBridgePage/AIBridgeHelpTooltip.tsx | 32 ---------- .../src/pages/AIBridgePage/AIBridgeLayout.tsx | 4 +- site/src/pages/AuditPage/AuditHelpPopover.tsx | 35 +++++++++++ site/src/pages/AuditPage/AuditHelpTooltip.tsx | 35 ----------- site/src/pages/AuditPage/AuditPageView.tsx | 4 +- .../ConnectionLogHelpPopover.tsx | 35 +++++++++++ .../ConnectionLogHelpTooltip.tsx | 35 ----------- .../ConnectionLogPageView.tsx | 4 +- .../IdpOrgSyncPage/IdpOrgSyncPageView.tsx | 28 ++++----- .../IdpSyncPage/IdpGroupSyncForm.tsx | 46 +++++++------- .../OrganizationMembersPageView.tsx | 8 +-- .../UserTable/EditRolesButton.tsx | 28 ++++----- ...Tooltip.tsx => TableColumnHelpPopover.tsx} | 40 ++++++------- .../TemplateInsightsPage.tsx | 44 +++++++------- .../PublishTemplateVersionDialog.tsx | 44 +++++++------- .../pages/TemplatesPage/TemplatesPageView.tsx | 42 ++++++------- .../pages/UsersPage/UsersTable/UsersTable.tsx | 8 +-- .../BuildParametersPopover.tsx | 30 +++++----- .../pages/WorkspacePage/WorkspaceTopbar.tsx | 44 +++++++------- .../WorkspacesPage/WorkspaceHelpPopover.tsx | 40 +++++++++++++ .../WorkspacesPage/WorkspaceHelpTooltip.tsx | 40 ------------- .../WorkspacesPage/WorkspacesPageView.tsx | 4 +- site/src/utils/schedule.tsx | 8 +-- 39 files changed, 590 insertions(+), 584 deletions(-) create mode 100644 site/src/components/HelpPopover/HelpPopover.stories.tsx rename site/src/components/{HelpTooltip/HelpTooltip.tsx => HelpPopover/HelpPopover.tsx} (69%) delete mode 100644 site/src/components/HelpTooltip/HelpTooltip.stories.tsx create mode 100644 site/src/pages/AIBridgePage/AIBridgeHelpPopover.tsx delete mode 100644 site/src/pages/AIBridgePage/AIBridgeHelpTooltip.tsx create mode 100644 site/src/pages/AuditPage/AuditHelpPopover.tsx delete mode 100644 site/src/pages/AuditPage/AuditHelpTooltip.tsx create mode 100644 site/src/pages/ConnectionLogPage/ConnectionLogHelpPopover.tsx delete mode 100644 site/src/pages/ConnectionLogPage/ConnectionLogHelpTooltip.tsx rename site/src/pages/OrganizationSettingsPage/UserTable/{TableColumnHelpTooltip.tsx => TableColumnHelpPopover.tsx} (66%) create mode 100644 site/src/pages/WorkspacesPage/WorkspaceHelpPopover.tsx delete mode 100644 site/src/pages/WorkspacesPage/WorkspaceHelpTooltip.tsx diff --git a/site/src/components/ActiveUserChart/ActiveUserChart.tsx b/site/src/components/ActiveUserChart/ActiveUserChart.tsx index d35b647ba5..79154933f9 100644 --- a/site/src/components/ActiveUserChart/ActiveUserChart.tsx +++ b/site/src/components/ActiveUserChart/ActiveUserChart.tsx @@ -7,12 +7,12 @@ import { ChartTooltipContent, } from "#/components/Chart/Chart"; import { - HelpTooltip, - HelpTooltipContent, - HelpTooltipIconTrigger, - HelpTooltipText, - HelpTooltipTitle, -} from "#/components/HelpTooltip/HelpTooltip"; + HelpPopover, + HelpPopoverContent, + HelpPopoverIconTrigger, + HelpPopoverText, + HelpPopoverTitle, +} from "#/components/HelpPopover/HelpPopover"; import { formatDate } from "#/utils/time"; const chartConfig = { @@ -120,18 +120,18 @@ export const ActiveUsersTitle: FC = ({ interval }) => { return (
{interval === "day" ? "Daily" : "Weekly"} Active Users - - - - How do we calculate active users? - + + + + How do we calculate active users? + When a connection is initiated to a user's workspace they are considered an active user. e.g. apps, web terminal, SSH. This is for measuring user activity and has no connection to license consumption. - - - + + +
); }; diff --git a/site/src/components/HelpPopover/HelpPopover.stories.tsx b/site/src/components/HelpPopover/HelpPopover.stories.tsx new file mode 100644 index 0000000000..fc9fbbd5d4 --- /dev/null +++ b/site/src/components/HelpPopover/HelpPopover.stories.tsx @@ -0,0 +1,36 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { + HelpPopover, + HelpPopoverLink, + HelpPopoverLinksGroup, + HelpPopoverText, + HelpPopoverTitle, +} from "./HelpPopover"; + +const meta: Meta = { + title: "components/HelpPopover", + component: HelpPopover, + args: { + children: ( + <> + What is a template? + + A template is a common configuration for your team's workspaces. + + + + Creating a template + + + Updating a template + + + + ), + }, +}; + +export default meta; +type Story = StoryObj; + +export const Example: Story = {}; diff --git a/site/src/components/HelpTooltip/HelpTooltip.tsx b/site/src/components/HelpPopover/HelpPopover.tsx similarity index 69% rename from site/src/components/HelpTooltip/HelpTooltip.tsx rename to site/src/components/HelpPopover/HelpPopover.tsx index 0b72421bd0..9d07389ee5 100644 --- a/site/src/components/HelpTooltip/HelpTooltip.tsx +++ b/site/src/components/HelpPopover/HelpPopover.tsx @@ -1,32 +1,29 @@ import { CircleHelpIcon, ExternalLinkIcon } from "lucide-react"; import type { FC, HTMLAttributes, PropsWithChildren, ReactNode } from "react"; import { - Tooltip, - TooltipContent, - type TooltipContentProps, - type TooltipProps, - TooltipTrigger, -} from "#/components/Tooltip/Tooltip"; + Popover, + PopoverContent, + type PopoverContentProps, + PopoverTrigger, +} from "#/components/Popover/Popover"; import { cn } from "#/utils/cn"; type Icon = typeof CircleHelpIcon; type Size = "small" | "medium"; -export const HelpTooltipTrigger = TooltipTrigger; +export const HelpPopoverTrigger = PopoverTrigger; -export const HelpTooltipIcon = CircleHelpIcon; +export const HelpPopoverIcon = CircleHelpIcon; -export const HelpTooltip: FC = (props) => { - return ; -}; +export const HelpPopover = Popover; -export const HelpTooltipContent: FC = ({ +export const HelpPopoverContent: FC = ({ className, ...props }) => { return ( - = ({ ); }; -type HelpTooltipIconTriggerProps = React.ComponentPropsWithRef<"button"> & { +type HelpPopoverIconTriggerProps = React.ComponentPropsWithRef<"button"> & { size?: Size; hoverEffect?: boolean; }; -export const HelpTooltipIconTrigger: React.FC = ({ +export const HelpPopoverIconTrigger: React.FC = ({ size = "medium", - children = , + children = , hoverEffect = true, className, ...buttonProps }) => { return ( - + - + ); }; -export const HelpTooltipTitle: FC> = ({ +export const HelpPopoverTitle: FC> = ({ children, className, ...attrs @@ -88,7 +86,7 @@ export const HelpTooltipTitle: FC> = ({ ); }; -export const HelpTooltipText: FC> = ({ +export const HelpPopoverText: FC> = ({ children, className, ...attrs @@ -106,12 +104,12 @@ export const HelpTooltipText: FC> = ({ ); }; -interface HelpTooltipLink { +interface HelpPopoverLink { children?: ReactNode; href: string; } -export const HelpTooltipLink: FC = ({ children, href }) => { +export const HelpPopoverLink: FC = ({ children, href }) => { return ( = ({ children, href }) => { ); }; -interface HelpTooltipActionProps { +interface HelpPopoverActionProps { children?: ReactNode; icon: Icon; onClick: () => void; ariaLabel?: string; } -export const HelpTooltipAction: FC = ({ +export const HelpPopoverAction: FC = ({ children, icon: Icon, onClick, @@ -151,6 +149,6 @@ export const HelpTooltipAction: FC = ({ ); }; -export const HelpTooltipLinksGroup: FC = ({ children }) => { +export const HelpPopoverLinksGroup: FC = ({ children }) => { return
{children}
; }; diff --git a/site/src/components/HelpTooltip/HelpTooltip.stories.tsx b/site/src/components/HelpTooltip/HelpTooltip.stories.tsx deleted file mode 100644 index a0c1d75229..0000000000 --- a/site/src/components/HelpTooltip/HelpTooltip.stories.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react-vite"; -import { - HelpTooltip, - HelpTooltipLink, - HelpTooltipLinksGroup, - HelpTooltipText, - HelpTooltipTitle, -} from "./HelpTooltip"; - -const meta: Meta = { - title: "components/HelpTooltip", - component: HelpTooltip, - args: { - children: ( - <> - What is a template? - - A template is a common configuration for your team's workspaces. - - - - Creating a template - - - Updating a template - - - - ), - }, -}; - -export default meta; -type Story = StoryObj; - -const Example: Story = {}; - -export { Example as HelpTooltip }; diff --git a/site/src/components/InfoTooltip/InfoTooltip.tsx b/site/src/components/InfoTooltip/InfoTooltip.tsx index e9953b290e..1dcf462603 100644 --- a/site/src/components/InfoTooltip/InfoTooltip.tsx +++ b/site/src/components/InfoTooltip/InfoTooltip.tsx @@ -1,12 +1,12 @@ import type { FC, ReactNode } from "react"; import { - HelpTooltip, - HelpTooltipContent, - HelpTooltipIcon, - HelpTooltipIconTrigger, - HelpTooltipText, - HelpTooltipTitle, -} from "#/components/HelpTooltip/HelpTooltip"; + HelpPopover, + HelpPopoverContent, + HelpPopoverIcon, + HelpPopoverIconTrigger, + HelpPopoverText, + HelpPopoverTitle, +} from "#/components/HelpPopover/HelpPopover"; import type { ThemeRole } from "#/theme/roles"; import { cn } from "#/utils/cn"; @@ -34,14 +34,14 @@ export const InfoTooltip: FC = ({ type = "info", }) => { return ( - - - - - - {title && {title}} - {message} - - + + + + + + {title && {title}} + {message} + + ); }; diff --git a/site/src/components/Popover/Popover.tsx b/site/src/components/Popover/Popover.tsx index 398da34fa3..ac6b7b4169 100644 --- a/site/src/components/Popover/Popover.tsx +++ b/site/src/components/Popover/Popover.tsx @@ -5,7 +5,11 @@ import * as PopoverPrimitive from "@radix-ui/react-popover"; import { cn } from "#/utils/cn"; -export type PopoverContentProps = PopoverPrimitive.PopoverContentProps; +export type PopoverContentProps = React.ComponentPropsWithRef< + typeof PopoverPrimitive.Content +> & { + disablePortal?: boolean; +}; export type PopoverTriggerProps = PopoverPrimitive.PopoverTriggerProps; @@ -13,28 +17,36 @@ export const Popover = PopoverPrimitive.Root; export const PopoverTrigger = PopoverPrimitive.Trigger; -export const PopoverContent: React.FC< - React.ComponentPropsWithRef -> = ({ className, align = "center", sideOffset = 4, ...props }) => { - return ( - - - +export const PopoverContent: React.FC = ({ + className, + align = "center", + sideOffset = 4, + disablePortal, + ...props +}) => { + const content = ( + + ); + + return disablePortal ? ( + content + ) : ( + {content} ); }; diff --git a/site/src/components/Tooltip/Tooltip.tsx b/site/src/components/Tooltip/Tooltip.tsx index b4a3a2e359..7ee76e7885 100644 --- a/site/src/components/Tooltip/Tooltip.tsx +++ b/site/src/components/Tooltip/Tooltip.tsx @@ -7,15 +7,13 @@ import { cn } from "#/utils/cn"; export const TooltipProvider = TooltipPrimitive.Provider; -export type TooltipProps = TooltipPrimitive.TooltipProps; - export const Tooltip = TooltipPrimitive.Root; export const TooltipTrigger = TooltipPrimitive.Trigger; export const TooltipArrow = TooltipPrimitive.Arrow; -export type TooltipContentProps = React.ComponentPropsWithRef< +type TooltipContentProps = React.ComponentPropsWithRef< typeof TooltipPrimitive.Content > & { disablePortal?: boolean; diff --git a/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.tsx b/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.tsx index 8c6129eede..1725585dda 100644 --- a/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.tsx +++ b/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.tsx @@ -24,7 +24,7 @@ import type { WorkspaceStatus, } from "#/api/typesGenerated"; import { Button } from "#/components/Button/Button"; -import { HelpTooltipTitle } from "#/components/HelpTooltip/HelpTooltip"; +import { HelpPopoverTitle } from "#/components/HelpPopover/HelpPopover"; import { JetBrainsIcon } from "#/components/Icons/JetBrainsIcon"; import { RocketIcon } from "#/components/Icons/RocketIcon"; import { TerminalIcon } from "#/components/Icons/TerminalIcon"; @@ -137,9 +137,9 @@ export const DeploymentBannerView: FC = ({ > {healthErrors.length > 0 ? ( <> - + We have detected problems with your Coder deployment. - +
{healthErrors.map((error) => ( {error} diff --git a/site/src/modules/resources/AgentLatency.tsx b/site/src/modules/resources/AgentLatency.tsx index 0081d83ff1..964752fcb1 100644 --- a/site/src/modules/resources/AgentLatency.tsx +++ b/site/src/modules/resources/AgentLatency.tsx @@ -1,12 +1,12 @@ import type { FC } from "react"; import type { DERPRegion, WorkspaceAgent } from "#/api/typesGenerated"; import { - HelpTooltip, - HelpTooltipContent, - HelpTooltipText, - HelpTooltipTitle, - HelpTooltipTrigger, -} from "#/components/HelpTooltip/HelpTooltip"; + HelpPopover, + HelpPopoverContent, + HelpPopoverText, + HelpPopoverTitle, + HelpPopoverTrigger, +} from "#/components/HelpPopover/HelpPopover"; import { cn } from "#/utils/cn"; import { getLatencyColor } from "#/utils/latency"; @@ -41,8 +41,8 @@ export const AgentLatency: FC = ({ agent }) => { } return ( - - + + = ({ agent }) => { > {Math.round(latency.latency_ms)}ms - - - Latency - + + + Latency + This is the latency overhead on non peer to peer connections. The first row is the preferred relay. - +
{Object.entries(agent.latency) .sort(([, a], [, b]) => a.latency_ms - b.latency_ms) @@ -73,7 +73,7 @@ export const AgentLatency: FC = ({ agent }) => {
))}
- - + + ); }; diff --git a/site/src/modules/resources/AgentOutdatedTooltip.tsx b/site/src/modules/resources/AgentOutdatedTooltip.tsx index 1c84bef957..499a17b2bf 100644 --- a/site/src/modules/resources/AgentOutdatedTooltip.tsx +++ b/site/src/modules/resources/AgentOutdatedTooltip.tsx @@ -2,14 +2,14 @@ import { RotateCcwIcon } from "lucide-react"; import { type FC, useState } from "react"; import type { WorkspaceAgent } from "#/api/typesGenerated"; import { - HelpTooltip, - HelpTooltipAction, - HelpTooltipContent, - HelpTooltipLinksGroup, - HelpTooltipText, - HelpTooltipTitle, - HelpTooltipTrigger, -} from "#/components/HelpTooltip/HelpTooltip"; + HelpPopover, + HelpPopoverAction, + HelpPopoverContent, + HelpPopoverLinksGroup, + HelpPopoverText, + HelpPopoverTitle, + HelpPopoverTrigger, +} from "#/components/HelpPopover/HelpPopover"; import { Stack } from "#/components/Stack/Stack"; import { agentVersionStatus } from "../../utils/workspace"; @@ -39,17 +39,17 @@ export const AgentOutdatedTooltip: FC = ({ const text = `${opener} This can happen after you update Coder with running workspaces. To fix this, you can stop and start the workspace.`; return ( - - + + {status === agentVersionStatus.Outdated ? "Outdated" : "Deprecated"} - - + +
- {title} - {text} + {title} + {text}
@@ -66,8 +66,8 @@ export const AgentOutdatedTooltip: FC = ({ {serverVersion} - - + { onUpdate(); @@ -76,10 +76,10 @@ export const AgentOutdatedTooltip: FC = ({ ariaLabel="Update workspace" > Update workspace - - + +
-
-
+ + ); }; diff --git a/site/src/modules/resources/AgentStatus.tsx b/site/src/modules/resources/AgentStatus.tsx index d6f185b233..a3a812f412 100644 --- a/site/src/modules/resources/AgentStatus.tsx +++ b/site/src/modules/resources/AgentStatus.tsx @@ -8,12 +8,12 @@ import type { } from "#/api/typesGenerated"; import { ChooseOne, Cond } from "#/components/Conditionals/ChooseOne"; import { - HelpTooltip, - HelpTooltipContent, - HelpTooltipText, - HelpTooltipTitle, - HelpTooltipTrigger, -} from "#/components/HelpTooltip/HelpTooltip"; + HelpPopover, + HelpPopoverContent, + HelpPopoverText, + HelpPopoverTitle, + HelpPopoverTrigger, +} from "#/components/HelpPopover/HelpPopover"; import { Tooltip, TooltipContent, @@ -52,8 +52,8 @@ const AgentWarningTooltip: FC = ({ variant = "warning", }) => { return ( - - + + = ({ : "text-content-destructive", )} /> - - - {title} - + + + {title} + {detail} {troubleshootingURL && ( <> @@ -75,9 +75,9 @@ const AgentWarningTooltip: FC = ({ )} - - - + + + ); }; diff --git a/site/src/modules/resources/PortForwardButton.tsx b/site/src/modules/resources/PortForwardButton.tsx index 7bd78b0d2d..8e5e4f696b 100644 --- a/site/src/modules/resources/PortForwardButton.tsx +++ b/site/src/modules/resources/PortForwardButton.tsx @@ -37,10 +37,10 @@ import { import { ChevronDownIcon } from "#/components/AnimatedIcons/ChevronDown"; import { Button } from "#/components/Button/Button"; import { - HelpTooltipLink, - HelpTooltipText, - HelpTooltipTitle, -} from "#/components/HelpTooltip/HelpTooltip"; + HelpPopoverLink, + HelpPopoverText, + HelpPopoverTitle, +} from "#/components/HelpPopover/HelpPopover"; import { Popover, PopoverContent, @@ -258,18 +258,18 @@ export const PortForwardPopoverView: FC = ({ justifyContent="space-between" alignItems="start" > - Listening Ports - Listening Ports + Learn more - +
- + The listening ports are exclusively accessible to you. Selecting HTTP/S will change the protocol for all listening ports. - +