From c0fe8824c44a6aca8b5e32a19921a3f70214eca1 Mon Sep 17 00:00:00 2001 From: igorbrasileiro Date: Mon, 13 Sep 2021 15:53:49 -0300 Subject: [PATCH 01/16] Add Modal component --- packages/store-ui/package.json | 1 + packages/store-ui/src/index.ts | 3 + .../store-ui/src/molecules/Modal/Modal.tsx | 32 ++++++ .../store-ui/src/molecules/Modal/index.tsx | 2 + yarn.lock | 105 +++++++++++++++++- 5 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 packages/store-ui/src/molecules/Modal/Modal.tsx create mode 100644 packages/store-ui/src/molecules/Modal/index.tsx diff --git a/packages/store-ui/package.json b/packages/store-ui/package.json index b07e754fe2..b2a5ae388d 100644 --- a/packages/store-ui/package.json +++ b/packages/store-ui/package.json @@ -51,6 +51,7 @@ } ], "dependencies": { + "@reach/dialog": "^0.16.0", "@reach/popover": "^0.16.0", "react-swipeable": "^6.1.2" }, diff --git a/packages/store-ui/src/index.ts b/packages/store-ui/src/index.ts index 6a75f5138f..73a0f0d613 100644 --- a/packages/store-ui/src/index.ts +++ b/packages/store-ui/src/index.ts @@ -57,6 +57,9 @@ export type { CarouselProps } from './molecules/Carousel' export { default as IconButton } from './molecules/IconButton' export type { IconButtonProps } from './molecules/IconButton' +export { default as Modal } from './molecules/Modal' +export type { ModalProps } from './molecules/Modal' + // Hooks export { default as useSlider } from './hooks/useSlider' export type { diff --git a/packages/store-ui/src/molecules/Modal/Modal.tsx b/packages/store-ui/src/molecules/Modal/Modal.tsx new file mode 100644 index 0000000000..1ba65f9ba8 --- /dev/null +++ b/packages/store-ui/src/molecules/Modal/Modal.tsx @@ -0,0 +1,32 @@ +import type { PropsWithChildren } from 'react' +import React from 'react' +import type { DialogProps } from '@reach/dialog' +import { Dialog } from '@reach/dialog' + +export interface Props extends DialogProps { + /** + * ID to find this component in testing tools (e.g.: cypress, testing library, and jest). + */ + testId?: string + /** + * Set the modal state. + */ + isOpen?: boolean +} + +const Modal = ({ + isOpen, + children, + testId = 'store-modal', + ...props +}: PropsWithChildren) => { + return ( + isOpen && ( + + {children} + + ) + ) +} + +export default Modal diff --git a/packages/store-ui/src/molecules/Modal/index.tsx b/packages/store-ui/src/molecules/Modal/index.tsx new file mode 100644 index 0000000000..8ca8fb52ef --- /dev/null +++ b/packages/store-ui/src/molecules/Modal/index.tsx @@ -0,0 +1,2 @@ +export { default } from './Modal' +export type { Props as ModalProps } from './Modal' diff --git a/yarn.lock b/yarn.lock index 60e7b041f4..4f0b0e119c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2178,6 +2178,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.12.13": + version "7.15.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a" + integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@^7.13.10", "@babel/runtime@^7.13.17", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.7.6", "@babel/runtime@^7.9.6": version "7.13.17" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.17.tgz#8966d1fc9593bf848602f0662d6b4d0069e3a7ec" @@ -4415,6 +4422,18 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353" integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q== +"@reach/dialog@^0.16.0": + version "0.16.0" + resolved "https://registry.yarnpkg.com/@reach/dialog/-/dialog-0.16.0.tgz#66651868bb6b149fac923f40952355252da05d53" + 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" + "@reach/observe-rect@1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.2.0.tgz#d7a6013b8aafcc64c778a0ccb83355a11204d3b2" @@ -10608,6 +10627,11 @@ detect-newline@^3.0.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== +detect-node-es@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" + integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== + detect-node@^2.0.4: version "2.0.5" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.5.tgz#9d270aa7eaa5af0b72c4c9d9b814e7f4ce738b79" @@ -12570,10 +12594,24 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" +<<<<<<< HEAD follow-redirects@^1.0.0, follow-redirects@^1.14.0: version "1.14.3" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.3.tgz#6ada78118d8d24caee595595accdc0ac6abd022e" integrity sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw== +======= +focus-lock@^0.9.1: + version "0.9.2" + resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.9.2.tgz#9d30918aaa99b1b97677731053d017f82a540d5b" + integrity sha512-YtHxjX7a0IC0ZACL5wsX8QdncXofWpGPNoVMuI/nZUrPGp6LmNI6+D5j0pPj+v8Kw5EpweA+T5yImK0rnWf7oQ== + dependencies: + tslib "^2.0.3" + +follow-redirects@^1.0.0, follow-redirects@^1.10.0: + version "1.13.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267" + integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA== +>>>>>>> 861553ebc (Add Modal component) for-each@^0.3.3: version "0.3.3" @@ -13674,6 +13712,11 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" +get-nonce@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" + integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== + get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" @@ -20926,6 +20969,13 @@ rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-clientside-effect@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.5.tgz#e2c4dc3c9ee109f642fac4f5b6e9bf5bcd2219a3" + integrity sha512-2bL8qFW1TGBHozGGbVeyvnggRpMjibeZM2536AKNENLECutp2yfs44IL8Hmpn8qjFQ2K7A9PnYf3vc7aQq/cPA== + dependencies: + "@babel/runtime" "^7.12.13" + react-colorful@^5.1.2: version "5.2.3" resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.2.3.tgz#579faf42542e32645c5dee66c8530292b2d61646" @@ -21036,6 +21086,18 @@ react-fast-compare@^3.0.1, react-fast-compare@^3.2.0: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== +react-focus-lock@^2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.5.2.tgz#f1e4db5e25cd8789351f2bd5ebe91e9dcb9c2922" + integrity sha512-WzpdOnEqjf+/A3EH9opMZWauag7gV0BxFl+EY4ElA4qFqYsUsBLnmo2sELbN5OC30S16GAWMy16B9DLPpdJKAQ== + dependencies: + "@babel/runtime" "^7.0.0" + focus-lock "^0.9.1" + prop-types "^15.6.2" + react-clientside-effect "^1.2.5" + use-callback-ref "^1.2.5" + use-sidecar "^1.0.5" + react-helmet-async@^1.0.7: version "1.0.9" resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.0.9.tgz#5b9ed2059de6b4aab47f769532f9fbcbce16c5ca" @@ -21098,6 +21160,25 @@ react-refresh@^0.9.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.9.0.tgz#71863337adc3e5c2f8a6bfddd12ae3bfe32aafbf" integrity sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ== +react-remove-scroll-bar@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.2.0.tgz#d4d545a7df024f75d67e151499a6ab5ac97c8cdd" + integrity sha512-UU9ZBP1wdMR8qoUs7owiVcpaPwsQxUDC2lypP6mmixaGlARZa7ZIBx1jcuObLdhMOvCsnZcvetOho0wzPa9PYg== + dependencies: + react-style-singleton "^2.1.0" + tslib "^1.0.0" + +react-remove-scroll@^2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.4.3.tgz#83d19b02503b04bd8141ed6e0b9e6691a2e935a6" + 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" + react-sizeme@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-3.0.1.tgz#4d12f4244e0e6a0fb97253e7af0314dc7c83a5a0" @@ -21108,6 +21189,15 @@ react-sizeme@^3.0.1: shallowequal "^1.1.0" throttle-debounce "^3.0.1" +react-style-singleton@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.1.1.tgz#ce7f90b67618be2b6b94902a30aaea152ce52e66" + integrity sha512-jNRp07Jza6CBqdRKNgGhT3u9umWvils1xsuMOjZlghBDH2MU0PL2WZor4PGYjXpnRCa9DQSlHMs/xnABWOwYbA== + dependencies: + get-nonce "^1.0.0" + invariant "^2.2.4" + tslib "^1.0.0" + react-swipeable@^6.1.2: version "6.1.2" resolved "https://registry.yarnpkg.com/react-swipeable/-/react-swipeable-6.1.2.tgz#cdaec61e1ea449fe03ccb06f7f6bcddd6a252317" @@ -24107,7 +24197,7 @@ tslib@2.0.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e" integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ== -tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: +tslib@^1.0.0, tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -24665,6 +24755,11 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" +use-callback-ref@^1.2.3, use-callback-ref@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.5.tgz#6115ed242cfbaed5915499c0a9842ca2912f38a5" + integrity sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg== + use-composed-ref@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.1.0.tgz#9220e4e94a97b7b02d7d27eaeab0b37034438bbc" @@ -24684,6 +24779,14 @@ use-latest@^1.0.0: dependencies: use-isomorphic-layout-effect "^1.0.0" +use-sidecar@^1.0.1, use-sidecar@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.5.tgz#ffff2a17c1df42e348624b699ba6e5c220527f2b" + integrity sha512-k9jnrjYNwN6xYLj1iaGhonDghfvmeTmYjAiGvOr7clwKfPjMXJf4/HOr7oT5tJwYafgp2tG2l3eZEOfoELiMcA== + dependencies: + detect-node-es "^1.1.0" + tslib "^1.9.3" + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" From fe99fa58f15910c33eda1ae041ad5a585f1ef5a5 Mon Sep 17 00:00:00 2001 From: igorbrasileiro Date: Mon, 20 Sep 2021 16:01:38 -0300 Subject: [PATCH 02/16] Modal Molecule, tests and story --- packages/store-ui/package.json | 5 +- .../src/molecules/Modal/Modal.test.tsx | 144 ++++++++++++++++++ .../store-ui/src/molecules/Modal/Modal.tsx | 136 +++++++++++++++-- .../store-ui/src/molecules/Modal/index.tsx | 2 +- .../src/molecules/Modal/stories/Modal.mdx | 20 +++ .../molecules/Modal/stories/Modal.stories.tsx | 45 ++++++ .../src/molecules/Modal/useTrapFocus.ts | 103 +++++++++++++ .../theme-b2c-tailwind/src/atoms/overlay.css | 2 +- .../src/molecules/index.css | 1 + .../src/molecules/modal.css | 9 ++ yarn.lock | 122 +++------------ 11 files changed, 464 insertions(+), 125 deletions(-) create mode 100644 packages/store-ui/src/molecules/Modal/Modal.test.tsx create mode 100644 packages/store-ui/src/molecules/Modal/stories/Modal.mdx create mode 100644 packages/store-ui/src/molecules/Modal/stories/Modal.stories.tsx create mode 100644 packages/store-ui/src/molecules/Modal/useTrapFocus.ts create mode 100644 themes/theme-b2c-tailwind/src/molecules/modal.css diff --git a/packages/store-ui/package.json b/packages/store-ui/package.json index b2a5ae388d..794cb528a6 100644 --- a/packages/store-ui/package.json +++ b/packages/store-ui/package.json @@ -51,9 +51,9 @@ } ], "dependencies": { - "@reach/dialog": "^0.16.0", "@reach/popover": "^0.16.0", - "react-swipeable": "^6.1.2" + "react-swipeable": "^6.1.2", + "tabbable": "^5.2.1" }, "peerDependencies": { "react": "^17.0.2", @@ -71,6 +71,7 @@ "@testing-library/jest-dom": "^5.12.0", "@testing-library/react": "^11.2.6", "@testing-library/user-event": "^13.1.8", + "@types/tabbable": "^3.1.1", "@types/testing-library__jest-dom": "^5.9.5", "@vtex/theme-b2c-tailwind": "^1.1.11", "@vtex/tsconfig": "^0.5.0", diff --git a/packages/store-ui/src/molecules/Modal/Modal.test.tsx b/packages/store-ui/src/molecules/Modal/Modal.test.tsx new file mode 100644 index 0000000000..9b1f3cbbab --- /dev/null +++ b/packages/store-ui/src/molecules/Modal/Modal.test.tsx @@ -0,0 +1,144 @@ +import React, { useState } from 'react' +import { fireEvent, render } from '@testing-library/react' + +import Modal from './Modal' +import Button from '../../atoms/Button' +import Input from '../../atoms/Input' + +const modalTestId = 'store-modal' + +const TestModal = () => { + const [isOpen, setIsOpen] = useState(false) + const handleOpen = () => { + setIsOpen(true) + } + + const onDismiss = () => { + setIsOpen(false) + } + + return ( + <> + + + + + + My Modal Content +
+
+ +
+
+ + ) +} + +export const Modal = ModalTemplate.bind({}) + +export default { + title: 'Molecules/Modal', + parameters: { + docs: { + page: mdx, + }, + }, +} as Meta diff --git a/packages/store-ui/src/molecules/Modal/useTrapFocus.ts b/packages/store-ui/src/molecules/Modal/useTrapFocus.ts new file mode 100644 index 0000000000..522851b939 --- /dev/null +++ b/packages/store-ui/src/molecules/Modal/useTrapFocus.ts @@ -0,0 +1,103 @@ +import { useEffect, useRef } from 'react' +import type { RefObject } from 'react' +import type { FocusableElement } from 'tabbable' +import { tabbable } from 'tabbable' + +interface TrapFocusParams { + sentinelStartRef: RefObject + trapFocusRef: RefObject + sentinelEndRef: RefObject +} + +/** + * Element that will maintain the focus inside, focus the first element, + * and focus back on the element that was in focus when useTrapFocus was triggered. + * + * Inspired by Material UI TrapFocus https://github.com/mui-org/material-ui/blob/master/packages/mui-core/src/Unstable_TrapFocus/Unstable_TrapFocus.js + */ +const useTrapFocus = ({ + trapFocusRef, + sentinelStartRef, + sentinelEndRef, +}: TrapFocusParams) => { + const tabbableNodesRef = useRef() + const lastKeyDownRef = useRef() + + const nodeToRestoreRef = useRef( + document.hasFocus() ? (document.activeElement as HTMLElement) : null + ) + + // Focus back on the element that was focused when useTrapFocus is triggered. + useEffect(() => { + const nodeToRestore = nodeToRestoreRef.current + + return () => { + nodeToRestore?.focus() + } + }, [nodeToRestoreRef]) + + // Set focus on first tababble element + useEffect(() => { + if (!trapFocusRef.current) { + return + } + + if (!tabbableNodesRef.current) { + tabbableNodesRef.current = tabbable(trapFocusRef.current) + } + + tabbableNodesRef.current[0]?.focus() + }, [trapFocusRef]) + + // Handle loop focus. Set keydown and focusin event listeners + useEffect(() => { + const handleLoopFocus = (nativeEvent: FocusEvent) => { + if ( + lastKeyDownRef.current?.key !== 'Tab' || + !trapFocusRef.current || + !document.hasFocus() + ) { + return + } + + if (trapFocusRef.current.contains(nativeEvent.target as Node)) { + return + } + + tabbableNodesRef.current = tabbable(trapFocusRef.current) + + /* + * Handle loop focus from sentinelStartRef. This node can only be focused if the user press shift tab. + * It will focus the last element of the trapFocusRef. + */ + if ( + nativeEvent.target === sentinelStartRef.current && + lastKeyDownRef.current.shiftKey + ) { + tabbableNodesRef.current[tabbableNodesRef.current.length - 1]?.focus() + } + + /* + * Handle loop focus from sentinelEndRef. This node can only be focused if the user press tab. + * It will focus the first element of the trapFocusRef. + */ + if (nativeEvent.target === sentinelEndRef.current) { + tabbableNodesRef.current[0]?.focus() + } + } + + const handleKeyDown = (nativeEvent: KeyboardEvent) => { + lastKeyDownRef.current = nativeEvent + } + + document.addEventListener('focusin', handleLoopFocus) + document.addEventListener('keydown', handleKeyDown, true) + + return () => { + document.removeEventListener('focusin', handleLoopFocus) + document.removeEventListener('keydown', handleKeyDown, true) + } + }, [tabbableNodesRef, sentinelEndRef, sentinelStartRef, trapFocusRef]) +} + +export default useTrapFocus diff --git a/themes/theme-b2c-tailwind/src/atoms/overlay.css b/themes/theme-b2c-tailwind/src/atoms/overlay.css index 34e5bb963e..749f4ada88 100644 --- a/themes/theme-b2c-tailwind/src/atoms/overlay.css +++ b/themes/theme-b2c-tailwind/src/atoms/overlay.css @@ -11,6 +11,6 @@ @apply bg-green-500 bg-opacity-50; } -[data-store-overlay][data-black] { +[data-store-overlay][data-black], [data-modal-overlay] { @apply bg-gray-500 bg-opacity-50; } diff --git a/themes/theme-b2c-tailwind/src/molecules/index.css b/themes/theme-b2c-tailwind/src/molecules/index.css index 200fb0f606..4a0cf3dd5d 100644 --- a/themes/theme-b2c-tailwind/src/molecules/index.css +++ b/themes/theme-b2c-tailwind/src/molecules/index.css @@ -3,3 +3,4 @@ @import "./icon-button.css"; @import './price-range.css'; @import "./carousel.css"; +@import "./modal.css"; diff --git a/themes/theme-b2c-tailwind/src/molecules/modal.css b/themes/theme-b2c-tailwind/src/molecules/modal.css new file mode 100644 index 0000000000..845fc23e8e --- /dev/null +++ b/themes/theme-b2c-tailwind/src/molecules/modal.css @@ -0,0 +1,9 @@ +[data-store-modal] { + @apply bg-white w-96 font-sans; + @apply p-5 rounded; +} + +[data-store-modal] > [data-action-container] { + @apply flex justify-end; +} + diff --git a/yarn.lock b/yarn.lock index 4f0b0e119c..3874473e22 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2178,13 +2178,6 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.12.13": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a" - integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/runtime@^7.13.10", "@babel/runtime@^7.13.17", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.7.6", "@babel/runtime@^7.9.6": version "7.13.17" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.17.tgz#8966d1fc9593bf848602f0662d6b4d0069e3a7ec" @@ -4422,18 +4415,6 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353" integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q== -"@reach/dialog@^0.16.0": - version "0.16.0" - resolved "https://registry.yarnpkg.com/@reach/dialog/-/dialog-0.16.0.tgz#66651868bb6b149fac923f40952355252da05d53" - 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" - "@reach/observe-rect@1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.2.0.tgz#d7a6013b8aafcc64c778a0ccb83355a11204d3b2" @@ -6352,6 +6333,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== +"@types/tabbable@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@types/tabbable/-/tabbable-3.1.1.tgz#af752e3ca8f4062fd4eccb2a8aa1394ceb5d62dc" + integrity sha512-W7XRO21VJ3Mh8YQWsjqfp6ooBNn5v+Njs1IahnElCv2g8zufYDQMxIS2seXbpmK/2HCdBQItW+xIimcPe1q+5g== + "@types/tapable@^1", "@types/tapable@^1.0.5": version "1.0.7" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.7.tgz#545158342f949e8fd3bfd813224971ecddc3fac4" @@ -10627,11 +10613,6 @@ detect-newline@^3.0.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -detect-node-es@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" - integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== - detect-node@^2.0.4: version "2.0.5" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.5.tgz#9d270aa7eaa5af0b72c4c9d9b814e7f4ce738b79" @@ -12594,24 +12575,15 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" -<<<<<<< HEAD -follow-redirects@^1.0.0, follow-redirects@^1.14.0: - version "1.14.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.3.tgz#6ada78118d8d24caee595595accdc0ac6abd022e" - integrity sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw== -======= -focus-lock@^0.9.1: - version "0.9.2" - resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.9.2.tgz#9d30918aaa99b1b97677731053d017f82a540d5b" - integrity sha512-YtHxjX7a0IC0ZACL5wsX8QdncXofWpGPNoVMuI/nZUrPGp6LmNI6+D5j0pPj+v8Kw5EpweA+T5yImK0rnWf7oQ== - dependencies: - tslib "^2.0.3" - -follow-redirects@^1.0.0, follow-redirects@^1.10.0: +follow-redirects@^1.0.0: version "1.13.3" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267" integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA== ->>>>>>> 861553ebc (Add Modal component) + +follow-redirects@^1.14.0: + version "1.14.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.3.tgz#6ada78118d8d24caee595595accdc0ac6abd022e" + integrity sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw== for-each@^0.3.3: version "0.3.3" @@ -13712,11 +13684,6 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" -get-nonce@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" - integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== - get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" @@ -20969,13 +20936,6 @@ rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-clientside-effect@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.5.tgz#e2c4dc3c9ee109f642fac4f5b6e9bf5bcd2219a3" - integrity sha512-2bL8qFW1TGBHozGGbVeyvnggRpMjibeZM2536AKNENLECutp2yfs44IL8Hmpn8qjFQ2K7A9PnYf3vc7aQq/cPA== - dependencies: - "@babel/runtime" "^7.12.13" - react-colorful@^5.1.2: version "5.2.3" resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.2.3.tgz#579faf42542e32645c5dee66c8530292b2d61646" @@ -21086,18 +21046,6 @@ react-fast-compare@^3.0.1, react-fast-compare@^3.2.0: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== -react-focus-lock@^2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.5.2.tgz#f1e4db5e25cd8789351f2bd5ebe91e9dcb9c2922" - integrity sha512-WzpdOnEqjf+/A3EH9opMZWauag7gV0BxFl+EY4ElA4qFqYsUsBLnmo2sELbN5OC30S16GAWMy16B9DLPpdJKAQ== - dependencies: - "@babel/runtime" "^7.0.0" - focus-lock "^0.9.1" - prop-types "^15.6.2" - react-clientside-effect "^1.2.5" - use-callback-ref "^1.2.5" - use-sidecar "^1.0.5" - react-helmet-async@^1.0.7: version "1.0.9" resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.0.9.tgz#5b9ed2059de6b4aab47f769532f9fbcbce16c5ca" @@ -21160,25 +21108,6 @@ react-refresh@^0.9.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.9.0.tgz#71863337adc3e5c2f8a6bfddd12ae3bfe32aafbf" integrity sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ== -react-remove-scroll-bar@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.2.0.tgz#d4d545a7df024f75d67e151499a6ab5ac97c8cdd" - integrity sha512-UU9ZBP1wdMR8qoUs7owiVcpaPwsQxUDC2lypP6mmixaGlARZa7ZIBx1jcuObLdhMOvCsnZcvetOho0wzPa9PYg== - dependencies: - react-style-singleton "^2.1.0" - tslib "^1.0.0" - -react-remove-scroll@^2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.4.3.tgz#83d19b02503b04bd8141ed6e0b9e6691a2e935a6" - 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" - react-sizeme@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-3.0.1.tgz#4d12f4244e0e6a0fb97253e7af0314dc7c83a5a0" @@ -21189,15 +21118,6 @@ react-sizeme@^3.0.1: shallowequal "^1.1.0" throttle-debounce "^3.0.1" -react-style-singleton@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.1.1.tgz#ce7f90b67618be2b6b94902a30aaea152ce52e66" - integrity sha512-jNRp07Jza6CBqdRKNgGhT3u9umWvils1xsuMOjZlghBDH2MU0PL2WZor4PGYjXpnRCa9DQSlHMs/xnABWOwYbA== - dependencies: - get-nonce "^1.0.0" - invariant "^2.2.4" - tslib "^1.0.0" - react-swipeable@^6.1.2: version "6.1.2" resolved "https://registry.yarnpkg.com/react-swipeable/-/react-swipeable-6.1.2.tgz#cdaec61e1ea449fe03ccb06f7f6bcddd6a252317" @@ -23547,6 +23467,11 @@ tabbable@^4.0.0: resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-4.0.0.tgz#5bff1d1135df1482cf0f0206434f15eadbeb9261" integrity sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ== +tabbable@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-5.2.1.tgz#e3fda7367ddbb172dcda9f871c0fdb36d1c4cd9c" + integrity sha512-40pEZ2mhjaZzK0BnI+QGNjJO8UYx9pP5v7BGe17SORTO0OEuuaAwQTkAp8whcZvqon44wKFOikD+Al11K3JICQ== + table@^5.2.3: version "5.4.6" resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" @@ -24197,7 +24122,7 @@ tslib@2.0.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e" integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ== -tslib@^1.0.0, tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: +tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -24755,11 +24680,6 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" -use-callback-ref@^1.2.3, use-callback-ref@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.5.tgz#6115ed242cfbaed5915499c0a9842ca2912f38a5" - integrity sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg== - use-composed-ref@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.1.0.tgz#9220e4e94a97b7b02d7d27eaeab0b37034438bbc" @@ -24779,14 +24699,6 @@ use-latest@^1.0.0: dependencies: use-isomorphic-layout-effect "^1.0.0" -use-sidecar@^1.0.1, use-sidecar@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.5.tgz#ffff2a17c1df42e348624b699ba6e5c220527f2b" - integrity sha512-k9jnrjYNwN6xYLj1iaGhonDghfvmeTmYjAiGvOr7clwKfPjMXJf4/HOr7oT5tJwYafgp2tG2l3eZEOfoELiMcA== - dependencies: - detect-node-es "^1.1.0" - tslib "^1.9.3" - use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" From fa157dc1ce05cf2b0234a0f6eec039bcab19b1c4 Mon Sep 17 00:00:00 2001 From: igorbrasileiro Date: Wed, 22 Sep 2021 16:01:01 -0300 Subject: [PATCH 03/16] Refactor to apply pure component pattern --- .../src/molecules/Modal/Modal.test.tsx | 6 +- .../store-ui/src/molecules/Modal/Modal.tsx | 116 ++++++++---------- .../src/molecules/Modal/ModalContent.tsx | 87 +++++++++++++ .../src/molecules/Modal/stories/Modal.mdx | 2 +- .../src/molecules/modal.css | 4 +- 5 files changed, 142 insertions(+), 73 deletions(-) create mode 100644 packages/store-ui/src/molecules/Modal/ModalContent.tsx diff --git a/packages/store-ui/src/molecules/Modal/Modal.test.tsx b/packages/store-ui/src/molecules/Modal/Modal.test.tsx index 9b1f3cbbab..03a5349de4 100644 --- a/packages/store-ui/src/molecules/Modal/Modal.test.tsx +++ b/packages/store-ui/src/molecules/Modal/Modal.test.tsx @@ -31,14 +31,16 @@ const TestModal = () => { } describe('Modal', () => { - it('The attribute data-store-modal should be present', () => { + it('The attribute data-store-modal-content should be present', () => { const { getByTestId } = render( Foo ) - expect(getByTestId('store-modal')).toHaveAttribute('data-store-modal') + expect(getByTestId('store-modal')).toHaveAttribute( + 'data-store-modal-content' + ) }) it('Test isOpen', () => { diff --git a/packages/store-ui/src/molecules/Modal/Modal.tsx b/packages/store-ui/src/molecules/Modal/Modal.tsx index b8ad8ae144..2ff1785847 100644 --- a/packages/store-ui/src/molecules/Modal/Modal.tsx +++ b/packages/store-ui/src/molecules/Modal/Modal.tsx @@ -1,93 +1,78 @@ -/* eslint-disable jsx-a11y/no-noninteractive-tabindex */ -/* - * Disable the eslint rule to use sentinel html elements that aren't interactive. - */ import type { AriaAttributes, - HTMLAttributes, KeyboardEvent, MouseEvent, PropsWithChildren, } from 'react' -import React, { useRef } from 'react' +import React from 'react' import { createPortal } from 'react-dom' +import type { OverlayProps } from '../../atoms/Overlay' import Overlay from '../../atoms/Overlay' -import useTrapFocus from './useTrapFocus' +import ModalContent from './ModalContent' +import type { ModalContentProps } from './ModalContent' const composeEventHandler = ( - handler: ((event: EventType) => void) | undefined, internalHandler: (event: EventType) => void ) => { return (event: EventType) => { - handler?.(event) if (!event.defaultPrevented) { internalHandler(event) } } } -interface ModalContentProps extends HTMLAttributes { +interface ModalPureProps extends ModalContentProps { + /** + * ID to find this component in testing tools (e.g.: cypress, testing library, and jest). + */ testId?: string - onDismiss?: (event: MouseEvent | KeyboardEvent) => void + /** + * Identifies the element (or elements) that labels the current element. + * @see aria-labelledby https://www.w3.org/TR/wai-aria-1.1/#aria-labelledby + */ + 'aria-labelledby'?: AriaAttributes['aria-label'] + /** + * on Click handler for the backdrop + */ + onBackdropClick: OverlayProps['onClick'] + /** + * on key down handler for the backdrop + */ + onBackdropKeyDown: OverlayProps['onKeyDown'] } -const ModalContent = ({ children, ...props }: ModalContentProps) => { - const trapFocusRef = useRef(null) - const sentinelStartRef = useRef(null) - const sentinelEndRef = useRef(null) - - useTrapFocus({ - sentinelStartRef, - sentinelEndRef, - trapFocusRef, - }) - +const ModalPure = ({ + testId = 'store-modal', + children, + onBackdropClick, + onBackdropKeyDown, + ...props +}: ModalPureProps) => { return ( - <> -
- {/* - * This next line is disabled due to the onClick prop. - * Even though div isn't clickable, the onClick is required to prevent the event bubbles - * until the overlay, which calls onDismiss inside the onClick handler, is clicked. - */} - {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */} -
{ - event.stopPropagation() - }} - > + + {children} -
-
- + + ) } -export interface ModalProps extends ModalContentProps { - /** - * ID to find this component in testing tools (e.g.: cypress, testing library, and jest). - */ - testId?: string - /** - * Controls whether or not the dialog is open. - */ - isOpen: boolean +export interface ModalProps + extends Omit { /** * This function is called whenever the user hits "Escape" or clicks outside * the dialog. */ onDismiss?: (event: MouseEvent | KeyboardEvent) => void /** - * Identifies the element (or elements) that labels the current element. - * @see aria-labelledby https://www.w3.org/TR/wai-aria-1.1/#aria-labelledby + * Controls whether or not the dialog is open. */ - 'aria-labelledby'?: AriaAttributes['aria-label'] + isOpen: boolean } /** @@ -100,17 +85,14 @@ const Modal = ({ isOpen, children, onDismiss, - testId, - onClick, - onKeyDown, ...props }: PropsWithChildren) => { - const handleClick = (event: MouseEvent) => { + const handleBackdropClick = (event: MouseEvent) => { event.stopPropagation() onDismiss?.(event) } - const handleKeyDown = (event: KeyboardEvent) => { + const handleBackdropKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') { event.stopPropagation() onDismiss?.(event) @@ -119,15 +101,13 @@ const Modal = ({ return isOpen ? createPortal( - - - {children} - - , + {children} + , document.body ) : null diff --git a/packages/store-ui/src/molecules/Modal/ModalContent.tsx b/packages/store-ui/src/molecules/Modal/ModalContent.tsx new file mode 100644 index 0000000000..e821579308 --- /dev/null +++ b/packages/store-ui/src/molecules/Modal/ModalContent.tsx @@ -0,0 +1,87 @@ +/* eslint-disable jsx-a11y/no-noninteractive-tabindex */ +/* + * Disable the eslint rule to use sentinel html elements that aren't interactive. + */ +import type { + DetailedHTMLProps, + HTMLAttributes, + MouseEvent, + RefObject, +} from 'react' +import React, { useRef } from 'react' + +import useTrapFocus from './useTrapFocus' + +interface ModalContentPureProps + extends Omit< + DetailedHTMLProps, HTMLDivElement>, + 'ref' + > { + sentinelStartRef: RefObject + trapFocusRef: RefObject + sentinelEndRef: RefObject +} + +const ModalContentPure = ({ + trapFocusRef, + sentinelStartRef, + sentinelEndRef, + children, + ...props +}: ModalContentPureProps) => { + return ( + <> +
+ {/* + * This next line is disabled due to the onClick prop. + * Even though div isn't clickable, the onClick is required to prevent the event bubbles + * until the overlay, which calls onDismiss inside the onClick handler, is clicked. + */} + {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */} +
+ {children} +
+
+ + ) +} + +export type ModalContentProps = Omit< + ModalContentPureProps, + 'sentinelStartRef' | 'sentinelEndRef' | 'trapFocusRef' +> + +const ModalContent = ({ children, ...props }: ModalContentProps) => { + const trapFocusRef = useRef(null) + const sentinelStartRef = useRef(null) + const sentinelEndRef = useRef(null) + + useTrapFocus({ + sentinelStartRef, + sentinelEndRef, + trapFocusRef, + }) + + return ( + { + event.stopPropagation() + }} + > + {children} + + ) +} + +export default ModalContent diff --git a/packages/store-ui/src/molecules/Modal/stories/Modal.mdx b/packages/store-ui/src/molecules/Modal/stories/Modal.mdx index 802d6ea399..c616454bc6 100644 --- a/packages/store-ui/src/molecules/Modal/stories/Modal.mdx +++ b/packages/store-ui/src/molecules/Modal/stories/Modal.mdx @@ -14,7 +14,7 @@ import Modal from '../Modal' ## CSS Selectors ```css -[data-store-modal] {} +[data-store-modal-content] {} [data-modal-overlay] {} ``` diff --git a/themes/theme-b2c-tailwind/src/molecules/modal.css b/themes/theme-b2c-tailwind/src/molecules/modal.css index 845fc23e8e..08ad79015d 100644 --- a/themes/theme-b2c-tailwind/src/molecules/modal.css +++ b/themes/theme-b2c-tailwind/src/molecules/modal.css @@ -1,9 +1,9 @@ -[data-store-modal] { +[data-store-modal-content] { @apply bg-white w-96 font-sans; @apply p-5 rounded; } -[data-store-modal] > [data-action-container] { +[data-store-modal-content] > [data-action-container] { @apply flex justify-end; } From e6e0e2b22518bbbc47798372bc93344ff0257817 Mon Sep 17 00:00:00 2001 From: igorbrasileiro Date: Wed, 22 Sep 2021 17:30:45 -0300 Subject: [PATCH 04/16] Add tests for modal inside another modal --- .../src/molecules/Modal/Modal.test.tsx | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/packages/store-ui/src/molecules/Modal/Modal.test.tsx b/packages/store-ui/src/molecules/Modal/Modal.test.tsx index 03a5349de4..90651a44d5 100644 --- a/packages/store-ui/src/molecules/Modal/Modal.test.tsx +++ b/packages/store-ui/src/molecules/Modal/Modal.test.tsx @@ -1,3 +1,4 @@ +import type { ReactNode } from 'react' import React, { useState } from 'react' import { fireEvent, render } from '@testing-library/react' @@ -7,7 +8,7 @@ import Input from '../../atoms/Input' const modalTestId = 'store-modal' -const TestModal = () => { +const TestModal = ({ children }: { children?: ReactNode }) => { const [isOpen, setIsOpen] = useState(false) const handleOpen = () => { setIsOpen(true) @@ -25,6 +26,7 @@ const TestModal = () => {