mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Fleet UI: Improve internal links/buttons (#43470)
This commit is contained in:
parent
8209dbebee
commit
ecf2bad9a5
26 changed files with 74 additions and 144 deletions
1
changes/43342-improved-button-link-styling
Normal file
1
changes/43342-improved-button-link-styling
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Fleet UI: Improved button and link styling
|
||||
|
|
@ -3,7 +3,7 @@ import { useQuery } from "react-query";
|
|||
import configAPI from "services/entities/config";
|
||||
import { AppContext } from "context/app";
|
||||
|
||||
import CustomLink from "components/CustomLink";
|
||||
import Button from "components/buttons/Button";
|
||||
import DataError from "components/DataError";
|
||||
import Modal from "components/Modal";
|
||||
import Spinner from "components/Spinner";
|
||||
|
|
@ -60,10 +60,9 @@ const AddHostsModal = ({
|
|||
<span className="info__data">
|
||||
You have no enroll secrets.{" "}
|
||||
{openEnrollSecretModal ? (
|
||||
<CustomLink
|
||||
customClickHandler={onManageEnrollSecretsClick}
|
||||
text="Manage enroll secrets"
|
||||
/>
|
||||
<Button variant="link" onClick={onManageEnrollSecretsClick}>
|
||||
Manage enroll secrets
|
||||
</Button>
|
||||
) : (
|
||||
"Manage enroll secrets"
|
||||
)}{" "}
|
||||
|
|
|
|||
|
|
@ -369,12 +369,9 @@ const PlatformWrapper = ({
|
|||
<div>
|
||||
<InfoBanner className={`${baseClass}__chrome--instructions`}>
|
||||
This works for macOS, Windows, and Linux hosts. To add
|
||||
Chromebooks,
|
||||
<Button
|
||||
variant="text-link-dark"
|
||||
onClick={() => setSelectedTabIndex(3)}
|
||||
>
|
||||
click here
|
||||
Chromebooks,{" "}
|
||||
<Button variant="link" onClick={() => setSelectedTabIndex(3)}>
|
||||
visit the ChromeOS tab
|
||||
</Button>
|
||||
.
|
||||
</InfoBanner>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ interface ICustomLinkProps {
|
|||
* @default "default"
|
||||
*/
|
||||
variant?: "tooltip-link" | "banner-link" | "flash-message-link" | "default";
|
||||
customClickHandler?: (e: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
const baseClass = "custom-link";
|
||||
|
|
@ -35,7 +34,6 @@ const CustomLink = ({
|
|||
multiline = false,
|
||||
disableKeyboardNavigation = false,
|
||||
variant = "default",
|
||||
customClickHandler,
|
||||
}: ICustomLinkProps): JSX.Element => {
|
||||
const getIconColor = (): Colors => {
|
||||
switch (variant) {
|
||||
|
|
@ -58,27 +56,6 @@ const CustomLink = ({
|
|||
// e.g. cell/row handlers with a tooltip that has a custom link inside
|
||||
const handleClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
|
||||
// prevent navigation when we’re handling it ourselves
|
||||
// e.g. designed underline links opening modals
|
||||
if (customClickHandler) {
|
||||
e.preventDefault();
|
||||
customClickHandler(e);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle "Enter" key presses for accessibility when a custom click handler is provided
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLAnchorElement>) => {
|
||||
if (!customClickHandler) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// Reuse the same logic as click
|
||||
customClickHandler((e as unknown) as React.MouseEvent<HTMLAnchorElement>);
|
||||
}
|
||||
};
|
||||
|
||||
const target = newTab ? "_blank" : "";
|
||||
|
|
@ -121,7 +98,6 @@ const CustomLink = ({
|
|||
className={customLinkClass}
|
||||
tabIndex={disableKeyboardNavigation ? -1 : 0}
|
||||
onClick={handleClick}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
{content}
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.button--text-link {
|
||||
.button--link {
|
||||
font-weight: $regular;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,8 +68,7 @@ export const InverseVariant = Template("inverse");
|
|||
export const InverseAlertVariant = Template("inverse-alert");
|
||||
|
||||
export const PillVariant = Template("pill");
|
||||
export const TextLinkVariant = Template("text-link");
|
||||
export const TextLinkDarkVariant = Template("text-link-dark");
|
||||
export const LinkVariant = Template("link");
|
||||
export const TextIconVariant = Template(
|
||||
"text-icon",
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ export type ButtonVariant =
|
|||
| "alert"
|
||||
| "pill"
|
||||
| "grey-pill"
|
||||
| "text-link" // Underlines on hover
|
||||
| "text-link-dark" // underline on hover, dark text
|
||||
| "link" // Looks like CustomLink with animated underline on hover
|
||||
| "brand-inverse-icon" // Green icon with text, no underline on hover
|
||||
| "text-icon"
|
||||
| "icon" // Buttons without text
|
||||
|
|
@ -143,7 +142,7 @@ class Button extends React.Component<IButtonProps, IButtonState> {
|
|||
}
|
||||
);
|
||||
const onWhite =
|
||||
variant === "text-link" ||
|
||||
variant === "link" ||
|
||||
variant === "inverse" ||
|
||||
variant === "brand-inverse-icon" ||
|
||||
variant === "text-icon" ||
|
||||
|
|
|
|||
|
|
@ -217,64 +217,28 @@ $base-class: "button";
|
|||
}
|
||||
}
|
||||
|
||||
&--text-link {
|
||||
@include button-variant(transparent);
|
||||
// Looks exactly like a CustomLink but is a <button>
|
||||
&--link {
|
||||
@include link;
|
||||
@include animated-bottom-border($core-fleet-black, $ui-fleet-black-25);
|
||||
background: transparent;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
color: $ui-fleet-black-75;
|
||||
font-size: $x-small;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: auto;
|
||||
line-height: normal;
|
||||
text-align: left;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: $pad-xsmall;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $ui-fleet-black-75-over;
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: $ui-fleet-black-75-down;
|
||||
box-shadow: none;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&.light-text {
|
||||
font-weight: $regular;
|
||||
}
|
||||
}
|
||||
|
||||
&--text-link-dark {
|
||||
@include button-variant(transparent);
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
color: $core-fleet-black;
|
||||
font-size: $x-small;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: auto;
|
||||
line-height: normal;
|
||||
text-align: left;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
text-decoration: underline;
|
||||
&:focus-visible {
|
||||
@include unstyled-button-focus-outline(3px);
|
||||
}
|
||||
|
||||
&:active {
|
||||
|
|
@ -283,6 +247,8 @@ $base-class: "button";
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// &--icon is used for svg icon buttons without text
|
||||
&--text-icon,
|
||||
&--icon {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ const meta: Meta<typeof DropdownButton> = {
|
|||
"success",
|
||||
"alert",
|
||||
"pill",
|
||||
"text-link",
|
||||
"link",
|
||||
"text-icon",
|
||||
"icon",
|
||||
"inverse",
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ const ScriptListItem = ({
|
|||
<ListItem
|
||||
className={baseClass}
|
||||
graphic={graphicName}
|
||||
title={<Button variant="text-link">{script.name}</Button>}
|
||||
title={<Button variant="link">{script.name}</Button>}
|
||||
details={
|
||||
<ScriptListItemDetails
|
||||
platform={platform}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ const HashCell = ({
|
|||
|
||||
// 2 or more hashes
|
||||
return (
|
||||
<Button variant="text-link" onClick={onClickMultipleHashes}>
|
||||
<Button variant="link" onClick={onClickMultipleHashes}>
|
||||
{uniqueHashCount.toString()} hashes
|
||||
</Button>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ const InstalledPathCell = ({
|
|||
|
||||
// 2 or more installed versions
|
||||
return (
|
||||
<Button variant="text-link" onClick={onClickMultiplePaths}>
|
||||
<Button variant="link" onClick={onClickMultiplePaths}>
|
||||
{uniquePathsCount.toString()} paths
|
||||
</Button>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -62,14 +62,8 @@ const AddUsersModal = ({
|
|||
</div>
|
||||
<p>
|
||||
User not here?
|
||||
<Button
|
||||
onClick={onCreateNewTeamUser}
|
||||
variant="text-link"
|
||||
className="light-text"
|
||||
>
|
||||
<>
|
||||
<strong>Create a user</strong>
|
||||
</>
|
||||
<Button onClick={onCreateNewTeamUser} variant="link">
|
||||
Create a user
|
||||
</Button>
|
||||
</p>
|
||||
<div className="modal-cta-wrap">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
.user-form {
|
||||
|
||||
.form {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -81,7 +80,7 @@
|
|||
|
||||
.sublabel-nosso {
|
||||
font-size: 0.75rem;
|
||||
button.button--text-link {
|
||||
button.button--link {
|
||||
display: inline-flex;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const getHostStatusPreview = (teamScope?: boolean) => {
|
|||
|
||||
return {
|
||||
text:
|
||||
"More than X% of your hosts have not checked into Fleet for more than Y days. You’ve been sent this message because the Host status webhook is enabled in your Fleet instance.",
|
||||
"More than X% of your hosts have not checked into Fleet for more than Y days. You've been sent this message because the Host status webhook is enabled in your Fleet instance.",
|
||||
data,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -85,7 +85,6 @@ import { strToBool } from "utilities/strings/stringUtils";
|
|||
|
||||
import Button from "components/buttons/Button";
|
||||
import Icon from "components/Icon/Icon";
|
||||
import CustomLink from "components/CustomLink";
|
||||
import { SingleValue } from "react-select-5";
|
||||
import DropdownWrapper from "components/forms/fields/DropdownWrapper";
|
||||
import { CustomOptionType } from "components/forms/fields/DropdownWrapper/DropdownWrapper";
|
||||
|
|
@ -1936,10 +1935,12 @@ const ManageHostsPage = ({
|
|||
<div>
|
||||
<span>
|
||||
You have no enroll secrets.{" "}
|
||||
<CustomLink
|
||||
customClickHandler={() => setShowEnrollSecretModal(true)}
|
||||
text="Manage enroll secrets"
|
||||
/>{" "}
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={() => setShowEnrollSecretModal(true)}
|
||||
>
|
||||
Manage enroll secrets
|
||||
</Button>{" "}
|
||||
to enroll hosts to{" "}
|
||||
<b>{isAnyTeamSelected ? currentTeamName : "Fleet"}</b>.
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ const DeviceUserBanners = ({
|
|||
variant="banner-link"
|
||||
/>
|
||||
) : (
|
||||
<Button variant="text-link-dark" onClick={onClickTurnOnMdm}>
|
||||
<Button variant="link" onClick={onClickTurnOnMdm}>
|
||||
Turn on MDM
|
||||
</Button>
|
||||
);
|
||||
|
|
@ -153,7 +153,7 @@ const DeviceUserBanners = ({
|
|||
<InfoBanner
|
||||
color="yellow"
|
||||
cta={
|
||||
<Button variant="text-link-dark" onClick={onClickCreatePIN}>
|
||||
<Button variant="link" onClick={onClickCreatePIN}>
|
||||
Create PIN
|
||||
</Button>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ const RecoveryLockPasswordModal = ({
|
|||
if (canRotatePassword) {
|
||||
return (
|
||||
<Button
|
||||
variant="text-link"
|
||||
variant="inverse"
|
||||
onClick={onRotatePassword}
|
||||
disabled={isRotating}
|
||||
className={`${baseClass}__rotate-button`}
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ const SelectQueryModal = ({
|
|||
<>
|
||||
{" "}
|
||||
or{" "}
|
||||
<Button variant="text-link" onClick={onQueryHostCustom}>
|
||||
<Button variant="link" onClick={onQueryHostCustom}>
|
||||
create your own report
|
||||
</Button>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { IHostMdmProfile, MdmProfileStatus } from "interfaces/mdm";
|
|||
|
||||
import Icon from "components/Icon";
|
||||
import { IconNames } from "components/icons";
|
||||
import CustomLink from "components/CustomLink";
|
||||
import Button from "components/buttons/Button";
|
||||
|
||||
const baseClass = "os-settings-indicator";
|
||||
|
||||
|
|
@ -122,15 +122,16 @@ const OSSettingsIndicator = ({
|
|||
|
||||
const statusDisplayOption = STATUS_DISPLAY_OPTIONS[displayStatus];
|
||||
|
||||
// Using custom link for underline styling
|
||||
return (
|
||||
<span className={`${baseClass} info-flex__data`}>
|
||||
<Icon name={statusDisplayOption.iconName} />
|
||||
<CustomLink
|
||||
text={displayStatus}
|
||||
customClickHandler={onClick}
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={onClick}
|
||||
className={`${baseClass}__button`}
|
||||
/>
|
||||
>
|
||||
{displayStatus}
|
||||
</Button>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,10 +2,6 @@
|
|||
display: flex;
|
||||
gap: 4px;
|
||||
|
||||
&__button {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
|
|
|||
|
|
@ -544,7 +544,7 @@ const InstallStatusCell = ({
|
|||
return (
|
||||
<Button
|
||||
className={`${baseClass}__item-status-button`}
|
||||
variant="text-link"
|
||||
variant="link"
|
||||
onClick={match.onClick}
|
||||
>
|
||||
{resolvedDisplayText}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import DataSet from "components/DataSet";
|
|||
import CardHeader from "components/CardHeader";
|
||||
import TooltipWrapperArchLinuxRolling from "components/TooltipWrapperArchLinuxRolling";
|
||||
import Icon from "components/Icon/Icon";
|
||||
import CustomLink from "components/CustomLink";
|
||||
import Button from "components/buttons/Button";
|
||||
|
||||
import DiskSpaceIndicator from "pages/hosts/components/DiskSpaceIndicator";
|
||||
import { getCityCountryLocation } from "../../modals/LocationModal/LocationModal";
|
||||
|
|
@ -365,14 +365,10 @@ const Vitals = ({
|
|||
isIosOrIpadosHost && mdm?.enrollment_status === "On (automatic)";
|
||||
|
||||
if (isAdeIDevice || geolocation) {
|
||||
// Using custom link for underline styling
|
||||
const geoLocationButton = (
|
||||
<CustomLink
|
||||
customClickHandler={toggleLocationModal}
|
||||
text={
|
||||
isAdeIDevice ? "Show location" : getCityCountryLocation(geolocation)
|
||||
}
|
||||
/>
|
||||
<Button variant="link" onClick={toggleLocationModal}>
|
||||
{isAdeIDevice ? "Show location" : getCityCountryLocation(geolocation)}
|
||||
</Button>
|
||||
);
|
||||
vitals.push({
|
||||
sortKey: "Location",
|
||||
|
|
@ -418,13 +414,12 @@ const Vitals = ({
|
|||
value={
|
||||
<>
|
||||
{mdm.dep_profile_error && <Icon name="error" />}
|
||||
<CustomLink
|
||||
text={
|
||||
<Button variant="link" onClick={toggleMDMStatusModal}>
|
||||
{
|
||||
MDM_ENROLLMENT_STATUS_UI_MAP[mdm.enrollment_status]
|
||||
.displayName
|
||||
}
|
||||
customClickHandler={toggleMDMStatusModal}
|
||||
/>
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
.form-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.button--text-link {
|
||||
.button--inverse {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useContext, useRef, useState } from "react";
|
||||
|
||||
import { LEARN_MORE_ABOUT_BASE_LINK } from "utilities/constants";
|
||||
import PATHS from "router/paths";
|
||||
|
||||
import TooltipTruncatedText from "components/TooltipTruncatedText";
|
||||
import CriticalPolicyBadge from "components/CriticalPolicyBadge";
|
||||
|
|
@ -139,8 +140,19 @@ const ConditionalAccessModal = ({
|
|||
connect Fleet to {providerText}.
|
||||
<br />
|
||||
<br />
|
||||
This can be configured in <b>Settings</b> > <b>Integrations</b> >{" "}
|
||||
<b>Conditional access</b>.
|
||||
This can be configured in{" "}
|
||||
{isGlobalAdmin ? (
|
||||
<CustomLink
|
||||
url={PATHS.ADMIN_INTEGRATIONS_CONDITIONAL_ACCESS}
|
||||
text="Settings > Integrations > Conditional access"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<b>Settings</b> > <b>Integrations</b> >{" "}
|
||||
<b>Conditional access</b>
|
||||
</>
|
||||
)}
|
||||
.
|
||||
<br />
|
||||
<br />
|
||||
{learnMoreLink}
|
||||
|
|
|
|||
|
|
@ -492,7 +492,7 @@ $max-width: 2560px;
|
|||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -1px;
|
||||
bottom: 0;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background: $inactive-color;
|
||||
|
|
@ -509,7 +509,7 @@ $max-width: 2560px;
|
|||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -1px;
|
||||
bottom: 0;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background: $active-color;
|
||||
|
|
@ -526,11 +526,6 @@ $max-width: 2560px;
|
|||
$inactive-color: $ui-fleet-black-25
|
||||
) {
|
||||
position: relative;
|
||||
margin-bottom: -1px;
|
||||
|
||||
&:hover {
|
||||
margin-bottom: -1px; // Account for underline height, removed for link-cell truncated
|
||||
}
|
||||
|
||||
@include bottom-border($active-color, $inactive-color);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue