From 73a368a2005b71af826f8be705a73a23f0a73f0a Mon Sep 17 00:00:00 2001
From: RachelElysia <71795832+RachelElysia@users.noreply.github.com>
Date: Thu, 27 Feb 2025 10:48:08 -0500
Subject: [PATCH] Fleet UI: Clean up TabNav and TargetChipSelector components
(#26256)
---
.../PlatformWrapper/PlatformWrapper.tsx | 9 +-
.../PlatformWrapper/_styles.scss | 2 +-
.../components/LiveQuery/SelectTargets.tsx | 47 +-----
.../TargetChipSelector.stories.tsx | 110 ++++++++++++++
.../TargetChipSelector.tests.tsx | 102 +++++++++++++
.../TargetChipSelector/TargetChipSelector.tsx | 55 +++++++
.../{ => TargetChipSelector}/_styles.scss | 4 +-
.../LiveQuery/TargetChipSelector/index.ts | 1 +
frontend/components/TabNav/TabNav.stories.tsx | 93 ++++++++++++
frontend/components/TabNav/TabNav.tests.tsx | 68 +++++++++
.../TabsWrapper.tsx => TabNav/TabNav.tsx} | 11 +-
frontend/components/TabNav/_styles.scss | 134 ++++++++++++++++++
frontend/components/TabNav/index.ts | 1 +
frontend/components/TabText/TabText.tsx | 47 ++++++
frontend/components/TabText/_styles.scss | 23 +++
frontend/components/TabText/index.ts | 1 +
frontend/components/TabsWrapper/_styles.scss | 90 ------------
frontend/components/TabsWrapper/index.ts | 1 -
frontend/components/icons/Check.tsx | 1 +
frontend/components/icons/Plus.tsx | 1 +
.../pages/DashboardPage/cards/MDM/MDM.tsx | 15 +-
.../DashboardPage/cards/MDM/_styles.scss | 2 +-
.../pages/DashboardPage/cards/Munki/Munki.tsx | 15 +-
.../DashboardPage/cards/Munki/_styles.scss | 2 +-
.../DashboardPage/cards/Software/Software.tsx | 15 +-
.../DashboardPage/cards/Software/_styles.scss | 2 +-
.../ManageControlsPage/ManageControlsPage.tsx | 9 +-
.../components/PlatformTabs/PlatformTabs.tsx | 17 ++-
.../SoftwareAddPage/SoftwareAddPage.tsx | 9 +-
frontend/pages/SoftwarePage/SoftwarePage.tsx | 9 +-
frontend/pages/SoftwarePage/_styles.scss | 2 +-
.../components/AddSoftwareModal/_styles.scss | 3 +-
frontend/pages/admin/AdminWrapper.tsx | 9 +-
.../TeamDetailsWrapper/TeamDetailsWrapper.tsx | 128 ++++++++---------
frontend/pages/admin/_styles.scss | 2 +-
.../details/DeviceUserPage/DeviceUserPage.tsx | 28 ++--
.../HostDetailsPage/HostDetailsPage.tsx | 26 ++--
.../details/HostDetailsPage/_styles.scss | 7 -
frontend/pages/hosts/details/_styles.scss | 13 +-
.../hosts/details/cards/Activity/Activity.tsx | 18 ++-
.../hosts/details/cards/Activity/_styles.scss | 15 --
.../SoftwareDetailsModal.tsx | 15 +-
.../labels/NewLabelPage/NewLabelPage.tsx | 9 +-
.../pages/labels/NewLabelPage/_styles.scss | 2 +-
.../PolicyResults/PolicyResults.tsx | 18 +--
.../components/QueryResults/QueryResults.tsx | 16 +--
46 files changed, 849 insertions(+), 358 deletions(-)
create mode 100644 frontend/components/LiveQuery/TargetChipSelector/TargetChipSelector.stories.tsx
create mode 100644 frontend/components/LiveQuery/TargetChipSelector/TargetChipSelector.tests.tsx
create mode 100644 frontend/components/LiveQuery/TargetChipSelector/TargetChipSelector.tsx
rename frontend/components/LiveQuery/{ => TargetChipSelector}/_styles.scss (92%)
create mode 100644 frontend/components/LiveQuery/TargetChipSelector/index.ts
create mode 100644 frontend/components/TabNav/TabNav.stories.tsx
create mode 100644 frontend/components/TabNav/TabNav.tests.tsx
rename frontend/components/{TabsWrapper/TabsWrapper.tsx => TabNav/TabNav.tsx} (65%)
create mode 100644 frontend/components/TabNav/_styles.scss
create mode 100644 frontend/components/TabNav/index.ts
create mode 100644 frontend/components/TabText/TabText.tsx
create mode 100644 frontend/components/TabText/_styles.scss
create mode 100644 frontend/components/TabText/index.ts
delete mode 100644 frontend/components/TabsWrapper/_styles.scss
delete mode 100644 frontend/components/TabsWrapper/index.ts
diff --git a/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx b/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx
index dbacef6bc8..e53ce0e068 100644
--- a/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx
+++ b/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx
@@ -13,10 +13,11 @@ import RevealButton from "components/buttons/RevealButton";
// @ts-ignore
import InputField from "components/forms/fields/InputField";
import TooltipWrapper from "components/TooltipWrapper";
-import TabsWrapper from "components/TabsWrapper";
+import TabNav from "components/TabNav";
import InfoBanner from "components/InfoBanner/InfoBanner";
import CustomLink from "components/CustomLink/CustomLink";
import Radio from "components/forms/fields/Radio";
+import TabText from "components/TabText";
import { isValidPemCertificate } from "../../../pages/hosts/ManageHostsPage/helpers";
import IosIpadosPanel from "./IosIpadosPanel";
@@ -573,7 +574,7 @@ const PlatformWrapper = ({
return (
-
+
setSelectedTabIndex(index)}
selectedIndex={selectedTabIndex}
@@ -584,7 +585,7 @@ const PlatformWrapper = ({
// so we add a hidden pseudo element with the same text string
return (
- {navItem.name}
+ {navItem.name}
);
})}
@@ -601,7 +602,7 @@ const PlatformWrapper = ({
);
})}
-
+
Done
diff --git a/frontend/components/AddHostsModal/PlatformWrapper/_styles.scss b/frontend/components/AddHostsModal/PlatformWrapper/_styles.scss
index 7ca7c255f2..e7b4474b4f 100644
--- a/frontend/components/AddHostsModal/PlatformWrapper/_styles.scss
+++ b/frontend/components/AddHostsModal/PlatformWrapper/_styles.scss
@@ -3,7 +3,7 @@
.platform-wrapper {
padding: 0; // different to pad sticky subnav properly
- .component__tabs-wrapper {
+ .tab-nav {
// negate problematic sticky positioning
position: initial;
.react-tabs {
diff --git a/frontend/components/LiveQuery/SelectTargets.tsx b/frontend/components/LiveQuery/SelectTargets.tsx
index 9afdccd0e5..ece1c0ac5a 100644
--- a/frontend/components/LiveQuery/SelectTargets.tsx
+++ b/frontend/components/LiveQuery/SelectTargets.tsx
@@ -25,29 +25,17 @@ import teamsAPI, { ILoadTeamsResponse } from "services/entities/teams";
import { formatSelectedTargetsForApi } from "utilities/helpers";
import { capitalize } from "lodash";
import permissions from "utilities/permissions";
-import {
- LABEL_DISPLAY_MAP,
- PlatformLabelNameFromAPI,
-} from "utilities/constants";
import PageError from "components/DataError";
import TargetsInput from "components/LiveQuery/TargetsInput";
import Button from "components/buttons/Button";
import Spinner from "components/Spinner";
import TooltipWrapper from "components/TooltipWrapper";
-import Icon from "components/Icon";
import SearchField from "components/forms/fields/SearchField";
import RevealButton from "components/buttons/RevealButton";
+import TargetPillSelector from "./TargetChipSelector";
import { generateTableHeaders } from "./TargetsInput/TargetsInputHostsTableConfig";
-interface ITargetPillSelectorProps {
- entity: ISelectLabel | ISelectTeam;
- isSelected: boolean;
- onClick: (
- value: ISelectLabel | ISelectTeam
- ) => React.MouseEventHandler;
-}
-
interface ISelectTargetsProps {
baseClass: string;
queryId?: number | null;
@@ -86,11 +74,6 @@ const STALE_TIME = 60000;
const SECTION_CHARACTER_LIMIT = 600;
const isLabel = (entity: ISelectTargetsEntity) => "label_type" in entity;
-const isBuiltInLabel = (
- entity: ISelectTargetsEntity
-): entity is ISelectLabel & { label_type: "builtin" } => {
- return "label_type" in entity && entity.label_type === "builtin";
-};
const isAllHosts = (entity: ISelectTargetsEntity) =>
"label_type" in entity &&
entity.name === "All Hosts" &&
@@ -125,34 +108,6 @@ const getTruncatedEntityCount = (
return index;
};
-const TargetPillSelector = ({
- entity,
- isSelected,
- onClick,
-}: ITargetPillSelectorProps): JSX.Element => {
- const displayText = (): string => {
- if (isBuiltInLabel(entity)) {
- const labelName = entity.name as PlatformLabelNameFromAPI;
- if (labelName in LABEL_DISPLAY_MAP) {
- return LABEL_DISPLAY_MAP[labelName] || labelName;
- }
- }
-
- return entity.name || "Missing display name";
- };
-
- return (
- onClick(entity)(e)}
- >
-
- {displayText()}
-
- );
-};
-
const SelectTargets = ({
baseClass,
queryId,
diff --git a/frontend/components/LiveQuery/TargetChipSelector/TargetChipSelector.stories.tsx b/frontend/components/LiveQuery/TargetChipSelector/TargetChipSelector.stories.tsx
new file mode 100644
index 0000000000..f5ca496d71
--- /dev/null
+++ b/frontend/components/LiveQuery/TargetChipSelector/TargetChipSelector.stories.tsx
@@ -0,0 +1,110 @@
+import React from "react";
+import { Meta, StoryObj } from "@storybook/react";
+import { ISelectLabel, ISelectTeam } from "interfaces/target";
+import TargetChipSelector from "./TargetChipSelector"; // Adjust the path if necessary
+
+const meta: Meta = {
+ component: TargetChipSelector,
+ title: "Components/TargetChipSelector",
+ argTypes: {
+ entity: {
+ description: "The label or team entity to display.",
+ control: { type: "object" },
+ },
+ isSelected: {
+ description:
+ "Whether the chip is currently selected, updated by parent onClick handler.",
+ control: { type: "boolean" },
+ },
+ onClick: {
+ description: "The handler to call when the chip is clicked.",
+ action: "clicked", // Use Storybook's action to track clicks
+ },
+ },
+ parameters: {
+ backgrounds: {
+ default: "light",
+ values: [
+ { name: "light", value: "#ffffff" },
+ { name: "dark", value: "#333333" },
+ ],
+ },
+ },
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+// Example data for labels and teams
+const mockLabel: ISelectLabel = {
+ id: 1,
+ name: "Example Label",
+ label_type: "regular",
+ description: "A test label",
+};
+
+const mockTeam: ISelectTeam = {
+ id: 2,
+ name: "Example Team",
+ description: "A test team",
+};
+
+export const LabelExample: Story = {
+ args: {
+ entity: mockLabel,
+ isSelected: false,
+ onClick: (value) => (event) => {
+ event.preventDefault();
+ console.log("Clicked label:", value);
+ },
+ },
+ render: (args) => (
+
+ ),
+};
+
+export const TeamExample: Story = {
+ args: {
+ entity: mockTeam,
+ isSelected: true,
+ onClick: (value) => (event) => {
+ event.preventDefault();
+ console.log("Clicked team:", value);
+ },
+ },
+ render: (args) => (
+
+ ),
+};
+
+export const BuiltInLabelExample: Story = {
+ args: {
+ entity: {
+ id: 3,
+ name: "MS Windows",
+ label_type: "builtin",
+ description: "Microsoft Windows hosts",
+ },
+ isSelected: false,
+ onClick: (value) => (event) => {
+ event.preventDefault();
+ console.log("Clicked label:", value);
+ },
+ },
+ render: (args) => (
+
+ ),
+};
diff --git a/frontend/components/LiveQuery/TargetChipSelector/TargetChipSelector.tests.tsx b/frontend/components/LiveQuery/TargetChipSelector/TargetChipSelector.tests.tsx
new file mode 100644
index 0000000000..6259fa6490
--- /dev/null
+++ b/frontend/components/LiveQuery/TargetChipSelector/TargetChipSelector.tests.tsx
@@ -0,0 +1,102 @@
+import React from "react";
+import { render, screen, fireEvent } from "@testing-library/react";
+
+import { ISelectLabel, ISelectTeam } from "interfaces/target";
+import TargetChipSelector from "./TargetChipSelector";
+
+describe("TargetChipSelector", () => {
+ const mockOnClick = jest.fn();
+
+ const mockLabel: ISelectLabel = {
+ id: 1,
+ name: "Example Label",
+ label_type: "regular",
+ description: "A test label",
+ };
+
+ const mockTeam: ISelectTeam = {
+ id: 2,
+ name: "Example Team",
+ description: "A test team",
+ };
+
+ it("renders the correct display text for a label", () => {
+ render(
+
+ );
+
+ expect(screen.getByText("Example Label")).toBeInTheDocument();
+ });
+
+ it("renders the correct display text for a team", () => {
+ render(
+
+ );
+
+ expect(screen.getByText("Example Team")).toBeInTheDocument();
+ });
+
+ it("renders the correct icon when selected", () => {
+ render(
+
+ );
+
+ expect(screen.getByLabelText("check")).toBeInTheDocument();
+ });
+
+ it("renders the correct icon when not selected", () => {
+ render(
+
+ );
+
+ expect(screen.getByLabelText("plus")).toBeInTheDocument();
+ });
+
+ it("calls the onClick handler with the correct entity when clicked", () => {
+ render(
+ (event) => mockOnClick(value, event)}
+ />
+ );
+
+ fireEvent.click(screen.getByRole("button"));
+
+ expect(mockOnClick).toHaveBeenCalledWith(mockLabel, expect.any(Object));
+ });
+
+ it("applies the correct data-selected attribute when selected", () => {
+ render(
+
+ );
+
+ const button = screen.getByRole("button");
+ expect(button).toHaveAttribute("data-selected", "true");
+ });
+
+ it("applies the correct data-selected attribute when not selected", () => {
+ render(
+
+ );
+
+ const button = screen.getByRole("button");
+ expect(button).toHaveAttribute("data-selected", "false");
+ });
+});
diff --git a/frontend/components/LiveQuery/TargetChipSelector/TargetChipSelector.tsx b/frontend/components/LiveQuery/TargetChipSelector/TargetChipSelector.tsx
new file mode 100644
index 0000000000..98ad2ccc1e
--- /dev/null
+++ b/frontend/components/LiveQuery/TargetChipSelector/TargetChipSelector.tsx
@@ -0,0 +1,55 @@
+import React from "react";
+import {
+ ISelectLabel,
+ ISelectTeam,
+ ISelectTargetsEntity,
+} from "interfaces/target";
+import Icon from "components/Icon";
+import {
+ PlatformLabelNameFromAPI,
+ LABEL_DISPLAY_MAP,
+} from "utilities/constants";
+
+interface ITargetChipSelectorProps {
+ entity: ISelectLabel | ISelectTeam;
+ isSelected: boolean;
+ onClick: (
+ value: ISelectLabel | ISelectTeam
+ ) => React.MouseEventHandler;
+}
+
+const isBuiltInLabel = (
+ entity: ISelectTargetsEntity
+): entity is ISelectLabel & { label_type: "builtin" } => {
+ return "label_type" in entity && entity.label_type === "builtin";
+};
+
+const TargetChipSelector = ({
+ entity,
+ isSelected,
+ onClick,
+}: ITargetChipSelectorProps): JSX.Element => {
+ const displayText = (): string => {
+ if (isBuiltInLabel(entity)) {
+ const labelName = entity.name as PlatformLabelNameFromAPI;
+ if (labelName in LABEL_DISPLAY_MAP) {
+ return LABEL_DISPLAY_MAP[labelName] || labelName;
+ }
+ }
+
+ return entity.name || "Missing display name";
+ };
+
+ return (
+ onClick(entity)(e)}
+ >
+
+ {displayText()}
+
+ );
+};
+
+export default TargetChipSelector;
diff --git a/frontend/components/LiveQuery/_styles.scss b/frontend/components/LiveQuery/TargetChipSelector/_styles.scss
similarity index 92%
rename from frontend/components/LiveQuery/_styles.scss
rename to frontend/components/LiveQuery/TargetChipSelector/_styles.scss
index e6e9ce0554..ca844e1354 100644
--- a/frontend/components/LiveQuery/_styles.scss
+++ b/frontend/components/LiveQuery/TargetChipSelector/_styles.scss
@@ -1,4 +1,4 @@
-.target-pill-selector {
+.target-chip-selector {
padding: $pad-small;
background-color: $core-white;
border: none;
@@ -34,7 +34,7 @@
}
&:hover {
- box-shadow: inset 0 0 0 1px $core-vibrant-blue-over;
+ background-color: $ui-vibrant-blue-10;
}
&:active {
diff --git a/frontend/components/LiveQuery/TargetChipSelector/index.ts b/frontend/components/LiveQuery/TargetChipSelector/index.ts
new file mode 100644
index 0000000000..94cc8dca7f
--- /dev/null
+++ b/frontend/components/LiveQuery/TargetChipSelector/index.ts
@@ -0,0 +1 @@
+export { default } from "./TargetChipSelector";
diff --git a/frontend/components/TabNav/TabNav.stories.tsx b/frontend/components/TabNav/TabNav.stories.tsx
new file mode 100644
index 0000000000..c86fc9ed64
--- /dev/null
+++ b/frontend/components/TabNav/TabNav.stories.tsx
@@ -0,0 +1,93 @@
+import React, { useState } from "react";
+import { Meta, StoryObj } from "@storybook/react";
+import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
+import TabText from "components/TabText";
+import TabNav from "./TabNav";
+
+const meta: Meta = {
+ component: TabNav,
+ title: "Components/TabNav",
+ parameters: {
+ backgrounds: {
+ default: "light",
+ values: [
+ {
+ name: "light",
+ value: "#ffffff",
+ },
+ {
+ name: "dark",
+ value: "#333333",
+ },
+ ],
+ },
+ },
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => {
+ const [selectedTabIndex, setSelectedTabIndex] = useState(0);
+
+ const platformSubNav = [
+ { name: Basic tab , type: "type1" },
+ { name: Basic tab 2 , type: "type2" },
+ {
+ name: Disabled tab ,
+ type: "type3",
+ disabled: true,
+ },
+ { name: Tab with count , type: "type4" },
+ {
+ name: (
+
+ Tab with error count
+
+ ),
+ type: "type5",
+ },
+ ];
+
+ const renderPanel = (type: string) => {
+ switch (type) {
+ case "type1":
+ return Content for Tab 1
;
+ case "type2":
+ return Content for Tab 2
;
+ case "type3":
+ return Content for Tab 3
;
+ case "type4":
+ return Content for Tab 4
;
+ case "type5":
+ return Content for Tab 5
;
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+ setSelectedTabIndex(index)}
+ selectedIndex={selectedTabIndex}
+ >
+
+ {platformSubNav.map((navItem) => (
+
+ {navItem.name}
+
+ ))}
+
+ {platformSubNav.map((navItem) => (
+
+ {renderPanel(navItem.type)}
+
+ ))}
+
+
+ );
+ },
+};
diff --git a/frontend/components/TabNav/TabNav.tests.tsx b/frontend/components/TabNav/TabNav.tests.tsx
new file mode 100644
index 0000000000..efff13cd9d
--- /dev/null
+++ b/frontend/components/TabNav/TabNav.tests.tsx
@@ -0,0 +1,68 @@
+import React from "react";
+import { render, screen, fireEvent } from "@testing-library/react";
+import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
+import TabText from "components/TabText";
+import TabNav from "./TabNav";
+
+describe("TabNav", () => {
+ it("renders tabs and panels correctly", () => {
+ render(
+
+
+
+
+ Tab 1
+
+
+ Tab 2
+
+
+
+ Content for Tab 1
+
+
+ Content for Tab 2
+
+
+
+ );
+
+ // Check if tabs are rendered
+ expect(screen.getByText("Tab 1")).toBeInTheDocument();
+ expect(screen.getByText("Tab 2")).toBeInTheDocument();
+
+ // Check if the first panel content is rendered by default
+ expect(screen.getByText("Content for Tab 1")).toBeInTheDocument();
+ expect(screen.queryByText("Content for Tab 2")).not.toBeInTheDocument();
+ });
+
+ it("switches tabs and displays the correct panel content", () => {
+ render(
+
+
+
+
+ Tab 1
+
+
+ Tab 2
+
+
+
+ Content for Tab 1
+
+
+ Content for Tab 2
+
+
+
+ );
+
+ // Switch to the second tab
+ fireEvent.click(screen.getByText("Tab 2"));
+
+ // Check if the second panel content is displayed
+ expect(screen.getByText("Content for Tab 2")).toBeInTheDocument();
+ expect(screen.queryByText("Content for Tab 1")).not.toBeInTheDocument();
+ });
+});
diff --git a/frontend/components/TabsWrapper/TabsWrapper.tsx b/frontend/components/TabNav/TabNav.tsx
similarity index 65%
rename from frontend/components/TabsWrapper/TabsWrapper.tsx
rename to frontend/components/TabNav/TabNav.tsx
index d788fb4d4e..6b9f59b049 100644
--- a/frontend/components/TabsWrapper/TabsWrapper.tsx
+++ b/frontend/components/TabNav/TabNav.tsx
@@ -1,7 +1,7 @@
import React from "react";
import classnames from "classnames";
-interface ITabsWrapperProps {
+interface ITabNavProps {
children: React.ReactChild | React.ReactChild[];
className?: string;
}
@@ -10,15 +10,12 @@ interface ITabsWrapperProps {
* This component exists so we can unify the styles
* and overwrite the loaded React Tabs styles.
*/
-const baseClass = "component__tabs-wrapper";
+const baseClass = "tab-nav";
-const TabsWrapper = ({
- children,
- className,
-}: ITabsWrapperProps): JSX.Element => {
+const TabNav = ({ children, className }: ITabNavProps): JSX.Element => {
const classNames = classnames(baseClass, className);
return {children}
;
};
-export default TabsWrapper;
+export default TabNav;
diff --git a/frontend/components/TabNav/_styles.scss b/frontend/components/TabNav/_styles.scss
new file mode 100644
index 0000000000..cb5d2a79b9
--- /dev/null
+++ b/frontend/components/TabNav/_styles.scss
@@ -0,0 +1,134 @@
+.tab-nav {
+ position: sticky;
+ top: 0;
+ background-color: $core-white;
+ z-index: 2;
+
+ .react-tabs {
+ &__tab-list {
+ display: inline-flex;
+ align-items: flex-start;
+ gap: $pad-xxlarge;
+ border-bottom: 1px solid $ui-fleet-black-10;
+ width: 100%;
+ height: 43px;
+ }
+ .tab-text {
+ display: flex; /* Ensure text and count are aligned horizontally */
+ align-items: center; /* Vertically align items */
+
+ .tab-text__text {
+ display: relative;
+
+ // Reserve space for bold text using a hidden pseudo-element
+ &::before {
+ content: attr(data-text); /* Same text as the visible one */
+ font-weight: bold; /* Mimic bold styling */
+ visibility: hidden; /* Keep it invisible */
+ position: absolute; /* Prevent it from affecting layout */
+ }
+ }
+ }
+
+ &__tab {
+ padding: 5px 0 $pad-medium;
+ font-size: $x-small;
+ border: none;
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+ line-height: 21px;
+
+ &:focus {
+ box-shadow: none;
+ outline: 0;
+ &:after {
+ left: 0;
+ bottom: 0;
+ }
+ }
+
+ // focus-visible only highlights when tabbing not clicking
+ &:focus-visible {
+ .tab-text {
+ border-radius: $border-radius;
+ // Outline used instead of border not to shift component
+ outline: 1px solid $ui-vibrant-blue-25;
+ outline-offset: -1px;
+ }
+ }
+
+ // // Bolding text when the button is active causes a layout shift
+ // // so we add a hidden pseudo element with the same text string
+ &:before {
+ content: attr(data-text);
+ height: 0;
+ visibility: hidden;
+ overflow: hidden;
+ user-select: none;
+ pointer-events: none;
+ font-weight: $bold;
+ }
+
+ &--selected {
+ font-weight: $bold;
+
+ &::after {
+ content: "";
+ width: 100%;
+ height: 0;
+ border-bottom: 2px solid $core-vibrant-blue;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ }
+ }
+
+ &:hover {
+ &::after {
+ content: "";
+ width: 100%;
+ height: 0;
+ border-bottom: 2px solid $core-vibrant-blue;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ }
+ }
+
+ &--disabled {
+ cursor: not-allowed;
+
+ &:hover {
+ &::after {
+ content: "";
+ width: 100%;
+ height: 0;
+ border-bottom: 0;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ }
+ }
+ }
+
+ &.no-count:not(.errors-empty).react-tabs__tab--selected::after {
+ bottom: -2px;
+ }
+ }
+ &__tab-panel {
+ .no-results-message {
+ margin-top: $pad-xxlarge;
+ font-size: $small;
+ font-weight: $bold;
+
+ span {
+ margin-top: $pad-medium;
+ font-size: $x-small;
+ font-weight: $regular;
+ display: block;
+ }
+ }
+ }
+ }
+}
diff --git a/frontend/components/TabNav/index.ts b/frontend/components/TabNav/index.ts
new file mode 100644
index 0000000000..bbb0fa5b23
--- /dev/null
+++ b/frontend/components/TabNav/index.ts
@@ -0,0 +1 @@
+export { default } from "./TabNav";
diff --git a/frontend/components/TabText/TabText.tsx b/frontend/components/TabText/TabText.tsx
new file mode 100644
index 0000000000..10ac42b2d5
--- /dev/null
+++ b/frontend/components/TabText/TabText.tsx
@@ -0,0 +1,47 @@
+import React from "react";
+import classnames from "classnames";
+
+interface ITabTextProps {
+ className?: string;
+ children: React.ReactNode;
+ count?: number;
+ /** Changes count badge from default purple to red */
+ isErrorCount?: boolean;
+}
+
+/*
+ * This component exists so we can unify the styles
+ * and add styles to react-tab text.
+ */
+const baseClass = "tab-text";
+
+const TabText = ({
+ className,
+ children,
+ count,
+ isErrorCount = false,
+}: ITabTextProps): JSX.Element => {
+ const classNames = classnames(baseClass, className);
+
+ const countClassNames = classnames(`${baseClass}__count`, {
+ [`${baseClass}__count--error`]: isErrorCount,
+ });
+
+ const renderCount = () => {
+ if (count && count > 0) {
+ return {count.toLocaleString()}
;
+ }
+ return undefined;
+ };
+
+ return (
+
+
+ {children}
+
+ {renderCount()}
+
+ );
+};
+
+export default TabText;
diff --git a/frontend/components/TabText/_styles.scss b/frontend/components/TabText/_styles.scss
new file mode 100644
index 0000000000..d28705f3ce
--- /dev/null
+++ b/frontend/components/TabText/_styles.scss
@@ -0,0 +1,23 @@
+.tab-text {
+ display: flex;
+ flex-direction: row;
+ gap: $pad-small;
+ align-items: center;
+ height: 21px;
+
+ &__count {
+ display: flex;
+ padding: 1px 12px;
+ justify-content: center;
+ align-items: center;
+ background-color: $core-vibrant-blue;
+ border-radius: 29px;
+ color: $core-white;
+ font-weight: $bold;
+ font-size: $xx-small;
+
+ &--error {
+ background-color: $core-vibrant-red;
+ }
+ }
+}
diff --git a/frontend/components/TabText/index.ts b/frontend/components/TabText/index.ts
new file mode 100644
index 0000000000..2f260c4d2c
--- /dev/null
+++ b/frontend/components/TabText/index.ts
@@ -0,0 +1 @@
+export { default } from "./TabText";
diff --git a/frontend/components/TabsWrapper/_styles.scss b/frontend/components/TabsWrapper/_styles.scss
deleted file mode 100644
index d4e8d08caa..0000000000
--- a/frontend/components/TabsWrapper/_styles.scss
+++ /dev/null
@@ -1,90 +0,0 @@
-.component__tabs-wrapper {
- position: sticky;
- top: 0;
- background-color: $core-white;
- z-index: 2;
-
- .react-tabs {
- &__tab-list {
- border-bottom: 1px solid $ui-gray;
- }
- &__tab {
- padding: $pad-small 0;
- margin-right: $pad-xxlarge;
- font-size: $x-small;
- border: none;
- display: inline-flex;
- flex-direction: column;
- align-items: center;
- line-height: 19px; // Fix shifty bold text
-
- &:focus {
- box-shadow: none;
- outline: 0;
- &:after {
- left: 0;
- bottom: 0;
- }
- }
-
- // focus-visible only highlights when tabbing not clicking
- &:focus-visible {
- background-color: $ui-vibrant-blue-10;
- }
-
- // Bolding text when the button is active causes a layout shift
- // so we add a hidden pseudo element with the same text string
- &:before {
- content: attr(data-text);
- height: 0;
- visibility: hidden;
- overflow: hidden;
- user-select: none;
- pointer-events: none;
- font-weight: $bold;
- }
- &--selected {
- font-weight: $bold;
-
- &::after {
- content: "";
- width: 100%;
- height: 0;
- border-bottom: 2px solid #6a67fe;
- position: absolute;
- bottom: 0;
- left: 0;
- }
- }
- &--disabled {
- cursor: not-allowed;
- }
- &.no-count:not(.errors-empty).react-tabs__tab--selected::after {
- bottom: -2px;
- }
- .count {
- margin-right: $pad-small;
- padding: $pad-xxsmall 12px;
- background-color: $core-vibrant-red;
- display: inline-block;
- border-radius: 29px;
- color: $core-white;
- font-weight: $bold;
- }
- }
- &__tab-panel {
- .no-results-message {
- margin-top: $pad-xxlarge;
- font-size: $small;
- font-weight: $bold;
-
- span {
- margin-top: $pad-medium;
- font-size: $x-small;
- font-weight: $regular;
- display: block;
- }
- }
- }
- }
-}
diff --git a/frontend/components/TabsWrapper/index.ts b/frontend/components/TabsWrapper/index.ts
deleted file mode 100644
index 980c53aaa0..0000000000
--- a/frontend/components/TabsWrapper/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from "./TabsWrapper";
diff --git a/frontend/components/icons/Check.tsx b/frontend/components/icons/Check.tsx
index f84a2ffd1f..106f6e00c3 100644
--- a/frontend/components/icons/Check.tsx
+++ b/frontend/components/icons/Check.tsx
@@ -13,6 +13,7 @@ const Check = ({ color = "core-fleet-blue" }: ICheckProps) => {
height="16"
fill="none"
viewBox="0 0 16 16"
+ aria-label="check"
>
{
fill="none"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
+ aria-label="plus"
>
)}
-
+
- Solutions
- Status
+
+ Solutions
+
+
+ Status
+
{error ? (
@@ -189,7 +194,7 @@ const Mdm = ({
)}
-
+
);
diff --git a/frontend/pages/DashboardPage/cards/MDM/_styles.scss b/frontend/pages/DashboardPage/cards/MDM/_styles.scss
index 29ba4c319c..e78a190404 100644
--- a/frontend/pages/DashboardPage/cards/MDM/_styles.scss
+++ b/frontend/pages/DashboardPage/cards/MDM/_styles.scss
@@ -3,7 +3,7 @@
position: relative;
height: 100%; // centers loading spinner
- .component__tabs-wrapper .table-container__header {
+ .tab-nav .table-container__header {
display: none;
}
diff --git a/frontend/pages/DashboardPage/cards/Munki/Munki.tsx b/frontend/pages/DashboardPage/cards/Munki/Munki.tsx
index 8b12f2d2a1..7ac7cf6a8a 100644
--- a/frontend/pages/DashboardPage/cards/Munki/Munki.tsx
+++ b/frontend/pages/DashboardPage/cards/Munki/Munki.tsx
@@ -6,7 +6,8 @@ import {
IMunkiVersionsAggregate,
} from "interfaces/macadmins";
-import TabsWrapper from "components/TabsWrapper";
+import TabNav from "components/TabNav";
+import TabText from "components/TabText";
import TableContainer from "components/TableContainer";
import Spinner from "components/Spinner";
import TableDataError from "components/DataError";
@@ -58,11 +59,15 @@ const Munki = ({
)}
-
+
{
return (
- {navItem.name}
+ {navItem.name}
);
})}
-
+
{React.cloneElement(children, {
teamIdForApi,
currentPage: page,
diff --git a/frontend/pages/ManageControlsPage/OSUpdates/components/PlatformTabs/PlatformTabs.tsx b/frontend/pages/ManageControlsPage/OSUpdates/components/PlatformTabs/PlatformTabs.tsx
index d69a9ec838..bc02c5800f 100644
--- a/frontend/pages/ManageControlsPage/OSUpdates/components/PlatformTabs/PlatformTabs.tsx
+++ b/frontend/pages/ManageControlsPage/OSUpdates/components/PlatformTabs/PlatformTabs.tsx
@@ -1,6 +1,7 @@
import React from "react";
import { Tab, TabList, TabPanel, Tabs } from "react-tabs";
-import TabsWrapper from "components/TabsWrapper";
+import TabNav from "components/TabNav";
+import TabText from "components/TabText";
import CustomLink from "components/CustomLink";
import { SUPPORT_LINK } from "utilities/constants";
@@ -62,27 +63,25 @@ const PlatformTabs = ({
return (
-
+
- {/* Bolding text when the tab is active causes a layout shift so
- we add a hidden pseudo element with the same text string */}
- macOS
+ macOS
{isWindowsMdmEnabled && (
- Windows
+ Windows
)}
- iOS
+ iOS
- iPadOS
+ iPadOS
{isAndroidMdmEnabled && (
@@ -149,7 +148,7 @@ const PlatformTabs = ({
)}
-
+
);
};
diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx
index ef9f244ab2..fa326a9a5d 100644
--- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx
+++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareAddPage.tsx
@@ -11,7 +11,8 @@ import { APP_CONTEXT_NO_TEAM_ID } from "interfaces/team";
import MainContent from "components/MainContent";
import BackLink from "components/BackLink";
-import TabsWrapper from "components/TabsWrapper";
+import TabNav from "components/TabNav";
+import TabText from "components/TabText";
import SidePanelContent from "components/SidePanelContent";
import QuerySidePanel from "components/side_panels/QuerySidePanel";
@@ -112,7 +113,7 @@ const SoftwareAddPage = ({
className={`${baseClass}__back-to-software`}
/>
Add software
-
+
{
return (
- {navItem.name}
+ {navItem.name}
);
})}
-
+
{React.cloneElement(children, {
router,
currentTeamId: parseInt(location.query.team_id, 10),
diff --git a/frontend/pages/SoftwarePage/SoftwarePage.tsx b/frontend/pages/SoftwarePage/SoftwarePage.tsx
index 21a8e18d55..0beceae5c3 100644
--- a/frontend/pages/SoftwarePage/SoftwarePage.tsx
+++ b/frontend/pages/SoftwarePage/SoftwarePage.tsx
@@ -31,7 +31,8 @@ import { getNextLocationPath } from "utilities/helpers";
import Button from "components/buttons/Button";
import MainContent from "components/MainContent";
import TeamsHeader from "components/TeamsHeader";
-import TabsWrapper from "components/TabsWrapper";
+import TabNav from "components/TabNav";
+import TabText from "components/TabText";
import ManageAutomationsModal from "./components/ManageSoftwareAutomationsModal";
import AddSoftwareModal from "./components/AddSoftwareModal";
@@ -412,7 +413,7 @@ const SoftwarePage = ({ children, router, location }: ISoftwarePageProps) => {
const renderBody = () => {
return (
-
+
{
{softwareSubNav.map((navItem) => {
return (
- {navItem.name}
+ {navItem.name}
);
})}
-
+
{React.cloneElement(children, {
router,
isSoftwareEnabled: Boolean(
diff --git a/frontend/pages/SoftwarePage/_styles.scss b/frontend/pages/SoftwarePage/_styles.scss
index fdbe0bea5d..5500c122f1 100644
--- a/frontend/pages/SoftwarePage/_styles.scss
+++ b/frontend/pages/SoftwarePage/_styles.scss
@@ -57,7 +57,7 @@
}
&__wrapper {
- .component__tabs-wrapper {
+ .tab-nav {
margin-bottom: $pad-xxlarge;
}
}
diff --git a/frontend/pages/SoftwarePage/components/AddSoftwareModal/_styles.scss b/frontend/pages/SoftwarePage/components/AddSoftwareModal/_styles.scss
index 706272708b..cd58cf7729 100644
--- a/frontend/pages/SoftwarePage/components/AddSoftwareModal/_styles.scss
+++ b/frontend/pages/SoftwarePage/components/AddSoftwareModal/_styles.scss
@@ -1,7 +1,6 @@
.add-software-modal {
-
// have to use this selector to override the default styles
- .component__tabs-wrapper {
+ .tab-nav {
margin-bottom: 0;
}
}
diff --git a/frontend/pages/admin/AdminWrapper.tsx b/frontend/pages/admin/AdminWrapper.tsx
index 9efa69ae67..98666ac002 100644
--- a/frontend/pages/admin/AdminWrapper.tsx
+++ b/frontend/pages/admin/AdminWrapper.tsx
@@ -4,8 +4,9 @@ import { InjectedRouter } from "react-router";
import PATHS from "router/paths";
import { AppContext } from "context/app";
-import TabsWrapper from "components/TabsWrapper";
+import TabNav from "components/TabNav";
import MainContent from "components/MainContent";
+import TabText from "components/TabText";
import classnames from "classnames";
interface ISettingSubNavItem {
@@ -77,7 +78,7 @@ const AdminWrapper = ({
return (
-
+
Settings
- {navItem.name}
+ {navItem.name}
);
})}
-
+
{children}
diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx
index 3a35856f60..eaf07701c0 100644
--- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx
+++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx
@@ -24,7 +24,8 @@ import sortUtils from "utilities/sort";
import ActionButtons from "components/buttons/ActionButtons/ActionButtons";
import Spinner from "components/Spinner";
-import TabsWrapper from "components/TabsWrapper";
+import TabNav from "components/TabNav";
+import TabText from "components/TabText";
import BackLink from "components/BackLink";
import TeamsDropdown from "components/TeamsDropdown";
import MainContent from "components/MainContent";
@@ -387,69 +388,66 @@ const TeamDetailsWrapper = ({
return (
<>
-
- {isGlobalAdmin ? (
-
-
-
- ) : (
- <>>
- )}
-
-
- {userTeams?.length === 1 ? (
-
{currentTeamDetails.name}
- ) : (
-
- )}
- {!!hostsTotalDisplay && (
-
- {hostsTotalDisplay}
-
- )}
-
-
+ {isGlobalAdmin ? (
+
+
+ ) : (
+ <>>
+ )}
+
+
+ {userTeams?.length === 1 ? (
+
{currentTeamDetails.name}
+ ) : (
+
+ )}
+ {!!hostsTotalDisplay && (
+
+ {hostsTotalDisplay}
+
+ )}
+
+
+
+
- {navItem.name}
+ {navItem.name}
);
})}
-
+
{showAddHostsModal && (
-
+
router.push(tabPaths[i])}
>
- Details
+
+ Details
+
{isPremiumTier && isSoftwareEnabled && hasSelfService && (
- Self-service
+
+ Self-service
+
+ )}
+ {isSoftwareEnabled && (
+
+ Software
+
)}
- {isSoftwareEnabled && Software }
{isPremiumTier && (
-
- {failingPoliciesCount > 0 && (
- {failingPoliciesCount}
- )}
+
Policies
-
+
)}
@@ -461,7 +467,7 @@ const DeviceUserPage = ({
)}
-
+
{showInfoModal &&
}
{showEnrollMdmModal &&
(host.dep_assigned_to_fleet ? (
diff --git a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx
index 8f72c023ec..dd5d715c00 100644
--- a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx
+++ b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx
@@ -45,7 +45,8 @@ import {
import { isAndroid, isIPadOrIPhone } from "interfaces/platform";
import Spinner from "components/Spinner";
-import TabsWrapper from "components/TabsWrapper";
+import TabNav from "components/TabNav";
+import TabText from "components/TabText";
import MainContent from "components/MainContent";
import BackLink from "components/BackLink";
import RunScriptDetailsModal from "pages/DashboardPage/cards/ActivityFeed/components/RunScriptDetailsModal";
@@ -124,6 +125,7 @@ interface IHostDetailsSubNavItem {
name: string | JSX.Element;
title: string;
pathname: string;
+ count?: number;
}
const DEFAULT_ACTIVITY_PAGE_SIZE = 8;
@@ -755,16 +757,10 @@ const HostDetailsPage = ({
pathname: PATHS.HOST_QUERIES(hostIdFromURL),
},
{
- name: (
- <>
- {failingPoliciesCount > 0 && (
-
{failingPoliciesCount}
- )}
- Policies
- >
- ),
+ name: "Policies",
title: "policies",
pathname: PATHS.HOST_POLICIES(hostIdFromURL),
+ count: failingPoliciesCount,
},
];
@@ -844,7 +840,7 @@ const HostDetailsPage = ({
)}
hostMdmDeviceStatus={hostMdmDeviceStatus}
/>
-
+
navigateToNav(i)}
@@ -853,7 +849,13 @@ const HostDetailsPage = ({
{hostDetailsSubNav.map((navItem) => {
// Bolding text when the tab is active causes a layout shift
// so we add a hidden pseudo element with the same text string
- return {navItem.name} ;
+ return (
+
+
+ {navItem.name}
+
+
+ );
})}
@@ -956,7 +958,7 @@ const HostDetailsPage = ({
/>
-
+
{showDeleteHostModal && (
setShowDeleteHostModal(false)}
diff --git a/frontend/pages/hosts/details/HostDetailsPage/_styles.scss b/frontend/pages/hosts/details/HostDetailsPage/_styles.scss
index 25f6e705f6..00621284c3 100644
--- a/frontend/pages/hosts/details/HostDetailsPage/_styles.scss
+++ b/frontend/pages/hosts/details/HostDetailsPage/_styles.scss
@@ -1,11 +1,4 @@
.host-details {
- &__tabs-wrapper {
- .react-tabs__tab {
- display: inline-flex;
- flex-direction: row;
- }
- }
-
// grid layout styles for the host details page
&__details-panel {
display: grid;
diff --git a/frontend/pages/hosts/details/_styles.scss b/frontend/pages/hosts/details/_styles.scss
index cd225a7577..d9efc254f5 100644
--- a/frontend/pages/hosts/details/_styles.scss
+++ b/frontend/pages/hosts/details/_styles.scss
@@ -71,24 +71,15 @@
}
}
- &__tabs-wrapper {
+ &__tab-nav {
background-color: $ui-off-white;
width: 100%;
// direct descendant of selector allows us to only change the first level of
// tab styling and not change the tabs inside the cards.
> .react-tabs > .react-tabs__tab-list {
- .react-tabs__tab {
- padding: 6px 0px 16px 0px;
- margin-right: $pad-xxlarge;
- }
.react-tabs__tab--selected {
background-color: $ui-off-white;
-
- // When tabbing through the app
- &:focus-visible {
- background-color: $ui-vibrant-blue-10;
- }
}
}
}
@@ -160,6 +151,6 @@
// we dont need the margin on the host details page as we are not using grid css
// for the spacing.
-.host-details__tabs-wrapper .card {
+.host-details__tab-nav .card {
margin-top: 0;
}
diff --git a/frontend/pages/hosts/details/cards/Activity/Activity.tsx b/frontend/pages/hosts/details/cards/Activity/Activity.tsx
index 58646d7661..97739410a1 100644
--- a/frontend/pages/hosts/details/cards/Activity/Activity.tsx
+++ b/frontend/pages/hosts/details/cards/Activity/Activity.tsx
@@ -8,7 +8,8 @@ import {
} from "services/entities/activities";
import Card from "components/Card";
-import TabsWrapper from "components/TabsWrapper";
+import TabNav from "components/TabNav";
+import TabText from "components/TabText";
import Spinner from "components/Spinner";
import TooltipWrapper from "components/TooltipWrapper";
import { ShowActivityDetailsHandler } from "components/ActivityItem/ActivityItem";
@@ -67,20 +68,17 @@ const Activity = ({
)}
Activity
-
+
- Past
- Upcoming
- {!!upcomingCount && (
-
- {upcomingCount}
-
- )}
+ Past
+
+
+ Upcoming
@@ -106,7 +104,7 @@ const Activity = ({
/>
-
+
);
};
diff --git a/frontend/pages/hosts/details/cards/Activity/_styles.scss b/frontend/pages/hosts/details/cards/Activity/_styles.scss
index 7fc58cd091..3aee164762 100644
--- a/frontend/pages/hosts/details/cards/Activity/_styles.scss
+++ b/frontend/pages/hosts/details/cards/Activity/_styles.scss
@@ -6,21 +6,6 @@
margin: 0 0 $pad-large;
}
- .react-tabs__tab-list {
- li:first-of-type {
- padding-bottom: 10px; // adds 2px to bottom padding of "Past" tab to adjust for the count badge on the "Upcoming" tab
- }
- }
-
- &__upcoming-count {
- padding: $pad-xxsmall $pad-xsmall;
- color: $core-white;
- background-color: $core-vibrant-blue;
- border-radius: $border-radius;
- font-weight: $bold;
- margin-left: $pad-small;
- }
-
&__loading-overlay {
height: 100%;
width: 100%;
diff --git a/frontend/pages/hosts/details/cards/Software/SoftwareDetailsModal/SoftwareDetailsModal.tsx b/frontend/pages/hosts/details/cards/Software/SoftwareDetailsModal/SoftwareDetailsModal.tsx
index 74c30fd026..078d7158b3 100644
--- a/frontend/pages/hosts/details/cards/Software/SoftwareDetailsModal/SoftwareDetailsModal.tsx
+++ b/frontend/pages/hosts/details/cards/Software/SoftwareDetailsModal/SoftwareDetailsModal.tsx
@@ -11,7 +11,8 @@ import {
} from "interfaces/software";
import Modal from "components/Modal";
-import TabsWrapper from "components/TabsWrapper";
+import TabNav from "components/TabNav";
+import TabText from "components/TabText";
import Button from "components/buttons/Button";
import DataSet from "components/DataSet";
import { dateAgo } from "utilities/date_format";
@@ -169,11 +170,15 @@ const TabsContent = ({
software: IHostSoftware;
}) => {
return (
-
+
- Software details
- Install details
+
+ Software details
+
+
+ Install details
+
@@ -185,7 +190,7 @@ const TabsContent = ({
/>
-
+
);
};
diff --git a/frontend/pages/labels/NewLabelPage/NewLabelPage.tsx b/frontend/pages/labels/NewLabelPage/NewLabelPage.tsx
index 1f18639c42..10ff28f75f 100644
--- a/frontend/pages/labels/NewLabelPage/NewLabelPage.tsx
+++ b/frontend/pages/labels/NewLabelPage/NewLabelPage.tsx
@@ -6,7 +6,8 @@ import useToggleSidePanel from "hooks/useToggleSidePanel";
import MainContent from "components/MainContent";
import SidePanelContent from "components/SidePanelContent";
-import TabsWrapper from "components/TabsWrapper";
+import TabNav from "components/TabNav";
+import TabText from "components/TabText";
import QuerySidePanel from "components/side_panels/QuerySidePanel";
import PATHS from "router/paths";
@@ -79,7 +80,7 @@ const NewLabelPage = ({ router, location, children }: INewLabelPageProps) => {
Dynamic (smart) labels are assigned to hosts if the query returns
results. Manual labels are assigned to selected hosts.
-
+
{
{labelSubNav.map((navItem) => {
return (
- {navItem.name}
+ {navItem.name}
);
})}
-
+
{React.cloneElement(children, {
showOpenSidebarButton,
onOpenSidebar,
diff --git a/frontend/pages/labels/NewLabelPage/_styles.scss b/frontend/pages/labels/NewLabelPage/_styles.scss
index 637d670a31..bb1592ba83 100644
--- a/frontend/pages/labels/NewLabelPage/_styles.scss
+++ b/frontend/pages/labels/NewLabelPage/_styles.scss
@@ -9,7 +9,7 @@
font-size: $xx-small;
}
- &__new-label-tabs-wrapper {
+ &__new-label-tab-nav {
margin-bottom: $pad-xxlarge;
}
}
diff --git a/frontend/pages/policies/PolicyPage/components/PolicyResults/PolicyResults.tsx b/frontend/pages/policies/PolicyPage/components/PolicyResults/PolicyResults.tsx
index b78ffbb171..07f2aa5c3a 100644
--- a/frontend/pages/policies/PolicyPage/components/PolicyResults/PolicyResults.tsx
+++ b/frontend/pages/policies/PolicyPage/components/PolicyResults/PolicyResults.tsx
@@ -15,7 +15,8 @@ import { ITarget } from "interfaces/target";
import Button from "components/buttons/Button";
import Icon from "components/Icon/Icon";
-import TabsWrapper from "components/TabsWrapper";
+import TabNav from "components/TabNav";
+import TabText from "components/TabText";
import InfoBanner from "components/InfoBanner";
import ShowQueryModal from "components/modals/ShowQueryModal";
import TooltipWrapper from "components/TooltipWrapper";
@@ -240,23 +241,22 @@ const PolicyResults = ({
onClickRunAgain={onRunQuery}
onClickStop={onStopQuery}
/>
-
+
setNavTabIndex(i)}>
- {NAV_TITLES.RESULTS}
+
+ {NAV_TITLES.RESULTS}
+
-
- {errors?.length > 0 && (
- {errors.length}
- )}
+
{NAV_TITLES.ERRORS}
-
+
{renderResultsTable()}
{renderErrorsTable()}
-
+
{showQueryModal && (
)}
-
+
setNavTabIndex(i)}>
{NAV_TITLES.RESULTS}
-
- {errors?.length > 0 && (
-
- {errors.length.toLocaleString()}
-
- )}
+
{NAV_TITLES.ERRORS}
-
+
{renderResultsTab()}
{renderErrorsTab()}
-
+
{showQueryModal && (