Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Tooltip with Radix and Tailwind #709

Merged
merged 4 commits into from
Dec 10, 2024
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
1 change: 1 addition & 0 deletions packages/graph-explorer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.4",
"@react-aria/button": "3.10.1",
"@react-aria/checkbox": "3.14.8",
"@react-aria/combobox": "3.10.5",
Expand Down
17 changes: 6 additions & 11 deletions packages/graph-explorer/src/components/IconButton.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { cn } from "@/utils";
import type { ComponentPropsWithoutRef, ForwardedRef, ReactNode } from "react";
import { forwardRef } from "react";
import type { TooltipProps } from "./Tooltip";
import Tooltip from "./Tooltip/Tooltip";
import { cva, type VariantProps } from "cva";
import { Tooltip, TooltipContent } from "@/components";
import { TooltipTrigger } from "@radix-ui/react-tooltip";

const iconButtonVariants = cva({
base: "focus-visible:ring-ring inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded font-medium transition-colors duration-150 focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50 disabled:saturate-0 [&_svg]:pointer-events-none [&_svg]:shrink-0",
Expand Down Expand Up @@ -55,15 +55,13 @@ export interface IconButtonProps
extends Omit<ComponentPropsWithoutRef<"button">, "color">,
VariantProps<typeof iconButtonVariants> {
icon: ReactNode;
tooltipText?: TooltipProps["text"];
tooltipPlacement?: TooltipProps["placement"];
tooltipText?: React.ReactNode;
}

export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
(
{
tooltipText,
tooltipPlacement,
variant,
size,
color,
Expand All @@ -86,12 +84,9 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(

if (tooltipText) {
return (
<Tooltip
text={tooltipText}
placement={tooltipPlacement}
delayEnter={400}
>
{component}
<Tooltip>
<TooltipTrigger asChild>{component}</TooltipTrigger>
<TooltipContent>{tooltipText}</TooltipContent>
</Tooltip>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ export function ExternalExportControl<T extends Record<string, unknown>>({
return (
<Popover>
<PopoverTrigger asChild>
<IconButton variant="text" icon={<TrayArrowIcon />} />
<IconButton
variant="text"
icon={<TrayArrowIcon />}
tooltipText="Export table"
/>
</PopoverTrigger>
<PopoverContent side="right" className="w-72">
<ExportOptionsModal
Expand Down
24 changes: 6 additions & 18 deletions packages/graph-explorer/src/components/Tooltip/InfoTooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,14 @@
import { PropsWithChildren } from "react";
import { InfoIcon } from "@/components/icons";
import Tooltip from "./Tooltip";
import { useTheme } from "@/core";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components";

export default function InfoTooltip({ children }: PropsWithChildren) {
const theme = useTheme();

return (
<Tooltip text={<div style={{ maxWidth: 300 }}>{children}</div>}>
<div
style={{
display: "flex",
gap: 2,
alignItems: "center",
justifyItems: "center",
}}
>
<InfoIcon
color={theme.theme.palette.text.secondary}
style={{ width: 22, height: 22 }}
/>
</div>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="text-text-secondary size-6" />
</TooltipTrigger>
<TooltipContent>{children}</TooltipContent>
</Tooltip>
);
}
13 changes: 0 additions & 13 deletions packages/graph-explorer/src/components/Tooltip/Tooltip.styles.ts

This file was deleted.

118 changes: 22 additions & 96 deletions packages/graph-explorer/src/components/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,103 +1,29 @@
import * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "@/utils";
import { AnimatePresence, motion } from "framer-motion";
import type { PropsWithChildren, ReactNode } from "react";
import { cloneElement, useEffect } from "react";
import { Arrow, useHover, useLayer } from "react-laag";
import type { PlacementType } from "react-laag/dist/PlacementType";
import { useWithTheme } from "@/core";
import usePrevious from "@/hooks/usePrevious";
import { tooltipStyles } from "./Tooltip.styles";

function isReactText(children: ReactNode) {
return ["string", "number"].includes(typeof children);
}
const TooltipProvider = TooltipPrimitive.Provider;

export type TooltipProps = {
text: ReactNode;
placement?: PlacementType;
delayEnter?: number;
delayLeave?: number;
triggerOffset?: number;
className?: string;
disabled?: boolean;
onHoverChange?: (isOver: boolean) => void;
};
const Tooltip = TooltipPrimitive.Root;

export const Tooltip = ({
children,
text,
placement = "bottom-center",
delayEnter = 100,
delayLeave = 300,
triggerOffset = 8,
className,
disabled,
onHoverChange,
}: PropsWithChildren<TooltipProps>) => {
const [isOver, hoverProps] = useHover({ delayEnter, delayLeave });
const [isOverTooltip, hoverTooltipProps] = useHover({
delayEnter,
delayLeave,
});
const { triggerProps, layerProps, arrowProps, renderLayer } = useLayer({
isOpen: !disabled && (isOver || isOverTooltip),
auto: true,
placement,
triggerOffset,
});
const TooltipTrigger = TooltipPrimitive.Trigger;

const prevIsOver = usePrevious(isOver);

useEffect(() => {
if (prevIsOver !== isOver) onHoverChange?.(isOver);
}, [isOver, onHoverChange, prevIsOver]);
// when children equals text (string | number), we need to wrap it in an
// extra span-element in order to attach props
let trigger;
if (isReactText(children)) {
trigger = (
<div className="tooltip-text-wrapper" {...triggerProps} {...hoverProps}>
{children}
</div>
);
} else {
// In case of an react-element, we need to clone it in order to attach our own props
trigger = cloneElement(children as any, {
...triggerProps,
...hoverProps,
});
}

const stylesWithTheme = useWithTheme();

return (
<>
{trigger}
{renderLayer(
<AnimatePresence>
{!disabled && (isOver || isOverTooltip) && (
<motion.div
className={cn(stylesWithTheme(tooltipStyles), className)}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
transition={{ duration: 0.1 }}
{...layerProps}
style={{ pointerEvents: "none", ...layerProps.style }}
>
<span {...hoverTooltipProps}>{text}</span>
<Arrow
{...arrowProps}
backgroundColor="rgb(78, 78, 78)"
borderColor="transparent"
size={6}
/>
</motion.div>
)}
</AnimatePresence>
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"bg-text-primary text-background-default animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-tooltip max-w-96 overflow-hidden rounded-md px-3 py-1.5 text-sm",
className
)}
</>
);
};
{...props}
/>
</TooltipPrimitive.Portal>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;

export default Tooltip;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
3 changes: 1 addition & 2 deletions packages/graph-explorer/src/components/Tooltip/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export { default as Tooltip } from "./Tooltip";
export * from "./Tooltip";
export { default as InfoTooltip } from "./InfoTooltip";
export type { TooltipProps } from "./Tooltip";
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from "react";
import * as TogglePrimitive from "@radix-ui/react-toggle";
import { Tooltip } from "../../Tooltip";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components";
import { cn } from "@/utils";

const SidebarButton = React.forwardRef<
Expand All @@ -13,22 +13,25 @@ const SidebarButton = React.forwardRef<
>(({ icon, title, badge = false, className, children, ...props }, ref) => {
return (
<Badge value={badge}>
<Tooltip text={title} placement="left-center" delayEnter={300}>
<TogglePrimitive.Root
ref={ref}
className={cn(
"text-brand-900 data-[state=on]:bg-brand-500 active:bg-brand-300 inline-flex size-10 items-center justify-center rounded-md bg-transparent p-2 ring-0 transition-colors duration-100 focus:shadow-none active:text-white disabled:pointer-events-none disabled:opacity-50 data-[state=on]:text-white [&_svg]:size-6",
"hover:bg-brand-200/50 hover:text-primary-dark hover:data-[state=on]:bg-brand-400",
"dark:text-brand-300 dark:hover:data-[state=on]:bg-brand-500 dark:data-[state=on]:bg-brand-400 dark:hover:bg-gray-800 dark:data-[state=on]:text-white",
"focus-visible:ring-brand-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 active:ring-0",
className
)}
{...props}
>
{icon}
{children}
<span className="sr-only">{title}</span>
</TogglePrimitive.Root>
<Tooltip>
<TooltipTrigger>
<TogglePrimitive.Root
ref={ref}
className={cn(
"text-brand-900 data-[state=on]:bg-brand-500 active:bg-brand-300 inline-flex size-10 items-center justify-center rounded-md bg-transparent p-2 ring-0 transition-colors duration-100 focus:shadow-none active:text-white disabled:pointer-events-none disabled:opacity-50 data-[state=on]:text-white [&_svg]:size-6",
"hover:bg-brand-200/50 hover:text-primary-dark hover:data-[state=on]:bg-brand-400",
"dark:text-brand-300 dark:hover:data-[state=on]:bg-brand-500 dark:data-[state=on]:bg-brand-400 dark:hover:bg-gray-800 dark:data-[state=on]:text-white",
"focus-visible:ring-brand-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 active:ring-0",
className
)}
{...props}
>
{icon}
{children}
<span className="sr-only">{title}</span>
</TogglePrimitive.Root>
</TooltipTrigger>
<TooltipContent side="left">{title}</TooltipContent>
</Tooltip>
</Badge>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ import type { PropsWithChildren, ReactElement } from "react";
import { useMemo } from "react";
import getChildOfType from "@/utils/getChildOfType";
import getChildrenOfType from "@/utils/getChildrenOfType";
import { SidebarButton } from "@/components/Workspace/components/SidebarButton";
import WorkspaceSideBarContent from "./WorkspaceSideBarContent";

interface WorkspaceSideBarComposition {
Button: typeof SidebarButton;
Content: typeof WorkspaceSideBarContent;
}

Expand Down Expand Up @@ -52,7 +50,6 @@ const WorkspaceSideBar = ({

WorkspaceSideBar.displayName = "WorkspaceSideBar";

WorkspaceSideBar.Button = SidebarButton;
WorkspaceSideBar.Content = WorkspaceSideBarContent;

export default WorkspaceSideBar as ((
Expand Down
1 change: 1 addition & 0 deletions packages/graph-explorer/src/components/Workspace/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export { default as WorkspaceTopBarTitle } from "./components/WorkspaceTopBarTit
export type { WorkspaceTopBarTitleProps } from "./components/WorkspaceTopBarTitle";
export { default as WorkspacesContent } from "./components/WorkspacesContent";
export { default as WorkspaceSideBar } from "./components/WorkspaceSideBar";
export * from "./components/SidebarButton";
export { default as WorkspaceSideBarContent } from "./components/WorkspaceSideBarContent";
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { emotionTransform, MantineEmotionProvider } from "@mantine/emotion";
import { ExpandNodeProvider } from "@/hooks/useExpandNode";
import { ErrorBoundary } from "react-error-boundary";
import AppErrorPage from "@/core/AppErrorPage";
import { TooltipProvider } from "@/components";

export type ConnectedProviderProps = {
config?: RawConfiguration;
Expand Down Expand Up @@ -38,19 +39,21 @@ const ConnectedProvider = (
return (
<ErrorBoundary FallbackComponent={AppErrorPage}>
<QueryClientProvider client={queryClient}>
<MantineProvider stylesTransform={emotionTransform}>
<MantineEmotionProvider>
<ThemeProvider>
<NotificationProvider component={Toast}>
<StateProvider>
<AppStatusLoader config={config}>
<ExpandNodeProvider>{children}</ExpandNodeProvider>
</AppStatusLoader>
</StateProvider>
</NotificationProvider>
</ThemeProvider>
</MantineEmotionProvider>
</MantineProvider>
<TooltipProvider delayDuration={200}>
<MantineProvider stylesTransform={emotionTransform}>
<MantineEmotionProvider>
<ThemeProvider>
<NotificationProvider component={Toast}>
<StateProvider>
<AppStatusLoader config={config}>
<ExpandNodeProvider>{children}</ExpandNodeProvider>
</AppStatusLoader>
</StateProvider>
</NotificationProvider>
</ThemeProvider>
</MantineEmotionProvider>
</MantineProvider>
</TooltipProvider>
</QueryClientProvider>
</ErrorBoundary>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function EntityAttribute({
<div className="text-text-secondary text-balance break-words text-sm">
{attribute.displayLabel}
</div>
<div className="text-text-primary text-balance break-words">
<div className="text-text-primary text-balance break-words font-medium">
{attribute.displayValue}
</div>
</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,6 @@ export default function GraphViewer({
/>
<IconButton
tooltipText="Re-run Layout"
tooltipPlacement="bottom-center"
icon={<ResetIcon />}
variant="text"
onClick={() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,6 @@ export default function SingleNodeStyling({
</>
}
tooltipText="Upload New Icon"
tooltipPlacement="bottom-end"
onClick={props.onClick}
/>
)}
Expand Down
Loading
Loading