From 46b1db7850a99b7a211d36e9b95c41812ff0ced3 Mon Sep 17 00:00:00 2001 From: Jacob Shandling <61553566+jacobshandling@users.noreply.github.com> Date: Wed, 13 Sep 2023 10:19:56 -0700 Subject: [PATCH] UI: Update tooltip/link structure & functionality in Software tables (#13893) --- .../12948-fix-software-bundle-clickability | 3 ++ .../DataTable/LinkCell/LinkCell.tsx | 30 ++++++++++---- .../TableContainer/DataTable/_styles.scss | 25 +++++++---- .../TooltipWrapper/TooltipWrapper.tsx | 10 ++++- .../components/TooltipWrapper/_styles.scss | 41 +++++++++++-------- .../cards/Software/SoftwareTableConfig.tsx | 7 +++- .../SoftwareTableConfig.tsx | 28 ++++--------- frontend/utilities/helpers.ts | 10 +++++ 8 files changed, 96 insertions(+), 58 deletions(-) create mode 100644 changes/12948-fix-software-bundle-clickability diff --git a/changes/12948-fix-software-bundle-clickability b/changes/12948-fix-software-bundle-clickability new file mode 100644 index 0000000000..1fbf8f8acd --- /dev/null +++ b/changes/12948-fix-software-bundle-clickability @@ -0,0 +1,3 @@ +- Restored the ability to click on and select/copy text from software bundle tooltips while + maintaining the abilities to click the software's name to get more details and to click anywhere + else in the row to view all hosts with that software installed. diff --git a/frontend/components/TableContainer/DataTable/LinkCell/LinkCell.tsx b/frontend/components/TableContainer/DataTable/LinkCell/LinkCell.tsx index 3f8b8dd690..f57d6e7055 100644 --- a/frontend/components/TableContainer/DataTable/LinkCell/LinkCell.tsx +++ b/frontend/components/TableContainer/DataTable/LinkCell/LinkCell.tsx @@ -3,6 +3,7 @@ import React from "react"; import { Link } from "react-router"; import classnames from "classnames"; +import TooltipWrapper from "components/TooltipWrapper"; interface ILinkCellProps { value: string | JSX.Element; @@ -10,7 +11,7 @@ interface ILinkCellProps { className?: string; customOnClick?: (e: React.MouseEvent) => void; /** allows viewing overflow for tooltip */ - withTooltip?: boolean; + tooltipContent?: string; title?: string; } @@ -21,21 +22,32 @@ const LinkCell = ({ path, className, customOnClick, - withTooltip, title, + tooltipContent, }: ILinkCellProps): JSX.Element => { - const cellClasses = classnames( - baseClass, - className, - withTooltip && "link-cell-tooltip" - ); + const cellClasses = classnames(baseClass, className); const onClick = (e: React.MouseEvent): void => { customOnClick && customOnClick(e); }; - return ( - + return tooltipContent ? ( + + + {value} + + + ) : ( + {value} ); diff --git a/frontend/components/TableContainer/DataTable/_styles.scss b/frontend/components/TableContainer/DataTable/_styles.scss index 26b18e6f01..ea958e3924 100644 --- a/frontend/components/TableContainer/DataTable/_styles.scss +++ b/frontend/components/TableContainer/DataTable/_styles.scss @@ -241,21 +241,32 @@ $shadow-transition-width: 10px; } } // css to properly style link-cell with tooltip - .link-cell-tooltip { + .link-cell-tooltip-wrapper { overflow: visible; // fixes tooltip overflow cut off by cell + white-space: nowrap; // single line + margin: 0; // padding applied to .link-cell for larger clickable area .component__tooltip-wrapper { - display: block; - white-space: nowrap; // single line - margin: 0; // padding applied to .link-cell for larger clickable area - .component__tooltip-wrapper__element { + &__element { display: block; white-space: nowrap; // single line text-overflow: ellipsis; // truncates text overflow: hidden; + &__underline { + width: 100%; - .component__tooltip-wrapper__underline { - max-width: 100%; // fixes underline overflowing past truncated text + &::after { + bottom: 9px; // compensate for padding to make larger clickable area + } } + // TODO – this naming is now confusing, as this .link-cell is not the outermost layer of + // the cell – it's a NameCell + .link-cell { + padding: 10px 0; + } + } + + &__tip-text { + cursor: auto; } } } diff --git a/frontend/components/TooltipWrapper/TooltipWrapper.tsx b/frontend/components/TooltipWrapper/TooltipWrapper.tsx index b49e88a471..0b48e54b91 100644 --- a/frontend/components/TooltipWrapper/TooltipWrapper.tsx +++ b/frontend/components/TooltipWrapper/TooltipWrapper.tsx @@ -4,7 +4,7 @@ import React from "react"; import * as DOMPurify from "dompurify"; interface ITooltipWrapperProps { - children: string; + children: string | JSX.Element; tipContent: string; position?: "top" | "bottom"; isDelayed?: boolean; @@ -33,11 +33,17 @@ const TooltipWrapper = ({
{children} -
+
{ + e.stopPropagation(); + }} />
); diff --git a/frontend/components/TooltipWrapper/_styles.scss b/frontend/components/TooltipWrapper/_styles.scss index 63714cdd14..b06a53cbaa 100644 --- a/frontend/components/TooltipWrapper/_styles.scss +++ b/frontend/components/TooltipWrapper/_styles.scss @@ -18,26 +18,31 @@ position: static; display: inline; // treat like a span but allow other tags as children white-space: nowrap; - } - &__underline { - position: absolute; - top: 0; - left: 0; - bottom: 0; - - &::before { - content: attr(data-text); - opacity: 0; - visibility: hidden; - } - &::after { - content: ""; - width: 100%; - height: 100%; + &__underline { position: absolute; - bottom: -2px; + top: 0; left: 0; - border-bottom: 1px dashed $ui-fleet-black-50; + bottom: 0; + + &::before { + content: attr(data-text); + opacity: 0; + visibility: hidden; + } + &::after { + content: ""; + width: 100%; + height: 100%; + position: absolute; + bottom: -2px; + left: 0; + border-bottom: 1px dashed $ui-fleet-black-50; + } + } + + a { + position: relative; + z-index: 99; } } &__tip-text { diff --git a/frontend/pages/hosts/details/cards/Software/SoftwareTableConfig.tsx b/frontend/pages/hosts/details/cards/Software/SoftwareTableConfig.tsx index 2b9763d685..1dec977d81 100644 --- a/frontend/pages/hosts/details/cards/Software/SoftwareTableConfig.tsx +++ b/frontend/pages/hosts/details/cards/Software/SoftwareTableConfig.tsx @@ -14,6 +14,7 @@ import TooltipWrapper from "components/TooltipWrapper"; import ViewAllHostsLink from "components/ViewAllHostsLink"; import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants"; import { COLORS } from "styles/var/colors"; +import { getSoftwareBundleTooltipMarkup } from "utilities/helpers"; interface IHeaderProps { column: { @@ -218,8 +219,10 @@ export const generateSoftwareTableHeaders = ({ ); }, diff --git a/frontend/pages/software/ManageSoftwarePage/SoftwareTableConfig.tsx b/frontend/pages/software/ManageSoftwarePage/SoftwareTableConfig.tsx index 78490e4ecf..58f7a8a3d4 100644 --- a/frontend/pages/software/ManageSoftwarePage/SoftwareTableConfig.tsx +++ b/frontend/pages/software/ManageSoftwarePage/SoftwareTableConfig.tsx @@ -6,7 +6,10 @@ import ReactTooltip from "react-tooltip"; import { formatSoftwareType, ISoftware } from "interfaces/software"; import { IVulnerability } from "interfaces/vulnerability"; import PATHS from "router/paths"; -import { formatFloatAsPercentage } from "utilities/helpers"; +import { + formatFloatAsPercentage, + getSoftwareBundleTooltipMarkup, +} from "utilities/helpers"; import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants"; import HeaderCell from "components/TableContainer/DataTable/HeaderCell"; @@ -65,23 +68,6 @@ const condenseVulnerabilities = ( : condensed; }; -const renderBundleTooltip = (name: string, bundle: string) => ( - - - Bundle identifier: -
- ${bundle} -
- `} - > - {name} - - -); - const getMaxProbability = (vulns: IVulnerability[]) => vulns.reduce( (max, { epss_probability }) => Math.max(max, epss_probability || 0), @@ -217,8 +203,10 @@ const generateTableHeaders = ( ); }, diff --git a/frontend/utilities/helpers.ts b/frontend/utilities/helpers.ts index 77d9b6dba7..5f2cb1987b 100644 --- a/frontend/utilities/helpers.ts +++ b/frontend/utilities/helpers.ts @@ -900,6 +900,16 @@ export const getNextLocationPath = ({ return queryString ? `/${nextLocation}?${queryString}` : `/${nextLocation}`; }; +export const getSoftwareBundleTooltipMarkup = (bundle: string) => { + return ` + + Bundle identifier: +
+ ${bundle} +
+ `; +}; + export default { addGravatarUrlToResource, formatConfigDataForServer,