diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eafae0bd..1dba3e2e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ Reactist follows [semantic versioning](https://semver.org/) and doesn't introduce breaking changes (API-wise) in minor or patch releases. However, the appearance of a component might change in a minor or patch release so keep an eye on redesigns and make sure your app still looks and feels like you expect it. +# v22.0.0 + +- [BREAKING] Remove `DeprecatedModal`. + # v21.3.0 - [Feat] `ToastProvider` accepts an optional `containerClassName` property, which let's you to add your own class for the container of all toasts. diff --git a/package-lock.json b/package-lock.json index 6b1debc18..95739362f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,15 @@ { "name": "@doist/reactist", - "version": "21.3.0", + "version": "22.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@doist/reactist", - "version": "21.3.0", + "version": "22.0.0", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@reach/dialog": "^0.16.0", "aria-hidden": "^1.2.1", "ariakit": "2.0.0-next.43", "ariakit-react-utils": "0.17.0-next.27", @@ -4091,121 +4090,6 @@ "node": ">=10" } }, - "node_modules/@reach/dialog": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@reach/dialog/-/dialog-0.16.0.tgz", - "integrity": "sha512-EsQ6wPafXHkny7ATihGCCJuVb6bo8MzzFvsDY+BNLeUt4XN3Kcq1oe5xJbXfxbm1Ljzs80bqF3E/Q16O12fPng==", - "dependencies": { - "@reach/portal": "0.16.0", - "@reach/utils": "0.16.0", - "prop-types": "^15.7.2", - "react-focus-lock": "^2.5.2", - "react-remove-scroll": "^2.4.3", - "tslib": "^2.3.0" - }, - "peerDependencies": { - "react": "^16.8.0 || 17.x", - "react-dom": "^16.8.0 || 17.x" - } - }, - "node_modules/@reach/dialog/node_modules/react-remove-scroll": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.4.3.tgz", - "integrity": "sha512-lGWYXfV6jykJwbFpsuPdexKKzp96f3RbvGapDSIdcyGvHb7/eqyn46C7/6h+rUzYar1j5mdU+XECITHXCKBk9Q==", - "dependencies": { - "react-remove-scroll-bar": "^2.1.0", - "react-style-singleton": "^2.1.0", - "tslib": "^1.0.0", - "use-callback-ref": "^1.2.3", - "use-sidecar": "^1.0.1" - }, - "engines": { - "node": ">=8.5.0" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@reach/dialog/node_modules/react-remove-scroll/node_modules/react-remove-scroll-bar": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.2.0.tgz", - "integrity": "sha512-UU9ZBP1wdMR8qoUs7owiVcpaPwsQxUDC2lypP6mmixaGlARZa7ZIBx1jcuObLdhMOvCsnZcvetOho0wzPa9PYg==", - "dependencies": { - "react-style-singleton": "^2.1.0", - "tslib": "^1.0.0" - }, - "engines": { - "node": ">=8.5.0" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@reach/dialog/node_modules/react-remove-scroll/node_modules/react-style-singleton": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.1.1.tgz", - "integrity": "sha512-jNRp07Jza6CBqdRKNgGhT3u9umWvils1xsuMOjZlghBDH2MU0PL2WZor4PGYjXpnRCa9DQSlHMs/xnABWOwYbA==", - "dependencies": { - "get-nonce": "^1.0.0", - "invariant": "^2.2.4", - "tslib": "^1.0.0" - }, - "engines": { - "node": ">=8.5.0" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@reach/dialog/node_modules/react-remove-scroll/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@reach/portal": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.16.0.tgz", - "integrity": "sha512-vXJ0O9T+72HiSEWHPs2cx7YbSO7pQsTMhgqPc5aaddIYpo2clJx1PnYuS0lSNlVaDO0IxQhwYq43evXaXnmviw==", - "dependencies": { - "@reach/utils": "0.16.0", - "tslib": "^2.3.0" - }, - "peerDependencies": { - "react": "^16.8.0 || 17.x", - "react-dom": "^16.8.0 || 17.x" - } - }, - "node_modules/@reach/utils": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.16.0.tgz", - "integrity": "sha512-PCggBet3qaQmwFNcmQ/GqHSefadAFyNCUekq9RrWoaU9hh/S4iaFgf2MBMdM47eQj5i/Bk0Mm07cP/XPFlkN+Q==", - "dependencies": { - "tiny-warning": "^1.0.3", - "tslib": "^2.3.0" - }, - "peerDependencies": { - "react": "^16.8.0 || 17.x", - "react-dom": "^16.8.0 || 17.x" - } - }, "node_modules/@rollup/plugin-commonjs": { "version": "11.1.0", "integrity": "sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==", @@ -17933,14 +17817,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "engines": { - "node": ">=6" - } - }, "node_modules/get-own-enumerable-property-symbols": { "version": "3.0.2", "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", @@ -19450,13 +19326,6 @@ "node": ">= 0.10" } }, - "node_modules/invariant": { - "version": "2.2.4", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/ip": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", @@ -33772,11 +33641,6 @@ "globrex": "^0.1.1" } }, - "node_modules/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==" - }, "node_modules/title-case": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", @@ -41088,77 +40952,6 @@ } } }, - "@reach/dialog": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@reach/dialog/-/dialog-0.16.0.tgz", - "integrity": "sha512-EsQ6wPafXHkny7ATihGCCJuVb6bo8MzzFvsDY+BNLeUt4XN3Kcq1oe5xJbXfxbm1Ljzs80bqF3E/Q16O12fPng==", - "requires": { - "@reach/portal": "0.16.0", - "@reach/utils": "0.16.0", - "prop-types": "^15.7.2", - "react-focus-lock": "^2.5.2", - "react-remove-scroll": "^2.4.3", - "tslib": "^2.3.0" - }, - "dependencies": { - "react-remove-scroll": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.4.3.tgz", - "integrity": "sha512-lGWYXfV6jykJwbFpsuPdexKKzp96f3RbvGapDSIdcyGvHb7/eqyn46C7/6h+rUzYar1j5mdU+XECITHXCKBk9Q==", - "requires": { - "react-remove-scroll-bar": "^2.1.0", - "react-style-singleton": "^2.1.0", - "tslib": "^1.0.0", - "use-callback-ref": "^1.2.3", - "use-sidecar": "^1.0.1" - }, - "dependencies": { - "react-remove-scroll-bar": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.2.0.tgz", - "integrity": "sha512-UU9ZBP1wdMR8qoUs7owiVcpaPwsQxUDC2lypP6mmixaGlARZa7ZIBx1jcuObLdhMOvCsnZcvetOho0wzPa9PYg==", - "requires": { - "react-style-singleton": "^2.1.0", - "tslib": "^1.0.0" - } - }, - "react-style-singleton": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.1.1.tgz", - "integrity": "sha512-jNRp07Jza6CBqdRKNgGhT3u9umWvils1xsuMOjZlghBDH2MU0PL2WZor4PGYjXpnRCa9DQSlHMs/xnABWOwYbA==", - "requires": { - "get-nonce": "^1.0.0", - "invariant": "^2.2.4", - "tslib": "^1.0.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - } - } - }, - "@reach/portal": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.16.0.tgz", - "integrity": "sha512-vXJ0O9T+72HiSEWHPs2cx7YbSO7pQsTMhgqPc5aaddIYpo2clJx1PnYuS0lSNlVaDO0IxQhwYq43evXaXnmviw==", - "requires": { - "@reach/utils": "0.16.0", - "tslib": "^2.3.0" - } - }, - "@reach/utils": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.16.0.tgz", - "integrity": "sha512-PCggBet3qaQmwFNcmQ/GqHSefadAFyNCUekq9RrWoaU9hh/S4iaFgf2MBMdM47eQj5i/Bk0Mm07cP/XPFlkN+Q==", - "requires": { - "tiny-warning": "^1.0.3", - "tslib": "^2.3.0" - } - }, "@rollup/plugin-commonjs": { "version": "11.1.0", "integrity": "sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==", @@ -51651,11 +51444,6 @@ "has-symbols": "^1.0.3" } }, - "get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==" - }, "get-own-enumerable-property-symbols": { "version": "3.0.2", "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", @@ -52806,13 +52594,6 @@ "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", "dev": true }, - "invariant": { - "version": "2.2.4", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "requires": { - "loose-envify": "^1.0.0" - } - }, "ip": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", @@ -63736,11 +63517,6 @@ "globrex": "^0.1.1" } }, - "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==" - }, "title-case": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", diff --git a/package.json b/package.json index 76d2acfda..933c7ed66 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "email": "henning@doist.com", "url": "http://doist.com" }, - "version": "21.3.0", + "version": "22.0.0", "license": "MIT", "homepage": "https://github.com/Doist/reactist#readme", "repository": { @@ -143,7 +143,6 @@ "webpack": "^4.43.0" }, "dependencies": { - "@reach/dialog": "^0.16.0", "aria-hidden": "^1.2.1", "ariakit": "2.0.0-next.43", "ariakit-react-utils": "0.17.0-next.27", diff --git a/src/deprecated-modal/index.ts b/src/deprecated-modal/index.ts deleted file mode 100644 index cdbd4fb06..000000000 --- a/src/deprecated-modal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './modal' diff --git a/src/deprecated-modal/modal.module.css b/src/deprecated-modal/modal.module.css deleted file mode 100644 index c2581081e..000000000 --- a/src/deprecated-modal/modal.module.css +++ /dev/null @@ -1,144 +0,0 @@ -@keyframes fadein { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -:root { - --reach-dialog: 1; - --reactist-modal-overlay-fill: rgba(0, 0, 0, 0.5); - --reactist-modal-padding-top: 13vh; -} - -.reach-portal { - isolation: isolate; -} - -[data-reach-dialog-overlay] { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - overflow: hidden; - background-color: var(--reactist-modal-overlay-fill); - animation: fadein 0.2s; - animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition: background-color 0.5s; - display: flex; - align-items: center; - justify-content: center; - z-index: var(--reactist-stacking-order-modal); -} - -[data-reach-dialog-overlay] > [data-focus-lock-disabled] { - display: flex; - flex-direction: column; - align-items: center; - width: 100%; - height: 100%; - box-sizing: border-box; - padding: var(--reactist-spacing-xxlarge); -} - -[data-reach-dialog-overlay].fitContent > [data-focus-lock-disabled] { - padding-top: var(--reactist-modal-padding-top); -} -[data-reach-dialog-overlay].fitContent > [data-focus-lock-disabled] .container { - max-height: calc(100vh - 2 * var(--reactist-modal-padding-top)); -} - -[data-reach-dialog-content] { - background: var(--reactist-bg-default); - padding: 0; - outline: none; - transition: box-shadow 0.5s; -} - -.container { - box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.16); - transition: width 0.2s ease-in-out; - max-width: 100%; -} - -/* responsive styles */ - -.full .container { - width: 100%; -} -.large .container { - width: 768px; -} -.medium .container { - width: 580px; -} -.small .container { - width: 450px; -} - -@media (min-width: 992px) { - .xlarge .container { - width: 960px; - } -} - -@media (min-width: 1200px) { - .xlarge .container { - width: 1060px; - } -} - -@media (max-width: 1000px) { - .xlarge .container { - width: 768px; - } -} - -@media (max-width: 580px) { - .overlay:not(.small) .container { - width: 100% !important; - max-height: none; - } - .overlay.expand:not(.small) > [data-focus-lock-disabled] { - padding-left: 0; - padding-right: 0; - padding-bottom: 0; - } - .overlay.expand:not(.small) .container { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } -} - -@media (max-width: 400px) { - .container { - width: 100% !important; - max-height: none; - } - .overlay.expand > [data-focus-lock-disabled] { - padding-left: 0; - padding-right: 0; - padding-bottom: 0; - } - .overlay.expand .container { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } -} - -/* header */ - -.buttonContainer { - display: flex; - align-items: center; - height: 32px; -} - -.headerContent { - /* This ensures that the min-height is always applied to the header content, */ - /* regardless of the modal having a button or not. */ - min-height: 32px; /* must be the same as the .buttonContainer height above */ -} diff --git a/src/deprecated-modal/modal.test.tsx b/src/deprecated-modal/modal.test.tsx deleted file mode 100644 index 1d5a3288f..000000000 --- a/src/deprecated-modal/modal.test.tsx +++ /dev/null @@ -1,433 +0,0 @@ -import * as React from 'react' -import { render, screen, within } from '@testing-library/react' -import { - DeprecatedModal, - DeprecatedModalHeader, - DeprecatedModalFooter, - DeprecatedModalActions, - DeprecatedModalBody, - DeprecatedModalCloseButton, -} from './modal' -import userEvent from '@testing-library/user-event' -import { axe } from 'jest-axe' - -describe('Modal', () => { - function TestCaseWithState() { - const [isOpen, setOpen] = React.useState(false) - return ( - <> - - setOpen(false)} - aria-label="modal" - > - - - - - ) - } - - it('renders a semantic accessible dialog', () => { - render( - - Hello - , - ) - expect(screen.getByRole('dialog', { name: "I'm an accessible dialog" })).toBeInTheDocument() - }) - - it('does not acknowledge the className prop, but exceptionallySetClassName instead', () => { - render( - - Hello - , - ) - const modal = screen.getByRole('dialog', { name: 'modal' }) - expect(modal).toHaveClass('right') - expect(modal).not.toHaveClass('wrong') - }) - - it('renders its children as its content', () => { - render( - -
one
-
two
-
, - ) - const modal = screen.getByRole('dialog', { name: 'modal' }) - expect(modal.innerHTML).toMatchInlineSnapshot(`"
one
two
"`) - }) - - it('is dismissed if isOpen="false"', () => { - render() - expect(screen.queryByRole('dialog', { name: 'modal' })).not.toBeInTheDocument() - userEvent.click(screen.getByRole('button', { name: 'Click me' })) - expect(screen.getByRole('dialog', { name: 'modal' })).toBeInTheDocument() - userEvent.click(screen.getByRole('button', { name: 'Close me' })) - expect(screen.queryByRole('dialog', { name: 'modal' })).not.toBeInTheDocument() - }) - - it('hides the content underneath from assistive technologies', () => { - render() - userEvent.click(screen.getByRole('button', { name: 'Click me' })) - - // Button is present, but not found by role - expect(screen.queryByRole('button', { name: 'Click me' })).not.toBeInTheDocument() - expect(screen.getByText('Click me')).toBeInTheDocument() - - // Button is visible by role again once the modal is gone - userEvent.click(screen.getByRole('button', { name: 'Close me' })) - expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument() - }) - - it('is dismissed when clicking in the overlay', () => { - render() - userEvent.click(screen.getByRole('button', { name: 'Click me' })) - expect(screen.getByRole('dialog', { name: 'modal' })).toBeInTheDocument() - userEvent.click(screen.getByTestId('modal-overlay')) - expect(screen.queryByRole('dialog', { name: 'modal' })).not.toBeInTheDocument() - }) -}) - -describe('ModalHeader', () => { - it('renders a semantic banner', () => { - render(Hello) - expect(screen.getByRole('banner')).toBeInTheDocument() - }) - - it('does not acknowledge the className prop, but exceptionallySetClassName instead', () => { - render( - - Hello - , - ) - const modalHeader = screen.getByTestId('modal-header') - expect(modalHeader).toHaveClass('right') - expect(modalHeader).not.toHaveClass('wrong') - }) - - it('renders its children as its content', () => { - render( - -
Hello
-
, - ) - expect( - within(screen.getByTestId('modal-header')).getByTestId('modal-header-content'), - ).toBeInTheDocument() - }) - - it("renders a button that calls the modal's onDismiss callback when clicked", () => { - const onDismiss = jest.fn() - render( - - Hello - , - ) - expect(onDismiss).not.toHaveBeenCalled() - userEvent.click(screen.getByRole('button', { name: 'Close modal' })) - expect(onDismiss).toHaveBeenCalledTimes(1) - }) - - it('allows to render custom content in place of the button', () => { - render( - Help}>Hello, - ) - expect(screen.queryByRole('button', { name: 'Close modal' })).not.toBeInTheDocument() - expect(screen.getByRole('link', { name: 'Help' })).toBeInTheDocument() - }) - - it('hides the button if its content is set to `false` or `null`', () => { - const { rerender } = render( - Hello, - ) - expect(screen.queryByRole('button', { name: 'Close modal' })).not.toBeInTheDocument() - expect(screen.queryByTestId('button-container')).not.toBeInTheDocument() - rerender(Hello) - expect(screen.queryByRole('button', { name: 'Close modal' })).not.toBeInTheDocument() - expect(screen.queryByTestId('button-container')).not.toBeInTheDocument() - }) - - it('optionally renders a divider', () => { - const { rerender } = render(Hello) - expect(screen.queryByRole('separator')).not.toBeInTheDocument() - rerender(Hello) - expect(screen.getByRole('separator')).toBeInTheDocument() - }) -}) - -describe('ModalFooter', () => { - it('renders a semantic contentinfo', () => { - render(Hello) - expect(screen.getByRole('contentinfo')).toBeInTheDocument() - }) - - it('does not acknowledge the className prop, but exceptionallySetClassName instead', () => { - render( - - Hello - , - ) - const modalFooter = screen.getByTestId('modal-footer') - expect(modalFooter).toHaveClass('right') - expect(modalFooter).not.toHaveClass('wrong') - }) - - it('renders its children as its content', () => { - render( - -
Hello
-
, - ) - expect( - within(screen.getByTestId('modal-footer')).getByTestId('modal-footer-content'), - ).toBeInTheDocument() - }) - - it('optionally renders a divider', () => { - const { rerender } = render(Hello) - expect(screen.queryByRole('separator')).not.toBeInTheDocument() - rerender(Hello) - expect(screen.getByRole('separator')).toBeInTheDocument() - }) -}) - -describe('ModalActions', () => { - it('renders a semantic contentinfo', () => { - render(Hello) - expect(screen.getByRole('contentinfo')).toBe(screen.getByTestId('modal-actions')) - }) - - it('does not acknowledge the className prop, but exceptionallySetClassName instead', () => { - render( - - Hello - , - ) - const modalFooter = screen.getByTestId('modal-actions') - expect(modalFooter).toHaveClass('right') - expect(modalFooter).not.toHaveClass('wrong') - }) - - it('renders its children inlined inside it', () => { - render( - - - - , - ) - expect(screen.getByTestId('modal-actions')).toMatchInlineSnapshot(` -
-
- - -
-
- `) - }) - - it('optionally renders a divider', () => { - const { rerender } = render(Hello) - expect(screen.queryByRole('separator')).not.toBeInTheDocument() - rerender(Hello) - expect(screen.getByRole('separator')).toBeInTheDocument() - }) - - it('ignores null children', () => { - render( - - - - {null} - , - ) - expect(screen.getByTestId('modal-actions').firstChild?.childNodes).toHaveLength(2) - }) - - it('flattens fragments', () => { - render( - - <> - - - - - , - ) - expect(screen.getByTestId('modal-actions').firstChild?.childNodes).toHaveLength(3) - }) -}) - -describe('ModalBody', () => { - it('does not acknowledge the className prop, but exceptionallySetClassName instead', () => { - render( - - Hello - , - ) - const modalBody = screen.getByTestId('modal-body') - expect(modalBody).toHaveClass('right') - expect(modalBody).not.toHaveClass('wrong') - }) - - it('renders its children as its content', () => { - render( - -
Hello
-
, - ) - expect( - within(screen.getByTestId('modal-body')).getByTestId('modal-body-content'), - ).toBeInTheDocument() - }) - - it('adapts its styles to the height prop of the outer Modal', () => { - const { rerender } = render( - - Content - , - ) - const modalBody = screen.getByTestId('modal-body') - expect(modalBody).toHaveClass('flexGrow-0') - expect(modalBody).not.toHaveClass('flexGrow-1') - expect(modalBody).not.toHaveClass('height-full') - - rerender( - - Content - , - ) - expect(modalBody).not.toHaveClass('flexGrow-0') - expect(modalBody).toHaveClass('flexGrow-1') - expect(modalBody).toHaveClass('height-full') - }) -}) - -describe('ModalCloseButton', () => { - function renderTestCase() { - const onDismiss = jest.fn() - const renderResult = render( - - } - > - Hello - - , - ) - return { - button: screen.getByRole('button', { name: 'Cerrar ventana' }), - onDismiss, - ...renderResult, - } - } - - it('can be used to render a customized button in place of the default one', () => { - renderTestCase() - expect(screen.queryByRole('button', { name: 'Close modal' })).not.toBeInTheDocument() - expect(screen.getByRole('button', { name: 'Cerrar ventana' })).toBeInTheDocument() - }) - - it("calls the modal's onDismiss callback when clicked", () => { - const { button, onDismiss } = renderTestCase() - expect(onDismiss).not.toHaveBeenCalled() - userEvent.click(button) - expect(onDismiss).toHaveBeenCalledTimes(1) - }) - - it('renders as a regular non-submit button', () => { - const { button } = renderTestCase() - expect(button).toHaveAttribute('type', 'button') - }) - - it('renders a svg icon as its content', () => { - const { button } = renderTestCase() - expect(button.firstElementChild?.tagName).toBe('svg') - }) - - it('does not get focus initially', () => { - const { button } = renderTestCase() - expect(button).not.toHaveFocus() - }) -}) - -describe('a11y', () => { - it('renders with no a11y violations with custom content', async () => { - const { container } = render( - - - - , - ) - const results = await axe(container) - - expect(results).toHaveNoViolations() - }) - - it('renders with no a11y violations when using inner-structure components', async () => { - const { container } = render( - - Header - Body - Footer - , - ) - const results = await axe(container) - - expect(results).toHaveNoViolations() - }) - - it("calls the modal's onDismiss callback when 'Esc' is pressed", () => { - const onDismiss = jest.fn() - render( - - Hello - , - ) - const modal = screen.getByRole('dialog', { name: 'modal' }) - - expect(onDismiss).not.toHaveBeenCalled() - userEvent.type(modal, '{esc}') - expect(onDismiss).toHaveBeenCalledTimes(1) - }) -}) diff --git a/src/deprecated-modal/modal.tsx b/src/deprecated-modal/modal.tsx deleted file mode 100644 index 6440a8a2f..000000000 --- a/src/deprecated-modal/modal.tsx +++ /dev/null @@ -1,385 +0,0 @@ -import * as React from 'react' -import classNames from 'classnames' -import { DialogOverlay, DialogContent } from '@reach/dialog' -import FocusLock from 'react-focus-lock' - -import { CloseIcon } from '../icons/close-icon' -import { Column, Columns } from '../columns' -import { Inline } from '../inline' -import { Divider } from '../divider' -import { Box } from '../box' -import { Button, ButtonProps } from '../button' - -import styles from './modal.module.css' - -type ModalWidth = 'small' | 'medium' | 'large' | 'xlarge' | 'full' -type ModalHeightMode = 'expand' | 'fitContent' - -// -// ModalContext -// - -type ModalContextValue = { - onDismiss?(this: void): void - height: ModalHeightMode -} - -const ModalContext = React.createContext({ - onDismiss: undefined, - height: 'fitContent', -}) - -// -// Modal container -// - -type DivProps = Omit< - React.DetailedHTMLProps, HTMLDivElement>, - 'className' | 'children' | `aria-label` | `aria-labelledby` -> - -export type DeprecatedModalProps = DivProps & { - /** - * The content of the modal. - */ - children: React.ReactNode - /** - * Whether the modal is open and visible or not. - */ - isOpen: boolean - /** - * Called when the user triggers closing the modal. - */ - onDismiss?(): void - /** - * A descriptive setting for how wide the modal should aim to be, depending on how much space - * it has on screen. - * @default 'medium' - */ - width?: ModalWidth - /** - * A descriptive setting for how tall the modal should aim to be. - * - * - 'expand': the modal aims to fill most of the available screen height, leaving only a small - * padding above and below. - * - 'fitContent': the modal shrinks to the smallest size that allow it to fit its content. - * - * In either case, if content does not fit, the content of the main body is set to scroll - * (provided you use `ModalBody`) so that the modal never has to strech vertically beyond the - * viewport boundaries. - * - * If you do not use `ModalBody`, the modal still prevents overflow, and you are in charge of - * the inner layout to ensure scroll, or whatever other strategy you may want. - */ - height?: ModalHeightMode - /** - * Whether to set or not the focus initially to the first focusable element inside the modal. - */ - autoFocus?: boolean - /** - * A escape hatch in case you need to provide a custom class name to the container element. - */ - exceptionallySetClassName?: string - /** Defines a string value that labels the current modal for assistive technologies. */ - 'aria-label'?: string - /** Identifies the element (or elements) that labels the current modal for assistive technologies. */ - 'aria-labelledby'?: string -} - -function isNotInternalFrame(element: HTMLElement) { - return !(element.ownerDocument === document && element.tagName.toLowerCase() === 'iframe') -} - -/** - * Renders a modal that sits on top of the rest of the content in the entire page. - * - * Follows the WAI-ARIA Dialog (Modal) Pattern. - * - * @see DeprecatedModalHeader - * @see DeprecatedModalFooter - * @see DeprecatedModalBody - * @deprecated - */ -export function DeprecatedModal({ - isOpen, - onDismiss, - height = 'fitContent', - width = 'medium', - exceptionallySetClassName, - autoFocus = true, - children, - ...props -}: DeprecatedModalProps) { - const contextValue: ModalContextValue = React.useMemo(() => ({ onDismiss, height }), [ - onDismiss, - height, - ]) - - return ( - - - - {children} - - - - ) -} - -// -// ModalCloseButton -// - -export type DeprecatedModalCloseButtonProps = Omit< - ButtonProps, - | 'type' - | 'children' - | 'variant' - | 'icon' - | 'startIcon' - | 'endIcon' - | 'disabled' - | 'loading' - | 'tabIndex' - | 'width' - | 'align' -> & { - /** - * The descriptive label of the button. - */ - 'aria-label': string -} - -/** - * The close button rendered by ModalHeader. Provided independently so that consumers can customize - * the button's label. - * - * @see DeprecatedModalHeader - */ -export function DeprecatedModalCloseButton(props: DeprecatedModalCloseButtonProps) { - const { onDismiss } = React.useContext(ModalContext) - const [includeInTabOrder, setIncludeInTabOrder] = React.useState(false) - const [isMounted, setIsMounted] = React.useState(false) - - React.useEffect( - function skipAutoFocus() { - if (isMounted) { - setIncludeInTabOrder(true) - } else { - setIsMounted(true) - } - }, - [isMounted], - ) - - return ( -