Skip to content
Closed
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
10 changes: 5 additions & 5 deletions site/src/components/Badges/Badges.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Interpolation, Theme } from "@emotion/react";
import Tooltip from "@mui/material/Tooltip";
import MiniTooltip from "components/MiniTooltip/MiniTooltip";
import { Stack } from "components/Stack/Stack";
import {
type FC,
Expand Down Expand Up @@ -69,17 +69,17 @@ export const NotHealthyBadge: FC = () => {

export const NotRegisteredBadge: FC = () => {
return (
<Tooltip title="Workspace Proxy has never come online and needs to be started.">
<MiniTooltip title="Workspace Proxy has never come online and needs to be started.">
<span css={[styles.badge, styles.warnBadge]}>Never seen</span>
</Tooltip>
</MiniTooltip>
);
};

export const NotReachableBadge: FC = () => {
return (
<Tooltip title="Workspace Proxy not responding to http(s) requests.">
<MiniTooltip title="Workspace Proxy not responding to http(s) requests.">
<span css={[styles.badge, styles.warnBadge]}>Not reachable</span>
</Tooltip>
</MiniTooltip>
);
};

Expand Down
16 changes: 7 additions & 9 deletions site/src/components/CopyableValue/CopyableValue.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import Tooltip, { type TooltipProps } from "@mui/material/Tooltip";
import MiniTooltip from "components/MiniTooltip/MiniTooltip";
import type { TooltipContentProps } from "components/Tooltip/Tooltip";
import { useClickable } from "hooks/useClickable";
import { useClipboard } from "hooks/useClipboard";
import type { FC, HTMLAttributes } from "react";

interface CopyableValueProps extends HTMLAttributes<HTMLSpanElement> {
value: string;
placement?: TooltipProps["placement"];
PopperProps?: TooltipProps["PopperProps"];
align?: TooltipContentProps["align"];
}

export const CopyableValue: FC<CopyableValueProps> = ({
value,
placement = "bottom-start",
PopperProps,
align = "start",
children,
...attrs
}) => {
Expand All @@ -22,14 +21,13 @@ export const CopyableValue: FC<CopyableValueProps> = ({
});

return (
<Tooltip
<MiniTooltip
title={showCopiedSuccess ? "Copied!" : "Click to copy"}
placement={placement}
PopperProps={PopperProps}
align={align}
>
<span {...attrs} {...clickableProps} css={{ cursor: "pointer" }}>
{children}
</span>
</Tooltip>
</MiniTooltip>
);
};
24 changes: 15 additions & 9 deletions site/src/components/Latency/Latency.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useTheme } from "@emotion/react";
import CircularProgress from "@mui/material/CircularProgress";
import Tooltip from "@mui/material/Tooltip";
import { Abbr } from "components/Abbr/Abbr";
import MiniTooltip from "components/MiniTooltip/MiniTooltip";
import { CircleHelpIcon } from "lucide-react";
import type { FC } from "react";
import { cn } from "utils/cn";
Expand All @@ -26,23 +26,29 @@ export const Latency: FC<LatencyProps> = ({

if (isLoading) {
return (
<Tooltip title="Loading latency..." className={className}>
<CircularProgress
className={cn("!size-icon-xs", iconClassName)}
style={{ color }}
/>
</Tooltip>
<MiniTooltip title="Loading latency..." className={className}>
{/**
* Spinning progress icon must be placed inside a fixed-size container,
* to ensure tooltip remains stationary when opened
*/}
<div className="size-4 flex flex-wrap place-content-center">
<CircularProgress
className={cn("!size-icon-xs", iconClassName)}
style={{ color }}
/>
</div>
</MiniTooltip>
);
}

if (!latency) {
return (
<Tooltip title="Latency not available" className={className}>
<MiniTooltip title="Latency not available" className={className}>
<CircleHelpIcon
className={cn("!size-icon-sm", iconClassName)}
style={{ color }}
/>
</Tooltip>
</MiniTooltip>
);
}

Expand Down
53 changes: 53 additions & 0 deletions site/src/components/MiniTooltip/MiniTooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
Tooltip,
TooltipArrow,
TooltipContent,
type TooltipContentProps,
TooltipProvider,
TooltipTrigger,
} from "components/Tooltip/Tooltip";
import { type FC, type ReactNode, useState } from "react";
import { cn } from "utils/cn";

type MiniTooltipProps = Omit<TooltipContentProps, "title"> & {
title: ReactNode;
arrow?: boolean;
open?: boolean;
};

const MiniTooltip: FC<MiniTooltipProps> = ({
title,
children,
arrow,
open = false,
...contentProps
}) => {
const [isOpen, setIsOpen] = useState(open);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to have state here, but the current implementation will cause bugs. If the open prop ever changes during re-renders, then we won't ever acknowledge it, because we'll be driving everything via the isOpen state instead

I think this will work

const MiniTooltip: FC<MiniTooltipProps> = ({
  // No default assignment
	open,
  // Also destructure onOpenChange, even if it's not defined
  onOpenChange,
	...contentProps
}) => {
	const [isOpen, setIsOpen] = useState(false);
  const activeIsOpen = open ?? isOpen;

  // Sync state change to both so that if the component ever flips from
  // controlled to uncontrolled, the other piece of state won't be lagging behind
  const handleOpenChange = (newOpen: boolean) => {
    setIsOpen(newOpen);
    onOpenChange?.(newOpen);
  };


return (
<TooltipProvider>
<Tooltip delayDuration={0} open={isOpen} onOpenChange={setIsOpen}>
<TooltipTrigger
asChild
aria-label={typeof title === "string" ? title : undefined}
>
{children}
</TooltipTrigger>
<TooltipContent
collisionPadding={16}
side="bottom"
{...contentProps}
className={cn(
"max-w-[300px] bg-surface-secondary border-surface-quaternary text-content-primary text-xs",
contentProps.className,
)}
>
{title}
{arrow && <TooltipArrow className="fill-surface-quaternary" />}
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
};

export default MiniTooltip;
18 changes: 9 additions & 9 deletions site/src/components/RichParameterInput/RichParameterInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import type { InputBaseComponentProps } from "@mui/material/InputBase";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import TextField, { type TextFieldProps } from "@mui/material/TextField";
import Tooltip from "@mui/material/Tooltip";
import type { TemplateVersionParameter } from "api/typesGenerated";
import { Button } from "components/Button/Button";
import { ExternalImage } from "components/ExternalImage/ExternalImage";
import { MemoizedMarkdown } from "components/Markdown/Markdown";
import MiniTooltip from "components/MiniTooltip/MiniTooltip";
import { Pill } from "components/Pill/Pill";
import { Stack } from "components/Stack/Stack";
import { CircleAlertIcon, SettingsIcon } from "lucide-react";
Expand Down Expand Up @@ -136,26 +136,26 @@ const ParameterLabel: FC<ParameterLabelProps> = ({ parameter, isPreset }) => {
{displayName}

{!parameter.required && (
<Tooltip title="If no value is specified, the system will default to the value set by the administrator.">
<MiniTooltip title="If no value is specified, the system will default to the value set by the administrator.">
<span css={styles.optionalLabel}>(optional)</span>
</Tooltip>
</MiniTooltip>
)}
{!parameter.mutable && (
<Tooltip title="This value cannot be modified after the workspace has been created.">
<MiniTooltip title="This value cannot be modified after the workspace has been created.">
<Pill
type="warning"
icon={<CircleAlertIcon className="size-icon-xs" />}
>
Immutable
</Pill>
</Tooltip>
</MiniTooltip>
)}
{isPreset && (
<Tooltip title="This value was set by a preset">
<MiniTooltip title="This value was set by a preset">
<Pill type="info" icon={<SettingsIcon className="size-icon-xs" />}>
Preset
</Pill>
</Tooltip>
</MiniTooltip>
)}
</span>
);
Expand Down Expand Up @@ -328,15 +328,15 @@ const RichParameterField: FC<RichParameterInputProps> = ({
css={{ padding: small ? undefined : "4px 0" }}
>
{small ? (
<Tooltip
<MiniTooltip
title={
<MemoizedMarkdown>
{option.description}
</MemoizedMarkdown>
}
>
<div>{option.name}</div>
</Tooltip>
</MiniTooltip>
) : (
<>
<span>{option.name}</span>
Expand Down
6 changes: 3 additions & 3 deletions site/src/components/SearchField/SearchField.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import IconButton from "@mui/material/IconButton";
import InputAdornment from "@mui/material/InputAdornment";
import TextField, { type TextFieldProps } from "@mui/material/TextField";
import Tooltip from "@mui/material/Tooltip";
import MiniTooltip from "components/MiniTooltip/MiniTooltip";
import { useEffectEvent } from "hooks/hookPolyfills";
import { SearchIcon, XIcon } from "lucide-react";
import { type FC, useLayoutEffect, useRef } from "react";
Expand Down Expand Up @@ -47,7 +47,7 @@ export const SearchField: FC<SearchFieldProps> = ({
),
endAdornment: value !== "" && (
<InputAdornment position="end">
<Tooltip title="Clear search">
<MiniTooltip title="Clear search">
<IconButton
size="small"
onClick={() => {
Expand All @@ -57,7 +57,7 @@ export const SearchField: FC<SearchFieldProps> = ({
<XIcon className="size-icon-xs" />
<span className="sr-only">Clear search</span>
</IconButton>
</Tooltip>
</MiniTooltip>
</InputAdornment>
),
...InputProps,
Expand Down
2 changes: 2 additions & 0 deletions site/src/components/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export const Tooltip = TooltipPrimitive.Root;

export const TooltipTrigger = TooltipPrimitive.Trigger;

export const TooltipArrow = TooltipPrimitive.Arrow;

export type TooltipContentProps = React.ComponentPropsWithoutRef<
typeof TooltipPrimitive.Content
> & {
Expand Down
Loading
Loading