From 5f820febdc8aa67e4ad05d83c3304cc8281fd2c9 Mon Sep 17 00:00:00 2001 From: jacobshandling <61553566+jacobshandling@users.noreply.github.com> Date: Mon, 7 Jul 2025 08:29:09 -0700 Subject: [PATCH] UI: New side nav styles, abstractions (#30568) ## #16846 [Demo](https://drive.google.com/file/d/1xocZDfOUbu29tPpf2J6dngy3pLACIe62/view?usp=drivesdk) - [x] Changes file added for user-visible changes in `changes/` - [x] Manual QA for all new/changed functionality ## Summary by CodeRabbit * **New Features** * Added tooltips to navigation and category menu items for improved accessibility and clarity. * Introduced a new optional tooltip position setting, allowing tooltips to appear on any side of the element. * Expanded the color palette with a new light shade option. * **Style** * Refactored navigation and category menu styles to use centralized, reusable mixins for a more consistent appearance. * Updated navigation and category menu layouts for better structure and maintainability. * **Chores** * Added new SCSS mixins for navigation styling, improving code maintainability and consistency. --------- Co-authored-by: Jacob Shandling --- changes/16846-update-side-nav-styles | 1 + .../TooltipTruncatedText.tsx | 4 +- .../IntegrationPage.tests.tsx | 3 +- .../admin/components/SideNav/_styles.scss | 12 +---- .../components/SideNavItem/SideNavItem.tsx | 11 +++-- .../admin/components/SideNavItem/_styles.scss | 18 +------ .../CategoriesMenu/CategoriesMenu.tsx | 40 ++++++++------- .../SelfService/CategoriesMenu/_styles.scss | 29 +---------- .../cards/Software/SelfService/_styles.scss | 2 - frontend/styles/var/colors.scss | 1 + frontend/styles/var/colors.ts | 1 + frontend/styles/var/mixins.scss | 49 +++++++++++++++++++ 12 files changed, 89 insertions(+), 82 deletions(-) create mode 100644 changes/16846-update-side-nav-styles diff --git a/changes/16846-update-side-nav-styles b/changes/16846-update-side-nav-styles new file mode 100644 index 0000000000..164acae774 --- /dev/null +++ b/changes/16846-update-side-nav-styles @@ -0,0 +1 @@ +* Update side nave styles across the app diff --git a/frontend/components/TooltipTruncatedText/TooltipTruncatedText.tsx b/frontend/components/TooltipTruncatedText/TooltipTruncatedText.tsx index 90c6ea59e8..641060b75b 100644 --- a/frontend/components/TooltipTruncatedText/TooltipTruncatedText.tsx +++ b/frontend/components/TooltipTruncatedText/TooltipTruncatedText.tsx @@ -15,6 +15,7 @@ interface ITooltipTruncatedTextCellProps { * By default the tooltip text breaks on any character. Default: false */ tooltipBreakOnWord?: boolean; className?: string; + tooltipPosition?: "top" | "bottom" | "left" | "right"; } const baseClass = "tooltip-truncated-text"; @@ -24,6 +25,7 @@ const TooltipTruncatedText = ({ tooltip, tooltipBreakOnWord = false, className, + tooltipPosition = "top", }: ITooltipTruncatedTextCellProps): JSX.Element => { const classNames = classnames(baseClass, className, { "tooltip-break-on-word": tooltipBreakOnWord, @@ -42,7 +44,7 @@ const TooltipTruncatedText = ({ { // // ); + // sidenav label, sidenav tooltip, and card header // await waitForLoadingToFinish(container); // expect( // screen.getAllByText("Mobile device management (MDM)") - // ).toHaveLength(2); + // ).toHaveLength(3); // }); // }); describe("Conditional access", () => { diff --git a/frontend/pages/admin/components/SideNav/_styles.scss b/frontend/pages/admin/components/SideNav/_styles.scss index 1085f0e476..393d1280f2 100644 --- a/frontend/pages/admin/components/SideNav/_styles.scss +++ b/frontend/pages/admin/components/SideNav/_styles.scss @@ -4,17 +4,7 @@ } &__nav-list { - position: -webkit-sticky; - position: sticky; - // this is the spacing needed to make the sticky form nav position correctly when scrolling - // TODO: find a way to calculate these sticky positions this and use variables. - // will be tedious to update otherwise. - top: 217px; - width: 178px; - margin: 0; - padding: 0 110px 0 0; - list-style: none; - font-size: $x-small; + @include side-nav-list; } &__card-container { diff --git a/frontend/pages/admin/components/SideNavItem/SideNavItem.tsx b/frontend/pages/admin/components/SideNavItem/SideNavItem.tsx index 1db35c4a0f..ddc0988a21 100644 --- a/frontend/pages/admin/components/SideNavItem/SideNavItem.tsx +++ b/frontend/pages/admin/components/SideNavItem/SideNavItem.tsx @@ -1,6 +1,7 @@ import React from "react"; import { Link } from "react-router"; import classnames from "classnames"; +import TooltipTruncatedText from "components/TooltipTruncatedText"; interface ISideNavItemProps { title: string; @@ -11,14 +12,14 @@ interface ISideNavItemProps { const baseClass = "side-nav-item"; const SideNavItem = ({ title, path, isActive }: ISideNavItemProps) => { - const linkClassnames = classnames(`${baseClass}__nav-link`, { - "active-nav": isActive, + const wrapperClasses = classnames(baseClass, { + [`${baseClass}--active`]: isActive, }); return ( -
  • - - {title} +
  • + +
  • ); diff --git a/frontend/pages/admin/components/SideNavItem/_styles.scss b/frontend/pages/admin/components/SideNavItem/_styles.scss index ddbcc2fb0b..4c58bd033a 100644 --- a/frontend/pages/admin/components/SideNavItem/_styles.scss +++ b/frontend/pages/admin/components/SideNavItem/_styles.scss @@ -1,19 +1,3 @@ .side-nav-item { - margin-bottom: $pad-medium; - - a { - color: $core-fleet-black; - font-weight: $regular; - text-decoration: none; - cursor: pointer; - - &:hover { - color: $core-vibrant-blue; - } - - &.active-nav { - font-weight: $bold; - } - } - + @include side-nav-item; } diff --git a/frontend/pages/hosts/details/cards/Software/SelfService/CategoriesMenu/CategoriesMenu.tsx b/frontend/pages/hosts/details/cards/Software/SelfService/CategoriesMenu/CategoriesMenu.tsx index 31ee925a30..5350780913 100644 --- a/frontend/pages/hosts/details/cards/Software/SelfService/CategoriesMenu/CategoriesMenu.tsx +++ b/frontend/pages/hosts/details/cards/Software/SelfService/CategoriesMenu/CategoriesMenu.tsx @@ -2,6 +2,8 @@ import React from "react"; import classNames from "classnames"; import LinkWithContext from "components/LinkWithContext"; +import TooltipTruncatedText from "components/TooltipTruncatedText"; + import { parseHostSoftwareQueryParams } from "../../HostSoftware"; import { ICategory } from "../helpers"; @@ -21,35 +23,37 @@ const CategoriesMenu = ({ const wrapperClasses = classNames(baseClass, className); return ( -
    +
      {categories.map((cat: ICategory) => { const isActive = cat.id === queryParams.category_id || (cat.id === 0 && !queryParams.category_id); return ( - - {cat.label} - + + + + ); })} -
    + ); }; export default CategoriesMenu; diff --git a/frontend/pages/hosts/details/cards/Software/SelfService/CategoriesMenu/_styles.scss b/frontend/pages/hosts/details/cards/Software/SelfService/CategoriesMenu/_styles.scss index 9337f69dcb..385b2b75ad 100644 --- a/frontend/pages/hosts/details/cards/Software/SelfService/CategoriesMenu/_styles.scss +++ b/frontend/pages/hosts/details/cards/Software/SelfService/CategoriesMenu/_styles.scss @@ -1,31 +1,6 @@ .categories-menu { - display: flex; - flex-direction: column; - width: 232px; - - // Matches styling in DropdownWrapper > options + @include side-nav-list; &__category-link { - padding: 10px 8px; - font-size: $x-small; - font-weight: $regular; - border-radius: $border-radius; - color: $core-fleet-black; - - // current selection - &--active { - font-weight: $bold; - } - } - - // Mouse over/mouse click states - &:hover { - .categories-menu__category-link:hover { - background-color: $ui-vibrant-blue-10; - cursor: "pointer"; - } - - .categories-menu__category-link:active { - background-color: $ui-vibrant-blue-25; - } + @include side-nav-item; } } diff --git a/frontend/pages/hosts/details/cards/Software/SelfService/_styles.scss b/frontend/pages/hosts/details/cards/Software/SelfService/_styles.scss index cfe0c1c74d..d94c9c69bb 100644 --- a/frontend/pages/hosts/details/cards/Software/SelfService/_styles.scss +++ b/frontend/pages/hosts/details/cards/Software/SelfService/_styles.scss @@ -61,8 +61,6 @@ @media (min-width: $break-md) { .categories-menu { display: flex; - flex-direction: column; - width: 300px; } &__categories-dropdown { diff --git a/frontend/styles/var/colors.scss b/frontend/styles/var/colors.scss index 174f07e471..a5a03bc390 100644 --- a/frontend/styles/var/colors.scss +++ b/frontend/styles/var/colors.scss @@ -16,6 +16,7 @@ $ui-fleet-black-33: #b3b6c1; $ui-fleet-black-25: #c5c7d1; $ui-fleet-black-10: #e2e4ea; $ui-fleet-blue-10: #f9fafc; +$ui-fleet-black-5: #f4f4f6; $ui-dark-blue-gray: #afbec1; $ui-blue-gray: #dbe3e5; $ui-gray: #e3e3e3; diff --git a/frontend/styles/var/colors.ts b/frontend/styles/var/colors.ts index b4b11898fe..334e7804c9 100644 --- a/frontend/styles/var/colors.ts +++ b/frontend/styles/var/colors.ts @@ -14,6 +14,7 @@ export const COLORS = { "ui-fleet-black-33": "#B3B6C1", "ui-fleet-black-25": "#C5C7D1", "ui-fleet-black-10": "#E2E4EA", + "ui-fleet-black-5": "#F4F4F6", "ui-off-white": "#F9FAFC", "ui-blue-hover": "#5D5AE7", "ui-blue-pressed": "#4B4AB4", diff --git a/frontend/styles/var/mixins.scss b/frontend/styles/var/mixins.scss index 770701fdf9..1f63e48b19 100644 --- a/frontend/styles/var/mixins.scss +++ b/frontend/styles/var/mixins.scss @@ -401,3 +401,52 @@ $max-width: 2560px; transform: rotate(315deg); } } + +@mixin side-nav-list { + position: -webkit-sticky; + position: sticky; + // this is the spacing needed to make the sticky form nav position correctly when scrolling + // TODO: find a way to calculate these sticky positions this and use variables. + // will be tedious to update otherwise. + top: 217px; + width: 260px; + padding: 0 64px 0 0; + list-style: none; + font-size: $x-small; + display: flex; + flex-direction: column; + gap: $pad-small; + margin: 0; +} + +@mixin side-nav-item { + white-space: nowrap; + height: 32px; + border-radius: 4px; + + a { + display: flex; + align-items: center; + height: 100%; + padding: 0 16px; + color: $core-fleet-black; + font-weight: $regular; + text-decoration: none; + cursor: pointer; + + &:hover { + color: $core-vibrant-blue; + } + } + + &--active { + background-color: $ui-fleet-black-5; + a { + font-weight: $bold; + color: $ui-fleet-black-75; + * > .__react_component_tooltip { + font-weight: initial; + } + } + } +}