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 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## 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. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
This commit is contained in:
co-authored by
Jacob Shandling
parent
dbd6a23053
commit
5f820febdc
@@ -0,0 +1 @@
|
||||
* Update side nave styles across the app
|
||||
@@ -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 = ({
|
||||
</div>
|
||||
</div>
|
||||
<ReactTooltip
|
||||
place="top"
|
||||
place={tooltipPosition}
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id={tooltipId}
|
||||
|
||||
@@ -38,11 +38,12 @@ describe("Integrations Page", () => {
|
||||
// <IntegrationsPage router={mockRouter} params={{ section: "mdm" }} />
|
||||
// );
|
||||
|
||||
// sidenav label, sidenav tooltip, and card header
|
||||
// await waitForLoadingToFinish(container);
|
||||
|
||||
// expect(
|
||||
// screen.getAllByText("Mobile device management (MDM)")
|
||||
// ).toHaveLength(2);
|
||||
// ).toHaveLength(3);
|
||||
// });
|
||||
// });
|
||||
describe("Conditional access", () => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 (
|
||||
<li className={baseClass}>
|
||||
<Link className={linkClassnames} to={path}>
|
||||
{title}
|
||||
<li className={wrapperClasses}>
|
||||
<Link to={path}>
|
||||
<TooltipTruncatedText value={title} tooltipPosition="right" />
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+22
-18
@@ -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 (
|
||||
<div className={wrapperClasses}>
|
||||
<ul className={wrapperClasses}>
|
||||
{categories.map((cat: ICategory) => {
|
||||
const isActive =
|
||||
cat.id === queryParams.category_id ||
|
||||
(cat.id === 0 && !queryParams.category_id);
|
||||
|
||||
return (
|
||||
<LinkWithContext
|
||||
key={cat.value ?? "all"}
|
||||
withParams={{
|
||||
type: "query",
|
||||
names: ["query", "page", "category_id"],
|
||||
}}
|
||||
currentQueryParams={{
|
||||
...queryParams,
|
||||
page: 0,
|
||||
category_id: cat.id !== 0 ? cat.id : undefined,
|
||||
}}
|
||||
to={location.pathname}
|
||||
className={classNames({
|
||||
[`${baseClass}__category-link`]: true,
|
||||
<li
|
||||
className={classNames(`${baseClass}__category-link`, {
|
||||
[`${baseClass}__category-link--active`]: isActive,
|
||||
})}
|
||||
key={cat.value ?? "all"}
|
||||
>
|
||||
<span data-text="">{cat.label}</span>
|
||||
</LinkWithContext>
|
||||
<LinkWithContext
|
||||
withParams={{
|
||||
type: "query",
|
||||
names: ["query", "page", "category_id"],
|
||||
}}
|
||||
currentQueryParams={{
|
||||
...queryParams,
|
||||
page: 0,
|
||||
category_id: cat.id !== 0 ? cat.id : undefined,
|
||||
}}
|
||||
to={location.pathname}
|
||||
>
|
||||
<TooltipTruncatedText value={cat.label} tooltipPosition="right" />
|
||||
</LinkWithContext>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
export default CategoriesMenu;
|
||||
|
||||
+2
-27
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,8 +61,6 @@
|
||||
@media (min-width: $break-md) {
|
||||
.categories-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
&__categories-dropdown {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user