diff --git a/changes/13205-table-links-can-open-in-new-tab b/changes/13205-table-links-can-open-in-new-tab new file mode 100644 index 0000000000..b971b1f7fd --- /dev/null +++ b/changes/13205-table-links-can-open-in-new-tab @@ -0,0 +1 @@ +* All table links are right-clickable \ No newline at end of file diff --git a/frontend/components/LiveQuery/TargetsInput/_styles.scss b/frontend/components/LiveQuery/TargetsInput/_styles.scss index 94eabdd805..e2f14ce6e1 100644 --- a/frontend/components/LiveQuery/TargetsInput/_styles.scss +++ b/frontend/components/LiveQuery/TargetsInput/_styles.scss @@ -74,12 +74,6 @@ &__hosts-selected-table { margin-top: 8px; - img { - cursor: pointer; - transform: scale(0.5); - position: relative; - top: 2px; - } .data-table__wrapper { width: 100%; overflow: auto; diff --git a/frontend/components/TableContainer/DataTable/LinkCell/LinkCell.tests.tsx b/frontend/components/TableContainer/DataTable/LinkCell/LinkCell.tests.tsx index 64e578e41d..de5332e35e 100644 --- a/frontend/components/TableContainer/DataTable/LinkCell/LinkCell.tests.tsx +++ b/frontend/components/TableContainer/DataTable/LinkCell/LinkCell.tests.tsx @@ -7,13 +7,12 @@ import LinkCell from "./LinkCell"; const VALUE = "40 hosts"; describe("Link cell", () => { - it("renders text and path", async () => { + it("renders text", async () => { const { user } = renderWithSetup( ); - await user.click(screen.getByText("40 hosts")); - - expect(window.location.pathname).toContain("/hosts"); + expect(screen.getByText("40 hosts")).toBeInTheDocument(); + // Note: Testing react-router Link would require Router or MemoryRouter wrapper which is app level }); }); diff --git a/frontend/components/TableContainer/DataTable/LinkCell/LinkCell.tsx b/frontend/components/TableContainer/DataTable/LinkCell/LinkCell.tsx index 5f23fbcc4f..3f8b8dd690 100644 --- a/frontend/components/TableContainer/DataTable/LinkCell/LinkCell.tsx +++ b/frontend/components/TableContainer/DataTable/LinkCell/LinkCell.tsx @@ -1,40 +1,43 @@ +// Utilizes Link over Button so we can right click links import React from "react"; -// using browserHistory directly because "router" -// is difficult to pass as a prop -import { browserHistory } from "react-router"; - -import Button from "components/buttons/Button/Button"; +import { Link } from "react-router"; +import classnames from "classnames"; interface ILinkCellProps { value: string | JSX.Element; path: string; + className?: string; + customOnClick?: (e: React.MouseEvent) => void; + /** allows viewing overflow for tooltip */ + withTooltip?: boolean; title?: string; - classes?: string; - customOnClick?: () => void; } +const baseClass = "link-cell"; + const LinkCell = ({ value, path, - title, - classes, + className, customOnClick, + withTooltip, + title, }: ILinkCellProps): JSX.Element => { - const onClick = (): void => { - customOnClick && customOnClick(); - browserHistory.push(path); + const cellClasses = classnames( + baseClass, + className, + withTooltip && "link-cell-tooltip" + ); + + const onClick = (e: React.MouseEvent): void => { + customOnClick && customOnClick(e); }; return ( - + ); }; diff --git a/frontend/components/TableContainer/DataTable/_styles.scss b/frontend/components/TableContainer/DataTable/_styles.scss index ca0cbd39f6..f38c51b54d 100644 --- a/frontend/components/TableContainer/DataTable/_styles.scss +++ b/frontend/components/TableContainer/DataTable/_styles.scss @@ -215,7 +215,6 @@ $shadow-transition-width: 10px; } .link-cell, .text-cell { - display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; @@ -224,6 +223,42 @@ $shadow-transition-width: 10px; white-space: normal; } } + .text-cell { + display: inline-flex; + } + .link-cell { + display: block; + padding: $pad-small 0; // larger clickable area + + &:hover { + text-decoration: underline; + } + > div { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } + // css to properly style link-cell with tooltip + .link-cell-tooltip { + overflow: visible; // fixes tooltip overflow cut off by cell + .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 { + display: block; + white-space: nowrap; // single line + text-overflow: ellipsis; // truncates text + overflow: hidden; + + .component__tooltip-wrapper__underline { + max-width: 100%; // fixes underline overflowing past truncated text + } + } + } + } .w400 { max-width: calc(400px - 48px); min-width: 100%; diff --git a/frontend/pages/hosts/ManageHostsPage/components/FilterPill/_styles.scss b/frontend/pages/hosts/ManageHostsPage/components/FilterPill/_styles.scss index 45a308e5dd..f82d327308 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/FilterPill/_styles.scss +++ b/frontend/pages/hosts/ManageHostsPage/components/FilterPill/_styles.scss @@ -32,7 +32,6 @@ .premium-icon-tip { .premium-feature-icon { position: relative; - top: 2px; margin-right: 6px; } } diff --git a/frontend/pages/hosts/details/cards/Software/SoftwareTableConfig.tsx b/frontend/pages/hosts/details/cards/Software/SoftwareTableConfig.tsx index 0685071930..2b9763d685 100644 --- a/frontend/pages/hosts/details/cards/Software/SoftwareTableConfig.tsx +++ b/frontend/pages/hosts/details/cards/Software/SoftwareTableConfig.tsx @@ -7,9 +7,9 @@ import { formatDistanceToNow } from "date-fns"; import { ISoftware } from "interfaces/software"; import PATHS from "router/paths"; -import Button from "components/buttons/Button"; import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell"; import TextCell from "components/TableContainer/DataTable/TextCell"; +import LinkCell from "components/TableContainer/DataTable/LinkCell"; import TooltipWrapper from "components/TooltipWrapper"; import ViewAllHostsLink from "components/ViewAllHostsLink"; import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants"; @@ -215,9 +215,12 @@ export const generateSoftwareTableHeaders = ({ }; return ( - + ); }, sortType: "caseInsensitive", diff --git a/frontend/pages/policies/ManagePoliciesPage/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/_styles.scss index 3f7fb04346..345b183d97 100644 --- a/frontend/pages/policies/ManagePoliciesPage/_styles.scss +++ b/frontend/pages/policies/ManagePoliciesPage/_styles.scss @@ -157,19 +157,25 @@ position: relative; } - .policy-name-cell { - .children-wrapper { - display: flex; - align-items: center; - gap: 2px; + .policies-table { + .data-table-block { + .data-table { + tbody { + .name__cell { + .policy-name-cell { + display: flex; // required for inline icon - .tooltip-base { - display: inline-flex; - } + .tooltip-base { + display: inline-flex; + } - .policy-name-text { - text-overflow: ellipsis; - overflow: hidden; + .policy-name-text { + text-overflow: ellipsis; + overflow: hidden; + } + } + } + } } } } diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx index 825f00db6f..2d3abf32bc 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx @@ -117,7 +117,7 @@ const generateTableHeaders = ( accessor: "name", Cell: (cellProps: ICellProps): JSX.Element => (
{cellProps.cell.value}
diff --git a/frontend/pages/queries/ManageQueriesPage/_styles.scss b/frontend/pages/queries/ManageQueriesPage/_styles.scss index 52220fc54f..fb26c7918c 100644 --- a/frontend/pages/queries/ManageQueriesPage/_styles.scss +++ b/frontend/pages/queries/ManageQueriesPage/_styles.scss @@ -135,13 +135,25 @@ .name__cell { max-width: $col-lg; + .query-name-cell { + display: flex; // required for inline icon + .children-wrapper { + .query-name-text { + text-overflow: ellipsis; + overflow: hidden; + } + } + } + .query-icon { + display: block; + } + .children-wrapper { display: flex; gap: $pad-xsmall; - - .observer-can-run-tooltip { - font-weight: $regular; - } + } + .observer-can-run-tooltip { + font-weight: $regular; } } @@ -177,19 +189,5 @@ } } } - - .query-name-cell { - .children-wrapper { - .query-name-text { - text-overflow: ellipsis; - overflow: hidden; - } - } - } - .query-icon { - position: relative; - top: 2px; - display: block; - } } } diff --git a/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx b/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx index 744a829bc6..f24c17af24 100644 --- a/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx +++ b/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx @@ -125,7 +125,7 @@ const generateTableHeaders = ({ Cell: (cellProps: ICellProps): JSX.Element => { return (
{cellProps.cell.value}
diff --git a/frontend/pages/software/ManageSoftwarePage/SoftwareTableConfig.tsx b/frontend/pages/software/ManageSoftwarePage/SoftwareTableConfig.tsx index 7da6142dff..78490e4ecf 100644 --- a/frontend/pages/software/ManageSoftwarePage/SoftwareTableConfig.tsx +++ b/frontend/pages/software/ManageSoftwarePage/SoftwareTableConfig.tsx @@ -9,9 +9,9 @@ import PATHS from "router/paths"; import { formatFloatAsPercentage } from "utilities/helpers"; import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants"; -import Button from "components/buttons/Button"; import HeaderCell from "components/TableContainer/DataTable/HeaderCell"; import TextCell from "components/TableContainer/DataTable/TextCell"; +import LinkCell from "components/TableContainer/DataTable/LinkCell/LinkCell"; import TooltipWrapper from "components/TooltipWrapper"; import ViewAllHostsLink from "components/ViewAllHostsLink"; import PremiumFeatureIconWithTooltip from "components/PremiumFeatureIconWithTooltip"; @@ -214,9 +214,12 @@ const generateTableHeaders = ( }; return ( - + ); }, sortType: "caseInsensitive", diff --git a/frontend/pages/software/ManageSoftwarePage/_styles.scss b/frontend/pages/software/ManageSoftwarePage/_styles.scss index 372af5a942..daf3b78ce2 100644 --- a/frontend/pages/software/ManageSoftwarePage/_styles.scss +++ b/frontend/pages/software/ManageSoftwarePage/_styles.scss @@ -143,12 +143,10 @@ display: table-cell; } } - @media (min-width: $break-md) { + @media (min-width: $break-lg) { .version__header { width: $col-md; } - } - @media (min-width: $break-lg) { .source__header { display: table-cell; } @@ -157,8 +155,7 @@ tbody { .name__cell { - width: $col-md; - + max-width: $col-md; // Tooltip does not get cut off .children-wrapper { overflow: initial; @@ -193,9 +190,9 @@ } } } - @media (min-width: $break-md) { - .version_cell { - width: $col-md; + @media (min-width: $break-sm) { + .name__cell { + max-width: $col-lg; } } @media (min-width: $break-md) { @@ -204,6 +201,9 @@ } } @media (min-width: $break-lg) { + .version_cell { + width: $col-md; + } .source__cell { display: table-cell; }