diff --git a/package-lock.json b/package-lock.json index 43edc4ce..7af147ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13136,20 +13136,6 @@ "integrity": "sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw==", "dev": true }, - "history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "dev": true, - "requires": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -18111,16 +18097,6 @@ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true }, - "mini-create-react-context": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz", - "integrity": "sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.5.5", - "tiny-warning": "^1.0.3" - } - }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -20912,56 +20888,6 @@ "react-popper": "^1.3.7" } }, - "react-router": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", - "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.4.0", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "requires": { - "isarray": "0.0.1" - } - } - } - }, - "react-router-dom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", - "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.2.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - } - }, "react-sizeme": { "version": "2.6.12", "resolved": "https://registry.npmjs.org/react-sizeme/-/react-sizeme-2.6.12.tgz", @@ -21803,12 +21729,6 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, - "resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==", - "dev": true - }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -24746,18 +24666,6 @@ "dev": true, "optional": true }, - "tiny-invariant": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", - "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==", - "dev": true - }, - "tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "dev": true - }, "tinycolor2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", @@ -25494,12 +25402,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", - "dev": true - }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 53001c73..98344c92 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,6 @@ "prop-types": "^15.7.2", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-router-dom": "^5.2.0", "sass-loader": "^9.0.2", "style-loader": "^1.2.1", "stylelint": "^13.6.1" diff --git a/src/components/Button/Button.docs.mdx b/src/components/Button/Button.docs.mdx index 7bd90bbb..27a880df 100644 --- a/src/components/Button/Button.docs.mdx +++ b/src/components/Button/Button.docs.mdx @@ -1,5 +1,5 @@ import { Story, Preview, Props } from '@storybook/addon-docs/blocks'; -import Button, { ButtonLink } from './Button'; +import Button, { ButtonLabel } from './Button'; # Button @@ -7,7 +7,7 @@ Presents the user with an action to take, such as a form submission or promotion Use the `Button` component when presenting the user with an action that affects the current page, such as submitting a form or dismissing a modal. This component renders an HTML ` ); @@ -63,12 +96,10 @@ Button.propTypes = { */ disabled: PropTypes.bool.isRequired, /** - * Toggles the same visual state as `props.disabled`, but without - * affecting the interactivity of the button. Use this when you want - * to indicate that a form's contents are invalid, but still - * submissible. + * Whether the button should be purely textual, e.g. for use in a + * paragraph of text. */ - inactive: PropTypes.bool.isRequired, + inline: PropTypes.bool.isRequired, /** * Visually replaces `props.children` with the Spinner component. Use * when waiting for an action to complete in response to a user @@ -108,36 +139,17 @@ Button.propTypes = { */ type: PropTypes.oneOf( [ 'submit', 'button' ] ).isRequired, /** - * Visual variations of the button. + * Visual variations of the button. See `ButtonLabel.propTypes.variant` */ - variant: PropTypes.oneOf( [ - 'inline', - 'primary', - 'secondary', - 'warning-inline', - 'warning', - ] ).isRequired, + variant: PropTypes.string, }; Button.defaultProps = { disabled: false, - inactive: false, loading: false, onClick: () => {}, type: 'button', - variant: 'primary', -}; - -const ButtonLink = ( { children, variant, ...props } ) => {children}; - -ButtonLink.propTypes = { - children: PropTypes.node.isRequired, - variant: PropTypes.string.isRequired, -}; - -ButtonLink.defaultProps = { - variant: 'primary', }; -export { ButtonLink }; +export { ButtonLabel }; export default Button; diff --git a/src/components/Button/Button.scss b/src/components/Button/Button.scss index 2c6420f0..0910d68a 100644 --- a/src/components/Button/Button.scss +++ b/src/components/Button/Button.scss @@ -1,81 +1,79 @@ @import '~@quartz/styles/scss/borders'; @import '~@quartz/styles/scss/color-scheme'; @import '~@quartz/styles/scss/fonts'; -@import '~@quartz/styles/scss/helpers/a11y'; +@import '~@quartz/styles/scss/helpers/positioning'; @import '~@quartz/styles/scss/helpers/resets'; @import '~@quartz/styles/scss/states'; @import '~@quartz/styles/scss/tokens'; -.variant-primary, -.variant-secondary, -.variant-warning { +.button { @include reset-button; - @include font-maison-800-2; @include fade-on-hover; - display: block; - width: 100%; - box-sizing: border-box; - line-height: 1; - text-align: center; - border-radius: $size-border-radius; cursor: pointer; - font-size: 16px; - padding: 14px; - white-space: nowrap; + position: relative; &:disabled { @include reset-fade-on-hover; + opacity: 0.5; cursor: default; } - &.inactive { - opacity: 0.3; + &.block { + display: block; + width: 100%; } } -.variant-primary { - color: $color-background-2; - background-color: $color-accent; +.label-text.loading { + opacity: 0; } -.variant-secondary { - color: $color-typography; - background-color: $color-background-3; +.spinner { + @include center; } -.variant-warning { - color: $color-white; - background-color: $color-red; -} - -.variant-inline, -.variant-warning-inline { - @include reset-button; - @include fade-on-hover; +.label { @include font-maison; - cursor: pointer; - color: $color-accent; - - &:disabled { - @include reset-fade-on-hover; + font-size: 16px; + white-space: nowrap; + line-height: 1; - cursor: default; - opacity: 0.5; + &.block { + font-weight: 800; + display: block; + box-sizing: border-box; + text-align: center; + border-radius: $size-border-radius; + padding: 14px; } } -.variant-warning-inline { - color: $color-red; +.variant-primary { + color: $color-accent; + + &.block { + color: $color-background-2; + background-color: $color-accent; + } } -.loading { - pointer-events: none; - padding: 0; +.variant-secondary { + color: $color-typography-faint; + + &.block { + color: $color-typography; + background-color: $color-background-3; + } } -label.loading { - @include visually-hidden; +.variant-warning { + color: $color-red; + + &.block { + color: $color-white; + background-color: $color-red; + } } diff --git a/src/components/Button/Button.story.jsx b/src/components/Button/Button.story.jsx index 6a16f62e..57f8bc35 100644 --- a/src/components/Button/Button.story.jsx +++ b/src/components/Button/Button.story.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import Button, { ButtonLink } from './Button'; +import Button, { ButtonLabel } from './Button'; import docs from './Button.docs.mdx'; export default { @@ -13,6 +13,6 @@ export default { export const Default = () => ; export const Secondary = () => ; export const Warning = () => ; -export const Inline = () => ; -export const InlineWarning = () => ; -export const Link = () => I link to example.com; +export const Inline = () => ; +export const InlineWarning = () => ; +export const LabelOnly = () => I am static text; diff --git a/src/components/CalloutCard/CalloutCard.story.jsx b/src/components/CalloutCard/CalloutCard.story.jsx index 7a3eb261..4374d1db 100644 --- a/src/components/CalloutCard/CalloutCard.story.jsx +++ b/src/components/CalloutCard/CalloutCard.story.jsx @@ -1,6 +1,5 @@ import React from 'react'; import CalloutCard from './CalloutCard'; -import Link from '../Link/Link'; import docs from './CalloutCard.docs.mdx'; export default { @@ -20,7 +19,7 @@ export const Default = () => (

Become a member of Quartz

-

Go beyond the headlines to master your understanding of the forces reshaping the world.

+

Go beyond the headlines to master your understanding of the forces reshaping the world.

); @@ -38,7 +37,7 @@ export const Form = () => (

Need to Know: Coronavirus

-

Want a calm, rational, even curious approach to coronavirus? We’ve got an email for that. Sign up here:

+

Want a calm, rational, even curious approach to coronavirus? We’ve got an email for that. Sign up here:

diff --git a/src/components/Link/Link.jsx b/src/components/Link/Link.jsx deleted file mode 100644 index 0c73d00a..00000000 --- a/src/components/Link/Link.jsx +++ /dev/null @@ -1,217 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Link as ReactRouterLink, useLocation } from 'react-router-dom'; - -/** - * Determines whether a URL "belongs" to this app or to something else and - * returns either a false (external URL) or a relative path. - * - * @param {string} link - * @return {false|string} - */ -export const getRelativeLink = link => { - if ( !link ) { - return false; - } - // These tests validate whether a permalink is governed by this app. - const appDomainTests = [ - /^(qz\.com|quartzy\.qz\.com|work\.qz\.com)$/, // whitelisted subdomains of qz.* - /(go-vip\.co|quartz\.work|qz\.dev|qz\.vip)$/, // dev domains - ]; - - const urlIsAbsolute = link.match( /^[A-z]\w+\:/ ); - const httpUrlMatches = link.match( /^https?\:\/\/(([^:\/?#]*))(?:\:([0-9]+))?/ ); - - // SecureDrop link is special (cannot be served by React). - if ( '/tips' === link || 'https://qz.com/tips' === link ) { - return false; - } - - // /app/ link is special (should not be served using ReactRouter - // to allow the app deep link to work correctly). - if ( link.includes( '/app/' ) && !link.match( /\/([0-9]+)\/([^\/]+)\/app\/?$/ ) ) { - return false; - } - - if ( httpUrlMatches ) { - const [ site, domain ] = httpUrlMatches; - - if ( appDomainTests.some( regex => regex.test( domain ) ) ) { - // URL is absolute and links to one of our domains - return link - // Make the URL relative by replacing the root part of the URL with a slash - .replace( site, '/' ) - // Remove the /app URL part if it exists at the end of an article URL. These URLs are just for the Quartz app. - .replace( /\/([0-9]+)\/([^\/]+)\/app\/?$/, ( match, articleId, articleSlug ) => `/${articleId}/${articleSlug}/` ) - // Replace any instances of multiple slashes - .replace( /\/+/g, '/' ); - } - - // URL points to another domain - return false; - } - - // URL is absolute but not associated with this app - if ( urlIsAbsolute ) { - return false; - } - - return link; -}; - -export const normalizeTo = ( to = {}, from ) => { - if ( typeof to === 'string' ) { - const [ pathname, hash ] = to.split( '#' ); - - return { - hash, - pathname, - state: { - from, - }, - }; - } - - return { - ...to, - state: { - from, - ...to.state, - }, - }; -}; - -/** - * Because ref props have magic behavior in React, a ref prop can "stick" to the - * HOC and React will not pass it down to the wrapped component (e.g., Link) - * where we want it: - * - * https://reactjs.org/docs/forwarding-refs.html#forwarding-refs-in-higher-order-components - * - * Forwarding refs is one method of navigating this issue, but it introduces - * complexity since each HOC must forward the ref so that it doesn't break the - * chain of forwarded refs. Over time, this will likely lead to every HOC - * implementing forwardRef just in case the wrapped component needs a ref. - * - * If, however, we simply rename the ref prop to something else (e.g., - * "innerRef"), it loses its magic behavior and React will continue to pass it - * down the tree. When we are in control of both the HOC and the wrapped - * component, this is a much simpler solution. - * - * NOTE: React Router 5.1 handles forwarding our refs automatically, so we no - * longer need to pass our refs to links as innerRef. However, we still - * *receive* the prop as innerRef for the reasons outlined above. - */ -export const Link = ( { - checked, - children, - className, - current, - innerRef, - label, - onClick, - rel, - role, - style, - target, - title, - to, -} ) => { - // If no destination was provided, omit the link but preserve formatting. - if ( ! to ) { - return ( -
- {children} -
- ); - } - - const location = useLocation(); - const normalizedTo = normalizeTo( to, location.pathname ); - const relativeLink = getRelativeLink( normalizedTo.pathname ); - - if ( !!relativeLink ) { - return ( - - {children} - - ); - } - - return ( - - {children} - - ); -}; - -Link.propTypes = { - checked: PropTypes.bool, - children: PropTypes.node, - className: PropTypes.string, - current: PropTypes.oneOfType( [ - PropTypes.bool, - PropTypes.string, - ] ), - innerRef: PropTypes.oneOfType( [ - PropTypes.object, - PropTypes.func, - ] ), - label: PropTypes.string, - onClick: PropTypes.func, - rel: PropTypes.string, - role: PropTypes.string, - style: PropTypes.object, - target: PropTypes.string, - title: PropTypes.string, - to: PropTypes.oneOfType( [ - PropTypes.string, - PropTypes.object, - ] ).isRequired, -}; - -Link.defaultProps = { - className: '', - onClick: () => {}, -}; - -export const LinkWhen = ( { when, ...props } ) => { - if ( when ) { - return ; - } - - return props.children; -}; - -LinkWhen.propTypes = { - children: PropTypes.node, -}; - -export default Link; diff --git a/src/components/Spinner/Spinner.scss b/src/components/Spinner/Spinner.scss index ac5316c2..e5decb9b 100644 --- a/src/components/Spinner/Spinner.scss +++ b/src/components/Spinner/Spinner.scss @@ -17,7 +17,7 @@ border-radius: 50%; width: 1.25rem; height: 1.25rem; - margin: 10px auto; + display: inline-block; transform: translateZ(0); animation: spin 1.1s infinite cubic-bezier(0.32, 0.64, 0.71, 0.74); border: 2px solid $color-accent; diff --git a/src/components/index.js b/src/components/index.js index 9f498a43..c205d1f1 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -1,10 +1,9 @@ -export { default as Button, ButtonLink } from './Button/Button'; export { default as BadgeGroup } from './BadgeGroup/BadgeGroup'; +export { default as Button, ButtonLabel } from './Button/Button'; export { default as Blockquote } from './Blockquote/Blockquote'; export { default as CalloutCard } from './CalloutCard/CalloutCard'; export { default as Checkbox } from './Checkbox/Checkbox'; export { default as Image } from './Image/Image'; -export { default as Link } from './Link/Link'; export { default as RadioButton } from './RadioButton/RadioButton'; export { default as Spinner } from './Spinner/Spinner'; export { default as TextGroup } from './TextGroup/TextGroup';