From 3a04df274677602fa19802d40c07590695e2ca49 Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Thu, 20 Mar 2025 09:20:30 -0400 Subject: [PATCH] Fleet UI: Create consistent padding and headers styling for HDP (#27302) --- .../CardHeader/CardHeader.stories.tsx | 18 ++++++++++ .../CardHeader/CardHeader.tests.tsx | 33 +++++++++++++++++++ frontend/components/CardHeader/CardHeader.tsx | 28 ++++++++++++++++ frontend/components/CardHeader/_styles.scss | 19 +++++++++++ frontend/components/CardHeader/index.ts | 1 + frontend/pages/hosts/details/_styles.scss | 6 ---- .../pages/hosts/details/cards/About/About.tsx | 9 ++--- .../hosts/details/cards/About/_styles.scss | 9 ++--- .../hosts/details/cards/Activity/Activity.tsx | 5 +-- .../hosts/details/cards/Activity/_styles.scss | 5 --- .../cards/AgentOptions/AgentOptions.tsx | 24 +++++++------- .../cards/Certificates/Certificates.tsx | 5 +-- .../details/cards/Certificates/_styles.scss | 7 ---- .../details/cards/HostSummary/HostSummary.tsx | 2 +- .../hosts/details/cards/Labels/Labels.tsx | 5 +-- .../hosts/details/cards/Labels/__styles.scss | 7 ---- .../details/cards/MunkiIssues/MunkiIssues.tsx | 8 ++--- .../pages/hosts/details/cards/Packs/Packs.tsx | 9 ++--- .../details/cards/Policies/HostPolicies.tsx | 9 ++--- .../details/cards/Queries/HostQueries.tsx | 9 ++--- .../details/cards/Software/HostSoftware.tsx | 17 +++++----- .../Software/SelfService/SelfService.tsx | 31 +++++++++-------- .../cards/Software/SelfService/_styles.scss | 10 ------ .../hosts/details/cards/Software/_styles.scss | 18 ---------- .../pages/hosts/details/cards/Users/Users.tsx | 17 +++++----- .../hosts/details/cards/Users/_styles.scss | 5 --- 26 files changed, 184 insertions(+), 132 deletions(-) create mode 100644 frontend/components/CardHeader/CardHeader.stories.tsx create mode 100644 frontend/components/CardHeader/CardHeader.tests.tsx create mode 100644 frontend/components/CardHeader/CardHeader.tsx create mode 100644 frontend/components/CardHeader/_styles.scss create mode 100644 frontend/components/CardHeader/index.ts delete mode 100644 frontend/pages/hosts/details/cards/Certificates/_styles.scss delete mode 100644 frontend/pages/hosts/details/cards/Labels/__styles.scss diff --git a/frontend/components/CardHeader/CardHeader.stories.tsx b/frontend/components/CardHeader/CardHeader.stories.tsx new file mode 100644 index 0000000000..c94acf8325 --- /dev/null +++ b/frontend/components/CardHeader/CardHeader.stories.tsx @@ -0,0 +1,18 @@ +import { Meta, StoryObj } from "@storybook/react"; + +import CardHeader from "."; + +const meta: Meta = { + component: CardHeader, + title: "Components/CardHeader", + args: { + header: "Card header", + subheader: "This is a card subtitle", + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/frontend/components/CardHeader/CardHeader.tests.tsx b/frontend/components/CardHeader/CardHeader.tests.tsx new file mode 100644 index 0000000000..1e082f65a9 --- /dev/null +++ b/frontend/components/CardHeader/CardHeader.tests.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import CardHeader from "./CardHeader"; + +describe("CardHeader", () => { + it("renders header text and subheader text when provided", () => { + const headerText = "Test Header"; + const subheaderText = "Test Subheader"; + render(); + + const header = screen.getByText(headerText); + expect(header).toBeInTheDocument(); + expect(header.tagName).toBe("H2"); + const subheader = screen.getByText(subheaderText); + expect(subheader).toBeInTheDocument(); + expect(subheader.tagName).toBe("P"); + }); + it("does not render subheader when not provided", () => { + const headerText = "Test Header"; + render(); + + const subheader = screen.queryByText(/subheader/i); + expect(subheader).not.toBeInTheDocument(); + }); + it("renders JSX elements for header and subheader", () => { + const headerJSX = Header JSX; + const subheaderJSX = Subheader JSX; + render(); + + expect(screen.getByTestId("header-jsx")).toBeInTheDocument(); + expect(screen.getByTestId("subheader-jsx")).toBeInTheDocument(); + }); +}); diff --git a/frontend/components/CardHeader/CardHeader.tsx b/frontend/components/CardHeader/CardHeader.tsx new file mode 100644 index 0000000000..8424696a6a --- /dev/null +++ b/frontend/components/CardHeader/CardHeader.tsx @@ -0,0 +1,28 @@ +// To be used within +import React from "react"; +import classnames from "classnames"; + +const baseClass = "card-header"; + +interface ICardHeaderProps { + header: JSX.Element | string; + subheader?: JSX.Element | string; + className?: string; +} + +/** + * A generic CardHeader component that will be used to render content within a CardHeader with a border and + * and selected background color. + */ +const CardHeader = ({ header, subheader, className }: ICardHeaderProps) => { + const classNames = classnames(baseClass, className); + + return ( +
+

{header}

+ {subheader &&

{subheader}

} +
+ ); +}; + +export default CardHeader; diff --git a/frontend/components/CardHeader/_styles.scss b/frontend/components/CardHeader/_styles.scss new file mode 100644 index 0000000000..f8e0b5c702 --- /dev/null +++ b/frontend/components/CardHeader/_styles.scss @@ -0,0 +1,19 @@ +.card-header { + margin: 0 0 $pad-large; + display: flex; + flex-direction: column; + gap: $pad-small; + + &__header { + font-size: $medium; + font-weight: $bold; + margin: 0; + } + + &__subheader { + color: $ui-fleet-black-75; + font-size: $x-small; + font-weight: $regular; + margin: 0; + } +} diff --git a/frontend/components/CardHeader/index.ts b/frontend/components/CardHeader/index.ts new file mode 100644 index 0000000000..f887ba6a27 --- /dev/null +++ b/frontend/components/CardHeader/index.ts @@ -0,0 +1 @@ +export { default } from "./CardHeader"; diff --git a/frontend/pages/hosts/details/_styles.scss b/frontend/pages/hosts/details/_styles.scss index d9efc254f5..37731c8423 100644 --- a/frontend/pages/hosts/details/_styles.scss +++ b/frontend/pages/hosts/details/_styles.scss @@ -15,12 +15,6 @@ flex-direction: column; } .card { - &__header { - font-size: $medium; - font-weight: $bold; - margin: 0 0 $pad-medium 0; - } - .list { list-style: none; padding: 0; diff --git a/frontend/pages/hosts/details/cards/About/About.tsx b/frontend/pages/hosts/details/cards/About/About.tsx index 3197942b9c..921de08cb5 100644 --- a/frontend/pages/hosts/details/cards/About/About.tsx +++ b/frontend/pages/hosts/details/cards/About/About.tsx @@ -18,6 +18,7 @@ import { BATTERY_TOOLTIP, } from "utilities/constants"; import DataSet from "components/DataSet"; +import CardHeader from "components/CardHeader"; const getDeviceUserTipContent = (deviceMapping: IDeviceUser[]) => { if (deviceMapping.length === 0) { @@ -225,12 +226,12 @@ const About = ({ return ( -

About

+
{isLoading && ( @@ -67,7 +68,7 @@ const Activity = ({
)} -

Activity

+ - {isChromeOS ? ( - - Agent options - - ) : ( -

Agent options

- )} + + Agent options + + ) : ( + "Agent options" + ) + } + />
diff --git a/frontend/pages/hosts/details/cards/Certificates/Certificates.tsx b/frontend/pages/hosts/details/cards/Certificates/Certificates.tsx index d733fbb052..4ae509854f 100644 --- a/frontend/pages/hosts/details/cards/Certificates/Certificates.tsx +++ b/frontend/pages/hosts/details/cards/Certificates/Certificates.tsx @@ -7,6 +7,7 @@ import { IListSort } from "interfaces/list_options"; import { HostPlatform } from "interfaces/platform"; import Card from "components/Card"; +import CardHeader from "components/CardHeader"; import DataError from "components/DataError"; import CertificatesTable from "./CertificatesTable"; @@ -65,10 +66,10 @@ const CertificatesCard = ({ -

Certificates

+ {renderContent()}
); diff --git a/frontend/pages/hosts/details/cards/Certificates/_styles.scss b/frontend/pages/hosts/details/cards/Certificates/_styles.scss deleted file mode 100644 index 87c29e23d6..0000000000 --- a/frontend/pages/hosts/details/cards/Certificates/_styles.scss +++ /dev/null @@ -1,7 +0,0 @@ -.certificates-card { - h2 { - font-size: $medium; - margin: 0 0 $pad-large; - } - -} diff --git a/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tsx b/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tsx index 3ff3fa7d32..c6fcc20d55 100644 --- a/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tsx +++ b/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tsx @@ -501,8 +501,8 @@ const HostSummary = ({ return ( {!isIosOrIpadosHost && !isAndroidHost && ( diff --git a/frontend/pages/hosts/details/cards/Labels/Labels.tsx b/frontend/pages/hosts/details/cards/Labels/Labels.tsx index efb33dfd19..2b2ce018d4 100644 --- a/frontend/pages/hosts/details/cards/Labels/Labels.tsx +++ b/frontend/pages/hosts/details/cards/Labels/Labels.tsx @@ -5,6 +5,7 @@ import { ILabel } from "interfaces/label"; import classnames from "classnames"; import Card from "components/Card"; +import CardHeader from "components/CardHeader"; import { LABEL_DISPLAY_MAP } from "utilities/constants"; const baseClass = "labels-card"; @@ -36,11 +37,11 @@ const Labels = ({ onLabelClick, labels }: ILabelsProps): JSX.Element => { return ( -

Labels

+ {labels.length === 0 ? (

No labels are associated with this host. diff --git a/frontend/pages/hosts/details/cards/Labels/__styles.scss b/frontend/pages/hosts/details/cards/Labels/__styles.scss deleted file mode 100644 index f70ba0f9a9..0000000000 --- a/frontend/pages/hosts/details/cards/Labels/__styles.scss +++ /dev/null @@ -1,7 +0,0 @@ -.labels-card { - - h2 { - font-size: $medium; - margin: 0 0 $pad-large; - } -} diff --git a/frontend/pages/hosts/details/cards/MunkiIssues/MunkiIssues.tsx b/frontend/pages/hosts/details/cards/MunkiIssues/MunkiIssues.tsx index 3ceec28fee..8711cfe44d 100644 --- a/frontend/pages/hosts/details/cards/MunkiIssues/MunkiIssues.tsx +++ b/frontend/pages/hosts/details/cards/MunkiIssues/MunkiIssues.tsx @@ -5,6 +5,7 @@ import { IMunkiIssue } from "interfaces/host"; import TableContainer from "components/TableContainer"; import EmptyTable from "components/EmptyTable"; import Card from "components/Card"; +import CardHeader from "components/CardHeader"; import { munkiIssuesTableHeaders } from "./MunkiIssuesTableConfig"; @@ -26,13 +27,12 @@ const MunkiIssuesTable = ({ return ( -

Munki issues

- + {munkiIssues?.length ? (
{ <> ) : ( -

Packs

+ {packsAccordion} diff --git a/frontend/pages/hosts/details/cards/Policies/HostPolicies.tsx b/frontend/pages/hosts/details/cards/Policies/HostPolicies.tsx index ec6e3fa0bb..8bd4089dbe 100644 --- a/frontend/pages/hosts/details/cards/Policies/HostPolicies.tsx +++ b/frontend/pages/hosts/details/cards/Policies/HostPolicies.tsx @@ -11,6 +11,7 @@ import { getPathWithQueryParams } from "utilities/url"; import TableContainer from "components/TableContainer"; import EmptyTable from "components/EmptyTable"; import Card from "components/Card"; +import CardHeader from "components/CardHeader"; import CustomLink from "components/CustomLink"; import { @@ -152,12 +153,12 @@ const Policies = ({ return ( -

Policies

+ {renderHostPolicies()}
); diff --git a/frontend/pages/hosts/details/cards/Queries/HostQueries.tsx b/frontend/pages/hosts/details/cards/Queries/HostQueries.tsx index dfad71ea6d..6f5fe86a9e 100644 --- a/frontend/pages/hosts/details/cards/Queries/HostQueries.tsx +++ b/frontend/pages/hosts/details/cards/Queries/HostQueries.tsx @@ -7,6 +7,7 @@ import TableContainer from "components/TableContainer"; import EmptyTable from "components/EmptyTable"; import CustomLink from "components/CustomLink"; import Card from "components/Card"; +import CardHeader from "components/CardHeader"; import PATHS from "router/paths"; import { InjectedRouter } from "react-router"; import { Row } from "react-table"; @@ -156,12 +157,12 @@ const HostQueries = ({ return ( -

Queries

+ {renderHostQueries()}
); diff --git a/frontend/pages/hosts/details/cards/Software/HostSoftware.tsx b/frontend/pages/hosts/details/cards/Software/HostSoftware.tsx index e91f103525..91c62bea80 100644 --- a/frontend/pages/hosts/details/cards/Software/HostSoftware.tsx +++ b/frontend/pages/hosts/details/cards/Software/HostSoftware.tsx @@ -22,6 +22,7 @@ import { NotificationContext } from "context/notification"; import { AppContext } from "context/app"; import Card from "components/Card/Card"; +import CardHeader from "components/CardHeader"; import DataError from "components/DataError"; import Spinner from "components/Spinner"; import SoftwareFiltersModal from "pages/SoftwarePage/components/SoftwareFiltersModal"; @@ -415,17 +416,17 @@ const HostSoftware = ({ return ( -
Software
- {isMyDevicePage && ( -
- Software installed on your device. -
- )} + {renderHostSoftware()}
); diff --git a/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tsx b/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tsx index b872197a41..d4fcb8fcf1 100644 --- a/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tsx +++ b/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tsx @@ -12,6 +12,7 @@ import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants"; import { pluralize } from "utilities/strings/stringUtils"; import Card from "components/Card"; +import CardHeader from "components/CardHeader"; import CustomLink from "components/CustomLink"; import DataError from "components/DataError"; import EmptyTable from "components/EmptyTable"; @@ -88,21 +89,25 @@ const SoftwareSelfService = ({ return ( -
Self-service
-
- Install organization-approved apps provided by your IT department.{" "} - {contactUrl && ( - - If you need help,{" "} - - - )} -
+ + Install organization-approved apps provided by your IT department.{" "} + {contactUrl && ( + + If you need help,{" "} + + + )} + + } + /> {isLoading ? ( ) : ( diff --git a/frontend/pages/hosts/details/cards/Software/SelfService/_styles.scss b/frontend/pages/hosts/details/cards/Software/SelfService/_styles.scss index 484312533c..f2003a039a 100644 --- a/frontend/pages/hosts/details/cards/Software/SelfService/_styles.scss +++ b/frontend/pages/hosts/details/cards/Software/SelfService/_styles.scss @@ -1,14 +1,4 @@ .software-self-service { - &__card-header { - margin: 0 0 8px 0; - } - - &__card-subheader { - margin: 0 0 24px 0; - color: $ui-fleet-black-75; - font-size: $x-small; - } - // TODO: empty table styling differs slightly from figma (font size, color, spacing), why?g .empty-table__container { margin: 64px 0; diff --git a/frontend/pages/hosts/details/cards/Software/_styles.scss b/frontend/pages/hosts/details/cards/Software/_styles.scss index 00a31b6c7a..3f59473f54 100644 --- a/frontend/pages/hosts/details/cards/Software/_styles.scss +++ b/frontend/pages/hosts/details/cards/Software/_styles.scss @@ -1,9 +1,4 @@ .software-card { - .card-header { - font-weight: $bold; - margin: 0 0 $pad-large 0; - } - .table-container { .stackable-header { min-width: initial; // Fix card overflow on low widths @@ -111,16 +106,3 @@ } } } - -.device-software { - .card-header { - font-weight: $regular; - margin: 0 0 $pad-small 0; - } - - .card-subheader { - margin: 0 0 $pad-large 0; - color: $ui-fleet-black-75; - font-size: $x-small; - } -} diff --git a/frontend/pages/hosts/details/cards/Users/Users.tsx b/frontend/pages/hosts/details/cards/Users/Users.tsx index 6fe749eef7..358e5e2494 100644 --- a/frontend/pages/hosts/details/cards/Users/Users.tsx +++ b/frontend/pages/hosts/details/cards/Users/Users.tsx @@ -7,6 +7,7 @@ import TableCount from "components/TableContainer/TableCount"; import EmptyTable from "components/EmptyTable"; import CustomLink from "components/CustomLink"; import Card from "components/Card"; +import CardHeader from "components/CardHeader"; import generateUsersTableHeaders from "./UsersTable/UsersTableConfig"; @@ -36,12 +37,12 @@ const Users = ({ if (!hostUsersEnabled) { return ( -

Users

+ <> -

Users

+ {users?.length ? (