diff --git a/frontend/components/BackLink/BackLink.tsx b/frontend/components/BackLink/BackLink.tsx index 4a9ba2617d..84f68acdee 100644 --- a/frontend/components/BackLink/BackLink.tsx +++ b/frontend/components/BackLink/BackLink.tsx @@ -1,7 +1,6 @@ import React from "react"; import { browserHistory, Link } from "react-router"; import Icon from "components/Icon"; -import Button from "components/buttons/Button"; import classnames from "classnames"; interface IBackLinkProps { diff --git a/frontend/components/CustomLink/CustomLink.tsx b/frontend/components/CustomLink/CustomLink.tsx new file mode 100644 index 0000000000..1c897a888a --- /dev/null +++ b/frontend/components/CustomLink/CustomLink.tsx @@ -0,0 +1,65 @@ +import React from "react"; + +import Icon from "components/Icon"; +import classnames from "classnames"; + +interface ICustomLinkProps { + url: string; + text: string; + className?: string; + newTab?: boolean; + /** Icon wraps on new line with last word */ + multiline?: boolean; +} + +const baseClass = "custom-link"; + +const CustomLink = ({ + url, + text, + className, + newTab = false, + multiline = false, +}: ICustomLinkProps): JSX.Element => { + const customLinkClass = classnames(baseClass, className); + + const target = newTab ? "_blank" : ""; + + const multilineText = text.substring(0, text.lastIndexOf(" ") + 1); + const lastWord = text.substring(text.lastIndexOf(" ") + 1, text.length); + + const content = multiline ? ( + <> + {multilineText} + + {lastWord} + {newTab && ( + + )} + + + ) : ( + <> + {text} + {newTab && ( + + )} + + ); + + return ( + + {content} + + ); +}; + +export default CustomLink; diff --git a/frontend/components/CustomLink/_styles.scss b/frontend/components/CustomLink/_styles.scss new file mode 100644 index 0000000000..1685ee2d5c --- /dev/null +++ b/frontend/components/CustomLink/_styles.scss @@ -0,0 +1,10 @@ +.custom-link { + &__no-wrap { + white-space: nowrap; + } + + &__external-icon { + display: inline; + margin-left: $pad-xsmall; + } +} diff --git a/frontend/components/CustomLink/index.ts b/frontend/components/CustomLink/index.ts new file mode 100644 index 0000000000..8be6b555c6 --- /dev/null +++ b/frontend/components/CustomLink/index.ts @@ -0,0 +1 @@ +export { default } from "./CustomLink"; diff --git a/frontend/components/DataError/DataError.tsx b/frontend/components/DataError/DataError.tsx index 3789d27b25..498d1ae025 100644 --- a/frontend/components/DataError/DataError.tsx +++ b/frontend/components/DataError/DataError.tsx @@ -1,6 +1,6 @@ import React from "react"; -import ExternalLinkIcon from "../../../assets/images/icon-external-link-12x12@2x.png"; +import CustomLink from "components/CustomLink"; import ErrorIcon from "../../../assets/images/icon-error-16x16@2x.png"; const baseClass = "data-error"; @@ -21,14 +21,11 @@ const DataError = ({ card }: IDataErrorProps): JSX.Element => { Refresh the page or log in again. If this keeps happening, please  - - file an issue - Open external link - + diff --git a/frontend/components/FleetMarkdown/FleetMarkdown.tsx b/frontend/components/FleetMarkdown/FleetMarkdown.tsx index 4d50bc83a6..92d81dfd44 100644 --- a/frontend/components/FleetMarkdown/FleetMarkdown.tsx +++ b/frontend/components/FleetMarkdown/FleetMarkdown.tsx @@ -6,24 +6,7 @@ import { IAceEditor } from "react-ace/lib/types"; import { noop } from "lodash"; import FleetAce from "components/FleetAce"; - -import ExternalLinkIcon from "../../../assets/images/icon-external-link-12x12@2x.png"; - -interface ICustomLinkProps { - text: React.ReactNode; - href: string; - newTab?: boolean; -} - -const CustomLink = ({ text, href, newTab = false }: ICustomLinkProps) => { - const target = newTab ? "__blank" : ""; - return ( - - {text} - Open external link - - ); -}; +import CustomLink from "components/CustomLink"; interface IFleetMarkdownProps { markdown: string; @@ -46,7 +29,9 @@ const FleetMarkdown = ({ markdown, className }: IFleetMarkdownProps) => { remarkPlugins={[remarkGfm]} components={{ a: ({ href = "", children }) => { - return ; + return ( + + ); }, // Overrides code display to use FleetAce with Readonly overrides. diff --git a/frontend/components/Icon/Icon.tsx b/frontend/components/Icon/Icon.tsx index 14bee6621d..3abc23e42a 100644 --- a/frontend/components/Icon/Icon.tsx +++ b/frontend/components/Icon/Icon.tsx @@ -7,22 +7,28 @@ interface IIconProps { color?: "coreVibrantBlue" | "coreFleetBlack"; direction?: "up" | "down" | "left" | "right"; className?: string; + size?: "small" | "medium"; } const baseClass = "icon"; -const Icon = ({ name, color, direction, className }: IIconProps) => { +const Icon = ({ name, color, direction, className, size }: IIconProps) => { const classNames = classnames(baseClass, className); // createPassedProps creates a props object that we pass to the specific icon // for values that are not null or undefined const props = useMemo(() => { const createPassedProps = () => { - return Object.assign({}, { color, direction }); + return Object.assign( + {}, + color === undefined ? undefined : { color }, + direction === undefined ? undefined : { direction }, + size === undefined ? undefined : { size } + ); }; return createPassedProps(); - }, [color, direction]); + }, [color, direction, size]); const IconComponent = ICON_MAP[name]; diff --git a/frontend/components/Sandbox/SandboxDemoMessage/SandboxDemoMessage.tsx b/frontend/components/Sandbox/SandboxDemoMessage/SandboxDemoMessage.tsx index 43a202d46b..a6e26d79da 100644 --- a/frontend/components/Sandbox/SandboxDemoMessage/SandboxDemoMessage.tsx +++ b/frontend/components/Sandbox/SandboxDemoMessage/SandboxDemoMessage.tsx @@ -1,7 +1,7 @@ import classnames from "classnames"; import React from "react"; -import ExternalLinkIcon from "../../../../assets/images/icon-external-link-12x12@2x.png"; +import CustomLink from "components/CustomLink"; interface ISandboxDemoMessageProps { /** message to display in the sandbox error */ @@ -24,15 +24,12 @@ const SandboxDemoMessage = ({

{message}

- Want to learn more? - - Schedule a demo - Open external link - + Want to learn more?{" "} +

); diff --git a/frontend/components/icons/ExternalLink.tsx b/frontend/components/icons/ExternalLink.tsx new file mode 100644 index 0000000000..915d8339dc --- /dev/null +++ b/frontend/components/icons/ExternalLink.tsx @@ -0,0 +1,31 @@ +import React from "react"; + +interface IExternalLinkProps { + size: "small" | "medium"; +} + +const SIZE_MAP = { + small: "12", + medium: "16", +}; + +const ExternalLink = ({ size = "small" }: IExternalLinkProps) => { + return ( + + + + ); +}; + +export default ExternalLink; diff --git a/frontend/components/icons/index.ts b/frontend/components/icons/index.ts index d509802a1c..3d5b22ae7b 100644 --- a/frontend/components/icons/index.ts +++ b/frontend/components/icons/index.ts @@ -8,6 +8,7 @@ import LowDiskSpaceHosts from "./LowDiskSpaceHosts"; import ApplePurple from "./ApplePurple"; import LinuxGreen from "./LinuxGreen"; import WindowsBlue from "./WindowsBlue"; +import ExternalLink from "./ExternalLink"; // a mapping of the usable names of icons to the icon source. export const ICON_MAP = { @@ -24,6 +25,7 @@ export const ICON_MAP = { "linux-green": LinuxGreen, "missing-hosts": MissingHosts, "low-disk-space-hosts": LowDiskSpaceHosts, + "external-link": ExternalLink, }; export type IconNames = keyof typeof ICON_MAP; diff --git a/frontend/components/side_panels/PackInfoSidePanel/PackInfoSidePanel.jsx b/frontend/components/side_panels/PackInfoSidePanel/PackInfoSidePanel.jsx index bb9be7448c..ba18bb56ce 100644 --- a/frontend/components/side_panels/PackInfoSidePanel/PackInfoSidePanel.jsx +++ b/frontend/components/side_panels/PackInfoSidePanel/PackInfoSidePanel.jsx @@ -1,8 +1,9 @@ import React from "react"; +import CustomLink from "components/CustomLink"; + import DifferentialIcon from "../../../../assets/images/icon-plus-minus-black-16x16@2x.png"; import SnapshotIcon from "../../../../assets/images/icon-snapshot-black-16x14@2x.png"; -import ExternalLinkIcon from "../../../../assets/images/icon-external-link-12x12@2x.png"; const baseClass = "pack-info-side-panel"; @@ -55,18 +56,11 @@ const PackInfoSidePanel = () => {

Learn more about log aggregation in the{" "} - - documentation - Open external link - +

); diff --git a/frontend/layouts/CoreLayout/CoreLayout.tsx b/frontend/layouts/CoreLayout/CoreLayout.tsx index 6dde829933..6c76dfa64a 100644 --- a/frontend/layouts/CoreLayout/CoreLayout.tsx +++ b/frontend/layouts/CoreLayout/CoreLayout.tsx @@ -8,9 +8,9 @@ import paths from "router/paths"; import useDeepEffect from "hooks/useDeepEffect"; import FlashMessage from "components/FlashMessage"; import SiteTopNav from "components/top_nav/SiteTopNav"; +import CustomLink from "components/CustomLink"; import { INotification } from "interfaces/notification"; import { licenseExpirationWarning } from "utilities/helpers"; -import ExternalLinkIcon from "../../../assets/images/icon-external-link-12x12@2x.png"; import smallScreenImage from "../../../assets/images/small-screen-160x80@2x.png"; @@ -23,14 +23,12 @@ const expirationMessage = ( <> Your license for Fleet Premium is about to expire. If you’d like to renew or have questions about downgrading,{" "} - - please head to the Fleet documentation - Open external link - + ); diff --git a/frontend/pages/DashboardPage/DashboardPage.tsx b/frontend/pages/DashboardPage/DashboardPage.tsx index 34db3ed474..57c7fbc65f 100644 --- a/frontend/pages/DashboardPage/DashboardPage.tsx +++ b/frontend/pages/DashboardPage/DashboardPage.tsx @@ -33,6 +33,7 @@ import { ITableQueryData } from "components/TableContainer"; import TeamsDropdown from "components/TeamsDropdown"; import Spinner from "components/Spinner"; +import CustomLink from "components/CustomLink"; // @ts-ignore import Dropdown from "components/forms/fields/Dropdown"; import MainContent from "components/MainContent"; @@ -49,7 +50,6 @@ import Mdm from "./cards/MDM"; import Munki from "./cards/Munki"; import OperatingSystems from "./cards/OperatingSystems"; import AddHostsModal from "../../components/AddHostsModal"; -import ExternalLinkIcon from "../../../assets/images/icon-external-link-12x12@2x.png"; const baseClass = "dashboard-page"; @@ -476,14 +476,11 @@ const DashboardPage = (): JSX.Element => { description: (

Munki is a tool for managing software on macOS devices.{" "} - - Learn about Munki - Open external link - +

), children: ( @@ -503,14 +500,11 @@ const DashboardPage = (): JSX.Element => { description: (

MDM is used to manage configuration on macOS devices.{" "} - - Learn about MDM - Open external link - +

), children: ( diff --git a/frontend/pages/DashboardPage/cards/OperatingSystems/OperatingSystems.tsx b/frontend/pages/DashboardPage/cards/OperatingSystems/OperatingSystems.tsx index 629d2b0de7..d25bda2301 100644 --- a/frontend/pages/DashboardPage/cards/OperatingSystems/OperatingSystems.tsx +++ b/frontend/pages/DashboardPage/cards/OperatingSystems/OperatingSystems.tsx @@ -18,8 +18,7 @@ import TableContainer from "components/TableContainer"; import Spinner from "components/Spinner"; import TableDataError from "components/DataError"; import LastUpdatedText from "components/LastUpdatedText"; - -import ExternalLinkIcon from "../../../../../assets/images/icon-external-link-12x12@2x.png"; +import CustomLink from "components/CustomLink"; import generateTableHeaders from "./OperatingSystemsTableConfig"; @@ -89,17 +88,12 @@ const OperatingSystems = ({

{OS_VENDOR_BY_PLATFORM[selectedPlatform]} releases updates and fixes for supported operating systems.{" "} - - See supported operating{" "} - - systems - Open external link - - +

) : null; diff --git a/frontend/pages/ForgotPasswordPage/ForgotPasswordPage.tsx b/frontend/pages/ForgotPasswordPage/ForgotPasswordPage.tsx index 7a83460468..94e4ba2d91 100644 --- a/frontend/pages/ForgotPasswordPage/ForgotPasswordPage.tsx +++ b/frontend/pages/ForgotPasswordPage/ForgotPasswordPage.tsx @@ -10,7 +10,7 @@ import ForgotPasswordForm from "components/forms/ForgotPasswordForm"; import StackedWhiteBoxes from "components/StackedWhiteBoxes"; import AuthenticationFormWrapper from "components/AuthenticationFormWrapper"; import Spinner from "components/Spinner"; -import ExternalLinkIcon from "../../../assets/images/icon-external-link-12x12@2x.png"; +import CustomLink from "components/CustomLink"; const ForgotPasswordPage = () => { const [email, setEmail] = useState(""); @@ -54,15 +54,11 @@ const ForgotPasswordPage = () => {

You can find more information on resetting passwords at the{" "} - - Password reset FAQ - Open external link - +

); diff --git a/frontend/pages/UserSettingsPage/UserSettingsPage.tsx b/frontend/pages/UserSettingsPage/UserSettingsPage.tsx index 3d31f7c8a2..ff54aeee06 100644 --- a/frontend/pages/UserSettingsPage/UserSettingsPage.tsx +++ b/frontend/pages/UserSettingsPage/UserSettingsPage.tsx @@ -25,9 +25,9 @@ import SandboxGate from "components/Sandbox/SandboxGate"; import SandboxDemoMessage from "components/Sandbox/SandboxDemoMessage"; import MainContent from "components/MainContent"; import SidePanelContent from "components/SidePanelContent"; +import CustomLink from "components/CustomLink"; import UserSidePanel from "./UserSidePanel"; -import ExternalLinkIcon from "../../../assets/images/icon-external-link-12x12@2x.png"; const baseClass = "user-settings"; @@ -188,14 +188,11 @@ const UserSettingsPage = ({

This token expires. If you want an API key for a permanent integration, create an  - - API-only user - Open external link - +  instead.

@@ -205,14 +202,11 @@ const UserSettingsPage = ({

This token is intended for SSO users to authenticate in the fleetctl CLI. It expires based on the{" "} - - session duration configuration - Open external link - +

diff --git a/frontend/pages/admin/AppSettingsPage/cards/Statistics/Statistics.tsx b/frontend/pages/admin/AppSettingsPage/cards/Statistics/Statistics.tsx index f72632386d..1e820e7468 100644 --- a/frontend/pages/admin/AppSettingsPage/cards/Statistics/Statistics.tsx +++ b/frontend/pages/admin/AppSettingsPage/cards/Statistics/Statistics.tsx @@ -5,7 +5,7 @@ import Button from "components/buttons/Button"; import Checkbox from "components/forms/fields/Checkbox"; import Modal from "components/Modal"; -import ExternalLinkIcon from "../../../../../../assets/images/icon-external-link-12x12@2x.png"; +import CustomLink from "components/CustomLink"; import { IAppConfigFormProps, IFormField, @@ -95,14 +95,11 @@ const Statistics = ({ we can make better product decisions.

- - Learn more about usage statistics - Open external link - +

)} - - Learn more about agent{" "} - - options - Open external link - - +

{isFetchingTeamOptions ? ( diff --git a/frontend/pages/admin/TeamManagementPage/TeamManagementPage.tsx b/frontend/pages/admin/TeamManagementPage/TeamManagementPage.tsx index 59c98c72b0..991021200b 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamManagementPage.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamManagementPage.tsx @@ -13,11 +13,12 @@ import teamsAPI, { import Button from "components/buttons/Button"; import TableContainer from "components/TableContainer"; import TableDataError from "components/DataError"; +import CustomLink from "components/CustomLink"; + import CreateTeamModal from "./components/CreateTeamModal"; import DeleteTeamModal from "./components/DeleteTeamModal"; import EditTeamModal from "./components/EditTeamModal"; import { generateTableHeaders, generateDataSet } from "./TeamTableConfig"; -import ExternalLinkIcon from "../../../../assets/images/icon-external-link-12x12@2x.png"; const baseClass = "team-management"; const noTeamsClass = "no-teams"; @@ -203,14 +204,11 @@ const TeamManagementPage = (): JSX.Element => {

Want to learn more?  - - Read about teams - Open external link - +