From 1ba18765d06416ee00ba7d2a4b6fadee50962382 Mon Sep 17 00:00:00 2001 From: Oisin Coveney <oisin@metabase.com> Date: Tue, 26 Mar 2024 14:20:58 +0200 Subject: [PATCH] Convert `link.module.css` to CSS modules (#40584) --- ...or-question-with-filter-causes-error.cy.spec.js | 2 +- .../ImpersonationModal/ImpersonationModalView.tsx | 3 ++- .../ImpersonationWarning/ImpersonationWarning.tsx | 3 ++- .../components/DatabaseList/DatabaseList.jsx | 4 ++-- .../metabase/admin/people/components/AddRow.jsx | 2 +- .../admin/people/components/GroupsListing.jsx | 7 ++----- .../admin/people/containers/UserSuccessModal.jsx | 6 ++++-- .../CollectionPermissionsModal.jsx | 2 +- .../components/UploadSettings/UploadSettings.tsx | 3 ++- .../PublicLinksListing/PublicLinksListing.jsx | 2 +- .../metabase/admin/tasks/containers/JobInfoApp.jsx | 2 +- .../metabase/admin/tasks/containers/TasksApp.jsx | 2 +- .../components/CollectionCopyEntityModal.jsx | 2 +- .../ChannelSetupMessage/ChannelSetupMessage.jsx | 2 +- .../components/ErrorDetails/ErrorDetails.tsx | 4 +++- .../components/LeftNavPane/LeftNavPane.jsx | 3 +-- .../components/PasswordReveal/PasswordReveal.jsx | 2 +- .../components/Triggerable/Triggerable.jsx | 3 ++- .../core/components/ExternalLink/ExternalLink.tsx | 3 ++- frontend/src/metabase/css/core/link.module.css | 12 +++++------- .../dashboard/containers/AutomaticDashboardApp.jsx | 5 ++++- .../DatabaseClientIdDescription.tsx | 3 ++- frontend/src/metabase/lib/formatting/url.tsx | 5 ++++- frontend/src/metabase/lib/formatting/value.tsx | 7 ++++++- .../nav/components/PaymentBanner/PaymentBanner.tsx | 5 +++-- .../components/QuestionLineage/QuestionLineage.tsx | 3 ++- .../components/AlertListPopoverContent.jsx | 6 +++--- .../DataSelectorTablePicker.tsx | 4 +++- .../components/DataSelector/TriggerComponents.tsx | 14 ++++++++++---- .../query_builder/components/ExpandableString.jsx | 7 +++++-- .../VisualizationError/VisualizationError.tsx | 2 +- .../components/VisualizationResult.jsx | 2 +- .../template_tags/TagEditorHelp/TagEditorHelp.tsx | 4 +++- .../TagEditorParamParts/FilterWidgetTypeSelect.tsx | 3 ++- .../setup/components/SetupHelp/SetupHelp.tsx | 3 ++- .../AddEditSidebar/CaveatMessage/CaveatMessage.jsx | 3 ++- .../sharing/components/NewPulseSidebar.tsx | 12 ++++++++++-- frontend/test/metabase/lib/formatting.unit.spec.js | 8 ++++---- 38 files changed, 103 insertions(+), 62 deletions(-) diff --git a/e2e/test/scenarios/dashboard-cards/reproductions/15993-click-behavior-question-with-filter-causes-error.cy.spec.js b/e2e/test/scenarios/dashboard-cards/reproductions/15993-click-behavior-question-with-filter-causes-error.cy.spec.js index 7774f43d0f4..d2d3ba2e983 100644 --- a/e2e/test/scenarios/dashboard-cards/reproductions/15993-click-behavior-question-with-filter-causes-error.cy.spec.js +++ b/e2e/test/scenarios/dashboard-cards/reproductions/15993-click-behavior-question-with-filter-causes-error.cy.spec.js @@ -39,7 +39,7 @@ describe("issue 15993", () => { }); // Drill-through - cy.findAllByTestId("cell-data").get(".link").contains("0").realClick(); + cy.findAllByTestId("cell-data").contains("0").realClick(); // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage cy.contains("117.03").should("not.exist"); // Total for the order in which quantity wasn't 0 diff --git a/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationModal/ImpersonationModalView.tsx b/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationModal/ImpersonationModalView.tsx index a341d79b747..df2efce4bf9 100644 --- a/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationModal/ImpersonationModalView.tsx +++ b/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationModal/ImpersonationModalView.tsx @@ -10,6 +10,7 @@ import FormFooter from "metabase/core/components/FormFooter"; import FormSelect from "metabase/core/components/FormSelect"; import FormSubmitButton from "metabase/core/components/FormSubmitButton"; import Link from "metabase/core/components/Link/Link"; +import CS from "metabase/css/core/index.css"; import { Form, FormProvider } from "metabase/forms"; import * as Errors from "metabase/lib/errors"; import MetabaseSettings from "metabase/lib/settings"; @@ -93,7 +94,7 @@ export const ImpersonationModalView = ({ <ImpersonationDescription> {modalMessage}{" "} <ExternalLink - className="link" + className={CS.link} // eslint-disable-next-line no-unconditional-metabase-links-render -- Admin settings href={MetabaseSettings.docsUrl("permissions/data")} >{t`Learn More`}</ExternalLink> diff --git a/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationWarning/ImpersonationWarning.tsx b/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationWarning/ImpersonationWarning.tsx index 1fedad4415b..3bc90ddc01d 100644 --- a/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationWarning/ImpersonationWarning.tsx +++ b/enterprise/frontend/src/metabase-enterprise/advanced_permissions/components/ImpersonationWarning/ImpersonationWarning.tsx @@ -2,6 +2,7 @@ import { t, jt } from "ttag"; import { BoldCode } from "metabase/components/Code"; import Link from "metabase/core/components/Link"; +import CS from "metabase/css/core/index.css"; import * as Urls from "metabase/lib/urls"; import { isEmpty } from "metabase/lib/validate"; import type Database from "metabase-lib/v1/metadata/Database"; @@ -61,7 +62,7 @@ export const ImpersonationWarning = ({ <ImpersonationAlert icon="warning" variant="warning"> {isEmpty(databaseUser) ? emptyText : warningText}{" "} <Link - className="link" + className={CS.link} to={Urls.editDatabase(database.id) + (databaseUser ? "#user" : "")} >{t`Edit settings`}</Link> </ImpersonationAlert> diff --git a/frontend/src/metabase/admin/databases/components/DatabaseList/DatabaseList.jsx b/frontend/src/metabase/admin/databases/components/DatabaseList/DatabaseList.jsx index 3eb2a55d1ba..d4d693150fc 100644 --- a/frontend/src/metabase/admin/databases/components/DatabaseList/DatabaseList.jsx +++ b/frontend/src/metabase/admin/databases/components/DatabaseList/DatabaseList.jsx @@ -120,7 +120,7 @@ export default class DatabaseList extends Component { )} <Link to={"/admin/databases/" + database.id} - className="text-bold link" + className={cx("text-bold", CS.link)} > {database.name} </Link> @@ -153,7 +153,7 @@ export default class DatabaseList extends Component { })} > {isAddingSampleDatabase ? ( - <span className="text-light no-decoration"> + <span className={cx("text-light", CS.noDecoration)}> {t`Restoring the sample database...`} </span> ) : ( diff --git a/frontend/src/metabase/admin/people/components/AddRow.jsx b/frontend/src/metabase/admin/people/components/AddRow.jsx index 7cb53103de3..5076d658c48 100644 --- a/frontend/src/metabase/admin/people/components/AddRow.jsx +++ b/frontend/src/metabase/admin/people/components/AddRow.jsx @@ -44,7 +44,7 @@ export const AddRow = forwardRef(function AddRow( onKeyDown={onKeyDown} onChange={onChange} /> - <span className="link no-decoration cursor-pointer" onClick={onCancel}> + <span className={CS.link} onClick={onCancel}> {t`Cancel`} </span> <button diff --git a/frontend/src/metabase/admin/people/components/GroupsListing.jsx b/frontend/src/metabase/admin/people/components/GroupsListing.jsx index 16ee454937a..11cc11c0e72 100644 --- a/frontend/src/metabase/admin/people/components/GroupsListing.jsx +++ b/frontend/src/metabase/admin/people/components/GroupsListing.jsx @@ -164,10 +164,7 @@ function EditingGroupRow({ </td> <td /> <td className="text-right"> - <span - className="link no-decoration cursor-pointer" - onClick={onCancelClicked} - >{t`Cancel`}</span> + <span className={CS.link} onClick={onCancelClicked}>{t`Cancel`}</span> <button className={cx(ButtonsS.Button, CS.ml2, { [ButtonsS.ButtonPrimary]: textIsValid && textHasChanged, @@ -213,7 +210,7 @@ function GroupRow({ <td> <Link to={"/admin/people/groups/" + group.id} - className={cx("link", CS.noDecoration, CS.flex, CS.alignCenter)} + className={cx(CS.link, CS.flex, CS.alignCenter)} > <span className="text-white"> <UserAvatar diff --git a/frontend/src/metabase/admin/people/containers/UserSuccessModal.jsx b/frontend/src/metabase/admin/people/containers/UserSuccessModal.jsx index bbe6b8769be..ee65d603ba7 100644 --- a/frontend/src/metabase/admin/people/containers/UserSuccessModal.jsx +++ b/frontend/src/metabase/admin/people/containers/UserSuccessModal.jsx @@ -1,4 +1,5 @@ /* eslint-disable react/prop-types */ +import cx from "classnames"; import { Component } from "react"; import { connect } from "react-redux"; import { push } from "react-router-redux"; @@ -9,6 +10,7 @@ import ModalContent from "metabase/components/ModalContent"; import PasswordReveal from "metabase/components/PasswordReveal"; import Button from "metabase/core/components/Button"; import Link from "metabase/core/components/Link"; +import CS from "metabase/css/core/index.css"; import User from "metabase/entities/users"; import MetabaseSettings from "metabase/lib/settings"; @@ -50,7 +52,7 @@ const EmailSuccess = ({ user, isSsoEnabled }) => { )} with instructions to log in. If this user is unable to authenticate then you can ${( <Link to={`/admin/people/${user.id}/reset`} - className="link" + className={CS.link} >{t`reset their password.`}</Link> )}`}</div> ); @@ -76,7 +78,7 @@ const PasswordSuccess = ({ user, temporaryPassword }) => ( className="pt4 text-centered" > {jt`If you want to be able to send email invites, just go to the ${( - <Link to="/admin/settings/email" className="link text-bold"> + <Link to="/admin/settings/email" className={cx(CS.link, "text-bold")}> Email Settings </Link> )} page.`} diff --git a/frontend/src/metabase/admin/permissions/components/CollectionPermissionsModal/CollectionPermissionsModal.jsx b/frontend/src/metabase/admin/permissions/components/CollectionPermissionsModal/CollectionPermissionsModal.jsx index 70aa92a8a6b..58720d32318 100644 --- a/frontend/src/metabase/admin/permissions/components/CollectionPermissionsModal/CollectionPermissionsModal.jsx +++ b/frontend/src/metabase/admin/permissions/components/CollectionPermissionsModal/CollectionPermissionsModal.jsx @@ -130,7 +130,7 @@ const CollectionPermissionsModal = ({ : [ <Link key="all-permissions" - className="link" + className={CS.link} to="/admin/permissions/collections" > {t`See all collection permissions`} diff --git a/frontend/src/metabase/admin/settings/components/UploadSettings/UploadSettings.tsx b/frontend/src/metabase/admin/settings/components/UploadSettings/UploadSettings.tsx index 9d3f91c7fd8..a3db423291d 100644 --- a/frontend/src/metabase/admin/settings/components/UploadSettings/UploadSettings.tsx +++ b/frontend/src/metabase/admin/settings/components/UploadSettings/UploadSettings.tsx @@ -12,6 +12,7 @@ import Input from "metabase/core/components/Input"; import Link from "metabase/core/components/Link"; import type { SelectChangeEvent } from "metabase/core/components/Select"; import Select from "metabase/core/components/Select"; +import CS from "metabase/css/core/index.css"; import Databases from "metabase/entities/databases"; import Schemas from "metabase/entities/schemas"; import { useDispatch } from "metabase/lib/redux"; @@ -71,7 +72,7 @@ const Header = () => ( display_name: t`Allow people to upload data to Collections`, description: jt`People will be able to upload CSV files that will be stored in the ${( <Link - className="link" + className={CS.link} key="db-link" to="/admin/databases" >{t`database`}</Link> diff --git a/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing/PublicLinksListing.jsx b/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing/PublicLinksListing.jsx index 416b14b6437..39a0a9a762f 100644 --- a/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing/PublicLinksListing.jsx +++ b/frontend/src/metabase/admin/settings/components/widgets/PublicLinksListing/PublicLinksListing.jsx @@ -100,7 +100,7 @@ class PublicLinksListing extends Component { <ExternalLink href={getPublicUrl(link)} onClick={() => this.trackEvent("Public Link Clicked")} - className="link text-wrap" + className={cx(CS.link, "text-wrap")} > {getPublicUrl(link)} </ExternalLink> diff --git a/frontend/src/metabase/admin/tasks/containers/JobInfoApp.jsx b/frontend/src/metabase/admin/tasks/containers/JobInfoApp.jsx index f7db719f188..a1410cfc27b 100644 --- a/frontend/src/metabase/admin/tasks/containers/JobInfoApp.jsx +++ b/frontend/src/metabase/admin/tasks/containers/JobInfoApp.jsx @@ -50,7 +50,7 @@ const renderJobsTable = jobs => { <td>{job.durable}</td> <td> <Link - className="link" + className={CS.link} to={`/admin/troubleshooting/jobs/${job.key}`} > {t`View triggers`} diff --git a/frontend/src/metabase/admin/tasks/containers/TasksApp.jsx b/frontend/src/metabase/admin/tasks/containers/TasksApp.jsx index e092749b3a2..abcfb72fd64 100644 --- a/frontend/src/metabase/admin/tasks/containers/TasksApp.jsx +++ b/frontend/src/metabase/admin/tasks/containers/TasksApp.jsx @@ -91,7 +91,7 @@ class TasksAppInner extends Component { <td>{task.duration}</td> <td> <Link - className="link text-bold" + className={cx(CS.link, "text-bold")} to={`/admin/troubleshooting/tasks/${task.id}`} >{t`View`}</Link> </td> diff --git a/frontend/src/metabase/collections/components/CollectionCopyEntityModal.jsx b/frontend/src/metabase/collections/components/CollectionCopyEntityModal.jsx index e200b0fef43..0dcd86b4ea9 100644 --- a/frontend/src/metabase/collections/components/CollectionCopyEntityModal.jsx +++ b/frontend/src/metabase/collections/components/CollectionCopyEntityModal.jsx @@ -59,7 +59,7 @@ function CollectionCopyEntityModal({ {newEntityObject.uncopied?.length > 0 ? t`Duplicated ${entityObject.model}, but couldn't duplicate some questions` : t`Duplicated ${entityObject.model}`} - <Link className="link text-bold ml1" to={newEntityUrl}> + <Link className={cx(CS.link, "text-bold ml1")} to={newEntityUrl}> {t`See it`} </Link> </div>, diff --git a/frontend/src/metabase/components/ChannelSetupMessage/ChannelSetupMessage.jsx b/frontend/src/metabase/components/ChannelSetupMessage/ChannelSetupMessage.jsx index bc83fe8585b..6a2e779f8af 100644 --- a/frontend/src/metabase/components/ChannelSetupMessage/ChannelSetupMessage.jsx +++ b/frontend/src/metabase/components/ChannelSetupMessage/ChannelSetupMessage.jsx @@ -42,7 +42,7 @@ export default class ChannelSetupMessage extends Component { content = ( <div className="mb1"> <h4 className="text-medium">{t`Your admin's email address`}:</h4> - <a className="h2 link no-decoration" href={"mailto:" + adminEmail}> + <a className={cx("h2", CS.link)} href={"mailto:" + adminEmail}> {adminEmail} </a> </div> diff --git a/frontend/src/metabase/components/ErrorDetails/ErrorDetails.tsx b/frontend/src/metabase/components/ErrorDetails/ErrorDetails.tsx index 625a7618af4..56f227894c3 100644 --- a/frontend/src/metabase/components/ErrorDetails/ErrorDetails.tsx +++ b/frontend/src/metabase/components/ErrorDetails/ErrorDetails.tsx @@ -2,6 +2,8 @@ import cx from "classnames"; import { useState } from "react"; import { t } from "ttag"; +import CS from "metabase/css/core/index.css"; + import { ErrorBox } from "./ErrorBox"; import type { ErrorDetailsProps } from "./types"; @@ -24,7 +26,7 @@ export default function ErrorDetails({ return ( <div className={className}> <div className={centered ? "text-centered" : "text-left"}> - <a onClick={toggleShowError} className="link cursor-pointer"> + <a onClick={toggleShowError} className={cx(CS.link)}> {showError ? t`Hide error details` : t`Show error details`} </a> </div> diff --git a/frontend/src/metabase/components/LeftNavPane/LeftNavPane.jsx b/frontend/src/metabase/components/LeftNavPane/LeftNavPane.jsx index 12565f9df6c..e777f0968e6 100644 --- a/frontend/src/metabase/components/LeftNavPane/LeftNavPane.jsx +++ b/frontend/src/metabase/components/LeftNavPane/LeftNavPane.jsx @@ -54,10 +54,9 @@ export function LeftNavPaneItemBack({ path }) { AdminS.AdminListItem, CS.flex, CS.alignCenter, - CS.noDecoration, CS.textBold, CS.justifyBetween, - "link", + CS.link, )} > < {t`Back`} diff --git a/frontend/src/metabase/components/PasswordReveal/PasswordReveal.jsx b/frontend/src/metabase/components/PasswordReveal/PasswordReveal.jsx index b2c7a7faa97..4f8a4bc8078 100644 --- a/frontend/src/metabase/components/PasswordReveal/PasswordReveal.jsx +++ b/frontend/src/metabase/components/PasswordReveal/PasswordReveal.jsx @@ -76,7 +76,7 @@ export default class PasswordReveal extends Component { <div className={cx(CS.mlAuto, CS.flex, CS.alignCenter)}> <a - className={cx("link", CS.textBold, CS.mr2)} + className={cx(CS.link, CS.textBold, CS.mr2)} onClick={() => this.setState({ visible: !visible })} > {visible ? t`Hide` : t`Show`} diff --git a/frontend/src/metabase/components/Triggerable/Triggerable.jsx b/frontend/src/metabase/components/Triggerable/Triggerable.jsx index d87290491ed..99c677da067 100644 --- a/frontend/src/metabase/components/Triggerable/Triggerable.jsx +++ b/frontend/src/metabase/components/Triggerable/Triggerable.jsx @@ -4,6 +4,7 @@ import cx from "classnames"; import { createRef, cloneElement, Children, Component } from "react"; import Tooltip from "metabase/core/components/Tooltip"; +import CS from "metabase/css/core/index.css"; import { isObscured } from "metabase/lib/dom"; const Trigger = styled.a``; @@ -153,7 +154,7 @@ const Triggerable = ComposedComponent => triggerClasses, isOpen && triggerClassesOpen, !isOpen && triggerClassesClose, - "no-decoration", + CS.noDecoration, { "cursor-default": this.props.disabled, }, diff --git a/frontend/src/metabase/core/components/ExternalLink/ExternalLink.tsx b/frontend/src/metabase/core/components/ExternalLink/ExternalLink.tsx index 608438a43a2..90813728595 100644 --- a/frontend/src/metabase/core/components/ExternalLink/ExternalLink.tsx +++ b/frontend/src/metabase/core/components/ExternalLink/ExternalLink.tsx @@ -1,6 +1,7 @@ import type { AnchorHTMLAttributes, ReactNode, Ref } from "react"; import { forwardRef } from "react"; +import CS from "metabase/css/core/index.css"; import { getUrlTarget } from "metabase/lib/dom"; import { LinkRoot } from "./ExternalLink.styled"; @@ -20,7 +21,7 @@ const ExternalLink = forwardRef(function ExternalLink( <LinkRoot ref={ref} href={href} - className={className || "link"} + className={className || CS.link} target={target} // prevent malicious pages from navigating us away rel="noopener noreferrer" diff --git a/frontend/src/metabase/css/core/link.module.css b/frontend/src/metabase/css/core/link.module.css index 03bec2f0e78..3495c97d760 100644 --- a/frontend/src/metabase/css/core/link.module.css +++ b/frontend/src/metabase/css/core/link.module.css @@ -1,27 +1,25 @@ -:global(.no-decoration), -.no-decoration, .noDecoration { text-decoration: none; } -:global(.link) { +.link { cursor: pointer; text-decoration: none; color: var(--color-brand); } -:global(.link:hover) { +.link:hover { text-decoration: underline; } -:global(.link:focus) { +.link:focus { outline: 2px solid var(--color-focus); } -:global(.link:focus:not(:focus-visible)) { +.link:focus:not(:focus-visible) { outline: none; } -:global(.link--wrappable) { +.linkWrappable { word-break: break-all; } diff --git a/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx b/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx index 2a2457dcf85..6c732e46395 100644 --- a/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx +++ b/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx @@ -79,7 +79,10 @@ class AutomaticDashboardAppInner extends Component { triggerToast( <div className={cx(CS.flex, CS.alignCenter)}> {t`Your dashboard was saved`} - <Link className="link text-bold ml1" to={Urls.dashboard(newDashboard)}> + <Link + className={cx(CS.link, "text-bold ml1")} + to={Urls.dashboard(newDashboard)} + > {t`See it`} </Link> </div>, diff --git a/frontend/src/metabase/databases/components/DatabaseClientIdDescription/DatabaseClientIdDescription.tsx b/frontend/src/metabase/databases/components/DatabaseClientIdDescription/DatabaseClientIdDescription.tsx index 6948f82bbaa..71b2f23cada 100644 --- a/frontend/src/metabase/databases/components/DatabaseClientIdDescription/DatabaseClientIdDescription.tsx +++ b/frontend/src/metabase/databases/components/DatabaseClientIdDescription/DatabaseClientIdDescription.tsx @@ -2,6 +2,7 @@ import { useFormikContext } from "formik"; import { jt, t } from "ttag"; import ExternalLink from "metabase/core/components/ExternalLink"; +import CS from "metabase/css/core/index.css"; import type { DatabaseData } from "metabase-types/api"; const CREDENTIAL_URLS: Record<string, string> = { @@ -24,7 +25,7 @@ const DatabaseClientIdDescription = (): JSX.Element | null => { return ( <span> {jt`${( - <ExternalLink className="link" href={projectUrl.href}> + <ExternalLink className={CS.link} href={projectUrl.href}> {t`Click here`} </ExternalLink> )} to generate a Client ID and Client Secret for your project.`}{" "} diff --git a/frontend/src/metabase/lib/formatting/url.tsx b/frontend/src/metabase/lib/formatting/url.tsx index 4d9ad10b937..cf820fb2a86 100644 --- a/frontend/src/metabase/lib/formatting/url.tsx +++ b/frontend/src/metabase/lib/formatting/url.tsx @@ -1,4 +1,7 @@ +import cx from "classnames"; + import ExternalLink from "metabase/core/components/ExternalLink"; +import CS from "metabase/css/core/index.css"; import { getDataFromClicked } from "metabase-lib/v1/parameters/utils/click-behavior"; import { isURL } from "metabase-lib/v1/types/utils/isa"; @@ -35,7 +38,7 @@ export function formatUrl(value: string, options: OptionsType = {}) { if (jsx && rich && url) { const text = getLinkText(value, options); return ( - <ExternalLink className="link link--wrappable" href={url}> + <ExternalLink className={cx(CS.link, CS.linkWrappable)} href={url}> {text} </ExternalLink> ); diff --git a/frontend/src/metabase/lib/formatting/value.tsx b/frontend/src/metabase/lib/formatting/value.tsx index 86f39280aba..0584672d568 100644 --- a/frontend/src/metabase/lib/formatting/value.tsx +++ b/frontend/src/metabase/lib/formatting/value.tsx @@ -1,3 +1,4 @@ +import cx from "classnames"; import type { Moment } from "moment-timezone"; // eslint-disable-line no-restricted-imports -- deprecated usage import moment from "moment-timezone"; // eslint-disable-line no-restricted-imports -- deprecated usage import Mustache from "mustache"; @@ -5,6 +6,7 @@ import type * as React from "react"; import ReactMarkdown from "react-markdown"; import ExternalLink from "metabase/core/components/ExternalLink"; +import CS from "metabase/css/core/index.css"; import { NULL_DISPLAY_VALUE, NULL_NUMERIC_VALUE } from "metabase/lib/constants"; import { renderLinkTextForClick } from "metabase/lib/formatting/link"; import { @@ -155,7 +157,10 @@ export function formatValueRaw( // Style this like a link if we're in a jsx context. // It's not actually a link since we handle the click differently for dashboard and question targets. return ( - <div className="link link--wrappable"> + <div + data-testid="link-formatted-text" + className={cx(CS.link, CS.linkWrappable)} + > {formatValueRaw(value, { ...options, jsx: false })} </div> ); diff --git a/frontend/src/metabase/nav/components/PaymentBanner/PaymentBanner.tsx b/frontend/src/metabase/nav/components/PaymentBanner/PaymentBanner.tsx index 9ae99d6b790..13bf516c5a5 100644 --- a/frontend/src/metabase/nav/components/PaymentBanner/PaymentBanner.tsx +++ b/frontend/src/metabase/nav/components/PaymentBanner/PaymentBanner.tsx @@ -2,6 +2,7 @@ import { jt, t } from "ttag"; import Banner from "metabase/components/Banner"; import ExternalLink from "metabase/core/components/ExternalLink"; +import CS from "metabase/css/core/index.css"; import MetabaseSettings from "metabase/lib/settings"; import type { TokenStatus } from "metabase-types/api"; @@ -17,7 +18,7 @@ export const PaymentBanner = ({ isAdmin, tokenStatus }: PaymentBannerProps) => { {jt`âš ï¸ We couldn't process payment for your account. Please ${( <ExternalLink key="payment-past-due" - className="link" + className={CS.link} href={MetabaseSettings.storeUrl()} > {t`review your payment settings`} @@ -31,7 +32,7 @@ export const PaymentBanner = ({ isAdmin, tokenStatus }: PaymentBannerProps) => { {jt`âš ï¸ Pro features won’t work right now due to lack of payment. ${( <ExternalLink key="payment-unpaid" - className="link" + className={CS.link} href={MetabaseSettings.storeUrl()} > {t`Review your payment settings`} diff --git a/frontend/src/metabase/nav/components/QuestionLineage/QuestionLineage.tsx b/frontend/src/metabase/nav/components/QuestionLineage/QuestionLineage.tsx index 0dad2c89a8b..afc3445313a 100644 --- a/frontend/src/metabase/nav/components/QuestionLineage/QuestionLineage.tsx +++ b/frontend/src/metabase/nav/components/QuestionLineage/QuestionLineage.tsx @@ -2,6 +2,7 @@ import { t } from "ttag"; import Badge from "metabase/components/Badge"; import Link from "metabase/core/components/Link/Link"; +import CS from "metabase/css/core/index.css"; import type { IconName } from "metabase/ui"; import type Question from "metabase-lib/v1/Question"; import * as ML_Urls from "metabase-lib/v1/urls"; @@ -24,7 +25,7 @@ const QuestionLineage = ({ return ( <Badge icon={icon} isSingleLine> {t`Started from`}{" "} - <Link className="link" to={ML_Urls.getUrl(originalQuestion)}> + <Link className={CS.link} to={ML_Urls.getUrl(originalQuestion)}> {originalQuestion.displayName()} </Link> </Badge> diff --git a/frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx b/frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx index 3d0179597e5..c768d90968a 100644 --- a/frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx +++ b/frontend/src/metabase/query_builder/components/AlertListPopoverContent.jsx @@ -87,7 +87,7 @@ class AlertListPopoverContent extends Component { <div className={cx(CS.borderTop, CS.p2, "bg-light-blue")}> <a className={cx( - "link", + CS.link, CS.flex, CS.alignCenter, CS.textBold, @@ -186,11 +186,11 @@ class AlertListItemInner extends Component { }} > {(isAdmin || isCurrentUser) && ( - <a className="link" onClick={this.onEdit}>{jt`Edit`}</a> + <a className={CS.link} onClick={this.onEdit}>{jt`Edit`}</a> )} {!isAdmin && !unsubscribingProgress && ( <a - className="link ml2" + className={cx(CS.link, "ml2")} onClick={this.onUnsubscribe} >{jt`Unsubscribe`}</a> )} diff --git a/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorTablePicker/DataSelectorTablePicker.tsx b/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorTablePicker/DataSelectorTablePicker.tsx index b3a6b68bde4..d0afdfa88be 100644 --- a/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorTablePicker/DataSelectorTablePicker.tsx +++ b/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorTablePicker/DataSelectorTablePicker.tsx @@ -1,3 +1,4 @@ +import cx from "classnames"; import type { ReactNode } from "react"; import { t } from "ttag"; @@ -7,6 +8,7 @@ import { } from "metabase/components/MetadataInfo/TableInfoIcon/TableInfoIcon"; import AccordionList from "metabase/core/components/AccordionList"; import ExternalLink from "metabase/core/components/ExternalLink"; +import CS from "metabase/css/core/index.css"; import { color } from "metabase/lib/colors"; import MetabaseSettings from "metabase/lib/settings"; import { isSyncCompleted } from "metabase/lib/syncing"; @@ -162,7 +164,7 @@ const LinkToDocsOnReferencingSavedQuestionsInQueries = () => ( "questions/native-editor/referencing-saved-questions-in-queries", )} target="_blank" - className="block link" + className={cx("block", CS.link)} > {t`Learn more about nested queries`} </ExternalLink> diff --git a/frontend/src/metabase/query_builder/components/DataSelector/TriggerComponents.tsx b/frontend/src/metabase/query_builder/components/DataSelector/TriggerComponents.tsx index 166eef94a38..b725f9222f3 100644 --- a/frontend/src/metabase/query_builder/components/DataSelector/TriggerComponents.tsx +++ b/frontend/src/metabase/query_builder/components/DataSelector/TriggerComponents.tsx @@ -1,7 +1,9 @@ +import cx from "classnames"; import type { CSSProperties, ReactNode } from "react"; import { t } from "ttag"; import _ from "underscore"; +import CS from "metabase/css/core/index.css"; import { Icon, Text } from "metabase/ui"; import type Database from "metabase-lib/v1/metadata/Database"; import type Field from "metabase-lib/v1/metadata/Field"; @@ -81,22 +83,26 @@ export function FieldTrigger({ export function DatabaseTrigger({ database }: { database: Database }) { return database ? ( <span - className="text-wrap text-grey no-decoration" + className={cx("text-wrap text-grey", CS.noDecoration)} data-testid="selected-database" > {database.name} </span> ) : ( - <span className="text-medium no-decoration">{t`Select a database`}</span> + <span + className={cx("text-medium", CS.noDecoration)} + >{t`Select a database`}</span> ); } export function TableTrigger({ table }: { table: Table }) { return table ? ( - <span className="text-wrap text-grey no-decoration"> + <span className={cx("text-wrap text-grey", CS.noDecoration)}> {table.display_name || table.name} </span> ) : ( - <span className="text-medium no-decoration">{t`Select a table`}</span> + <span + className={cx("text-medium", CS.noDecoration)} + >{t`Select a table`}</span> ); } diff --git a/frontend/src/metabase/query_builder/components/ExpandableString.jsx b/frontend/src/metabase/query_builder/components/ExpandableString.jsx index ba542ce6288..539268aed8e 100644 --- a/frontend/src/metabase/query_builder/components/ExpandableString.jsx +++ b/frontend/src/metabase/query_builder/components/ExpandableString.jsx @@ -1,8 +1,11 @@ /* eslint-disable react/prop-types */ +import cx from "classnames"; import Humanize from "humanize-plus"; import { Component } from "react"; import { t } from "ttag"; +import CS from "metabase/css/core/index.css"; + export default class ExpandableString extends Component { constructor(props, context) { super(props, context); @@ -43,7 +46,7 @@ export default class ExpandableString extends Component { <span> {this.props.str}{" "} <span - className="block mt1 link" + className={cx("block mt1", CS.link)} onClick={this.toggleExpansion} >{t`View less`}</span> </span> @@ -53,7 +56,7 @@ export default class ExpandableString extends Component { <span> {truncated}{" "} <span - className="block mt1 link" + className={cx("block mt1", CS.link)} onClick={this.toggleExpansion} >{t`View more`}</span> </span> diff --git a/frontend/src/metabase/query_builder/components/VisualizationError/VisualizationError.tsx b/frontend/src/metabase/query_builder/components/VisualizationError/VisualizationError.tsx index 7f8d29b8ad8..024152ec234 100644 --- a/frontend/src/metabase/query_builder/components/VisualizationError/VisualizationError.tsx +++ b/frontend/src/metabase/query_builder/components/VisualizationError/VisualizationError.tsx @@ -31,7 +31,7 @@ function EmailAdmin(): JSX.Element | null { const hasAdminEmail = isNotNull(MetabaseSettings.adminEmail()); return hasAdminEmail ? ( <span className={QueryBuilderS.QueryErrorAdminEmail}> - <a className="no-decoration" href={`mailto:${hasAdminEmail}`}> + <a className={CS.noDecoration} href={`mailto:${hasAdminEmail}`}> {hasAdminEmail} </a> </span> diff --git a/frontend/src/metabase/query_builder/components/VisualizationResult.jsx b/frontend/src/metabase/query_builder/components/VisualizationResult.jsx index 9b89e6c6af1..00452d780bb 100644 --- a/frontend/src/metabase/query_builder/components/VisualizationResult.jsx +++ b/frontend/src/metabase/query_builder/components/VisualizationResult.jsx @@ -78,7 +78,7 @@ export default class VisualizationResult extends Component { <p> {jt`You can also ${( <a - className="link" + className={CS.link} key="link" onClick={this.showCreateAlertModal} > diff --git a/frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp/TagEditorHelp.tsx b/frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp/TagEditorHelp.tsx index 7fa023d36f8..6f26acc08cf 100644 --- a/frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp/TagEditorHelp.tsx +++ b/frontend/src/metabase/query_builder/components/template_tags/TagEditorHelp/TagEditorHelp.tsx @@ -1,8 +1,10 @@ +import cx from "classnames"; import { t, jt } from "ttag"; import Code from "metabase/components/Code"; import Button from "metabase/core/components/Button"; import ExternalLink from "metabase/core/components/ExternalLink"; +import CS from "metabase/css/core/index.css"; import { useSelector } from "metabase/lib/redux"; import MetabaseSettings from "metabase/lib/settings"; import { uuid } from "metabase/lib/utils"; @@ -312,7 +314,7 @@ export const TagEditorHelp = ({ /> {showMetabaseLinks && ( - <p className="pt2 link"> + <p className={cx("pt2", CS.link)}> <ExternalLink href={MetabaseSettings.docsUrl( "questions/native-editor/sql-parameters", diff --git a/frontend/src/metabase/query_builder/components/template_tags/TagEditorParamParts/FilterWidgetTypeSelect.tsx b/frontend/src/metabase/query_builder/components/template_tags/TagEditorParamParts/FilterWidgetTypeSelect.tsx index 81a421fea8e..3c6309bb272 100644 --- a/frontend/src/metabase/query_builder/components/template_tags/TagEditorParamParts/FilterWidgetTypeSelect.tsx +++ b/frontend/src/metabase/query_builder/components/template_tags/TagEditorParamParts/FilterWidgetTypeSelect.tsx @@ -2,6 +2,7 @@ import { useMemo } from "react"; import { Link } from "react-router"; import { t } from "ttag"; +import CS from "metabase/css/core/index.css"; import MetabaseSettings from "metabase/lib/settings"; import { Select } from "metabase/ui"; import type { TemplateTag } from "metabase-types/api"; @@ -65,7 +66,7 @@ export function FilterWidgetTypeSelect({ "the-field-filter-variable-type", )} target="_blank" - className="link" + className={CS.link} > {t`Learn more`} </Link> diff --git a/frontend/src/metabase/setup/components/SetupHelp/SetupHelp.tsx b/frontend/src/metabase/setup/components/SetupHelp/SetupHelp.tsx index 8286019e82a..6e8af10cea9 100644 --- a/frontend/src/metabase/setup/components/SetupHelp/SetupHelp.tsx +++ b/frontend/src/metabase/setup/components/SetupHelp/SetupHelp.tsx @@ -1,6 +1,7 @@ import { t } from "ttag"; import ExternalLink from "metabase/core/components/ExternalLink"; +import CS from "metabase/css/core/index.css"; import MetabaseSettings from "metabase/lib/settings"; import { SetupFooterRoot } from "./SetupHelp.styled"; @@ -10,7 +11,7 @@ export const SetupHelp = (): JSX.Element => { <SetupFooterRoot> {t`If you feel stuck`},{" "} <ExternalLink - className="link" + className={CS.link} href={MetabaseSettings.docsUrl( "configuring-metabase/setting-up-metabase", )} diff --git a/frontend/src/metabase/sharing/components/AddEditSidebar/CaveatMessage/CaveatMessage.jsx b/frontend/src/metabase/sharing/components/AddEditSidebar/CaveatMessage/CaveatMessage.jsx index 302f8647103..293ebbe9874 100644 --- a/frontend/src/metabase/sharing/components/AddEditSidebar/CaveatMessage/CaveatMessage.jsx +++ b/frontend/src/metabase/sharing/components/AddEditSidebar/CaveatMessage/CaveatMessage.jsx @@ -1,6 +1,7 @@ import { t } from "ttag"; import ExternalLink from "metabase/core/components/ExternalLink"; +import CS from "metabase/css/core/index.css"; import { useSelector } from "metabase/lib/redux"; import MetabaseSettings from "metabase/lib/settings"; import { getShowMetabaseLinks } from "metabase/selectors/whitelabel"; @@ -16,7 +17,7 @@ export function CaveatMessage() { <> <ExternalLink - className="link" + className={CS.link} target="_blank" href={MetabaseSettings.docsUrl("dashboards/subscriptions")} > diff --git a/frontend/src/metabase/sharing/components/NewPulseSidebar.tsx b/frontend/src/metabase/sharing/components/NewPulseSidebar.tsx index 3ed66590fc4..e6338bd7d97 100644 --- a/frontend/src/metabase/sharing/components/NewPulseSidebar.tsx +++ b/frontend/src/metabase/sharing/components/NewPulseSidebar.tsx @@ -63,7 +63,11 @@ export function NewPulseSidebar({ > {!emailConfigured && jt`You'll need to ${( - <Link key="link" to="/admin/settings/email" className="link"> + <Link + key="link" + to="/admin/settings/email" + className={CS.link} + > {t`set up email`} </Link> )} first.`} @@ -101,7 +105,11 @@ export function NewPulseSidebar({ > {!slackConfigured && jt`First, you'll have to ${( - <Link key="link" to="/admin/settings/slack" className="link"> + <Link + key="link" + to="/admin/settings/slack" + className={CS.link} + > {t`configure Slack`} </Link> )}.`} diff --git a/frontend/test/metabase/lib/formatting.unit.spec.js b/frontend/test/metabase/lib/formatting.unit.spec.js index f00b913b5ab..ec4090fd875 100644 --- a/frontend/test/metabase/lib/formatting.unit.spec.js +++ b/frontend/test/metabase/lib/formatting.unit.spec.js @@ -268,8 +268,8 @@ describe("formatting", () => { }); // it's not actually a link expect(isElementOfType(formatted, ExternalLink)).toEqual(false); - // but it's formatted as a link - expect(formatted.props.className).toEqual("link link--wrappable"); + // expect the text to be in a div (which has link formatting) rather than ExternalLink + expect(formatted.props["data-testid"]).toEqual("link-formatted-text"); }); it("should render image", () => { const formatted = formatValue("http://metabase.com/logo.png", { @@ -498,8 +498,8 @@ describe("formatting", () => { // it is not a link set on the question level expect(isElementOfType(formatted, ExternalLink)).toEqual(false); - // it is formatted as a link cell for the dashboard level click behavior - expect(formatted.props.className).toEqual("link link--wrappable"); + // expect the text to be in a div (which has link formatting) rather than ExternalLink + expect(formatted.props["data-testid"]).toEqual("link-formatted-text"); }); }); -- GitLab