From 4cfadae0064538c6c21b34de388db3033d2d529c Mon Sep 17 00:00:00 2001 From: RyukTheCoder Date: Sat, 14 Sep 2024 07:06:45 +0000 Subject: [PATCH 1/9] chore: use radix to improve modal component --- widget/ui/package.json | 1 + .../ui/src/components/Modal/Modal.styles.ts | 6 +- widget/ui/src/components/Modal/Modal.tsx | 99 ++++++------- widget/ui/src/components/Modal/Modal.types.ts | 4 +- yarn.lock | 140 ++++++++++++++++++ 5 files changed, 195 insertions(+), 55 deletions(-) diff --git a/widget/ui/package.json b/widget/ui/package.json index dea3341362..ae23c21c16 100644 --- a/widget/ui/package.json +++ b/widget/ui/package.json @@ -48,6 +48,7 @@ "dependencies": { "@radix-ui/react-checkbox": "^1.0.1", "@radix-ui/react-collapsible": "^1.0.3", + "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-popover": "^1.0.6", "@radix-ui/react-radio-group": "^1.1.1", "@radix-ui/react-select": "^2.0.0", diff --git a/widget/ui/src/components/Modal/Modal.styles.ts b/widget/ui/src/components/Modal/Modal.styles.ts index 4b15d5e3ae..da19637d15 100644 --- a/widget/ui/src/components/Modal/Modal.styles.ts +++ b/widget/ui/src/components/Modal/Modal.styles.ts @@ -1,7 +1,9 @@ +import * as Dialog from '@radix-ui/react-dialog'; + import { styled } from '../../theme.js'; import { IconButton } from '../IconButton/index.js'; -export const BackDrop = styled('div', { +export const DialogOverlay = styled(Dialog.Overlay, { position: 'absolute', top: '0', left: '0', @@ -24,7 +26,7 @@ export const BackDrop = styled('div', { }, }); -export const ModalContainer = styled('div', { +export const DialogContent = styled(Dialog.Content, { backgroundColor: '$background', width: '100%', borderRadius: '$primary', diff --git a/widget/ui/src/components/Modal/Modal.tsx b/widget/ui/src/components/Modal/Modal.tsx index 13d7098cf9..8b6e112ed0 100644 --- a/widget/ui/src/components/Modal/Modal.tsx +++ b/widget/ui/src/components/Modal/Modal.tsx @@ -1,8 +1,8 @@ import type { ModalPropTypes } from './Modal.types.js'; import type { PropsWithChildren } from 'react'; +import * as Dialog from '@radix-ui/react-dialog'; import React, { useEffect, useRef, useState } from 'react'; -import { createPortal } from 'react-dom'; import { CloseIcon } from '../../icons/index.js'; import { BottomLogo } from '../BottomLogo/index.js'; @@ -12,11 +12,11 @@ import { Typography } from '../Typography/index.js'; import { forceReflow } from './Modal.helpers.js'; import { - BackDrop, Content, + DialogContent, + DialogOverlay, Flex, Footer, - ModalContainer, ModalHeader, } from './Modal.styles.js'; @@ -48,10 +48,8 @@ export function Modal(props: PropsWithChildren) { const modalContainerRef = useRef(null); const exitCallbackRef = useRef(); - const handleBackDropClick = (event: React.MouseEvent) => { - event.stopPropagation(); - - if (event.target === event.currentTarget && dismissible) { + const handleBackDropClick = (open: boolean) => { + if (dismissible && !open) { onClose(); } }; @@ -109,50 +107,49 @@ export function Modal(props: PropsWithChildren) { } }, [isMount, container]); - if (status === 'unmounted' || !container) { - return null; - } - - return createPortal( - - (modalContainerRef.current = ref)}> - {header ?? ( - - {prefix} - {title && ( - - {title} - - )} - - {suffix} - {dismissible && hasCloseIcon && ( - - - + return ( + + + + (modalContainerRef.current = ref)}> + {header ?? ( + + {prefix} + {title && ( + + {title} + )} - - - )} - {children} - -
- {footer &&
{footer}
} - -
- - -
-
-
-
, - container + + {suffix} + {dismissible && hasCloseIcon && ( + + + + )} + + + )} + {children} +
+ {footer &&
{footer}
} + +
+ + +
+
+ + + ); } diff --git a/widget/ui/src/components/Modal/Modal.types.ts b/widget/ui/src/components/Modal/Modal.types.ts index ef71d57db7..6c1ca8f984 100644 --- a/widget/ui/src/components/Modal/Modal.types.ts +++ b/widget/ui/src/components/Modal/Modal.types.ts @@ -1,8 +1,8 @@ -import type { ModalContainer } from './Modal.styles.js'; +import type { DialogContent } from './Modal.styles.js'; import type { config } from '../../theme.js'; import type * as Stitches from '@stitches/react'; -type BaseProps = Stitches.VariantProps; +type BaseProps = Stitches.VariantProps; type BaseAnchor = Exclude; export interface ModalPropTypes { diff --git a/yarn.lock b/yarn.lock index 5dcc05a558..0e25b5c214 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5267,6 +5267,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/primitive@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.0.tgz#42ef83b3b56dccad5d703ae8c42919a68798bbe2" + integrity sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA== + "@radix-ui/react-arrow@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz#c24f7968996ed934d57fe6cde5d6ec7266e1d25d" @@ -5323,6 +5328,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-compose-refs@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz#656432461fc8283d7b591dcf0d79152fae9ecc74" + integrity sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw== + "@radix-ui/react-context@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.0.1.tgz#fe46e67c96b240de59187dcb7a1a50ce3e2ec00c" @@ -5330,6 +5340,31 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-context@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.0.tgz#6df8d983546cfd1999c8512f3a8ad85a6e7fcee8" + integrity sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A== + +"@radix-ui/react-dialog@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz#4906507f7b4ad31e22d7dad69d9330c87c431d44" + integrity sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-dismissable-layer" "1.1.0" + "@radix-ui/react-focus-guards" "1.1.0" + "@radix-ui/react-focus-scope" "1.1.0" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-portal" "1.1.1" + "@radix-ui/react-presence" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-slot" "1.1.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + aria-hidden "^1.1.1" + react-remove-scroll "2.5.7" + "@radix-ui/react-direction@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.1.tgz#9cb61bf2ccf568f3421422d182637b7f47596c9b" @@ -5349,6 +5384,17 @@ "@radix-ui/react-use-callback-ref" "1.0.1" "@radix-ui/react-use-escape-keydown" "1.0.3" +"@radix-ui/react-dismissable-layer@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz#2cd0a49a732372513733754e6032d3fb7988834e" + integrity sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-use-escape-keydown" "1.1.0" + "@radix-ui/react-focus-guards@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz#1ea7e32092216b946397866199d892f71f7f98ad" @@ -5356,6 +5402,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-focus-guards@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz#8e9abb472a9a394f59a1b45f3dd26cfe3fc6da13" + integrity sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw== + "@radix-ui/react-focus-scope@1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz#2ac45fce8c5bb33eb18419cdc1905ef4f1906525" @@ -5366,6 +5417,15 @@ "@radix-ui/react-primitive" "1.0.3" "@radix-ui/react-use-callback-ref" "1.0.1" +"@radix-ui/react-focus-scope@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz#ebe2891a298e0a33ad34daab2aad8dea31caf0b2" + integrity sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA== + dependencies: + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-id@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0" @@ -5374,6 +5434,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-id@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.1.0.tgz#de47339656594ad722eb87f94a6b25f9cffae0ed" + integrity sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA== + dependencies: + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-popover@^1.0.6": version "1.0.7" resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.0.7.tgz#23eb7e3327330cb75ec7b4092d685398c1654e3c" @@ -5421,6 +5488,14 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-primitive" "1.0.3" +"@radix-ui/react-portal@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.1.tgz#1957f1eb2e1aedfb4a5475bd6867d67b50b1d15f" + integrity sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g== + dependencies: + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-presence@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz#491990ba913b8e2a5db1b06b203cb24b5cdef9ba" @@ -5430,6 +5505,14 @@ "@radix-ui/react-compose-refs" "1.0.1" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-presence@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.0.tgz#227d84d20ca6bfe7da97104b1a8b48a833bfb478" + integrity sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ== + dependencies: + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-primitive@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz#d49ea0f3f0b2fe3ab1cb5667eb03e8b843b914d0" @@ -5438,6 +5521,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-slot" "1.0.2" +"@radix-ui/react-primitive@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz#fe05715faa9203a223ccc0be15dc44b9f9822884" + integrity sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw== + dependencies: + "@radix-ui/react-slot" "1.1.0" + "@radix-ui/react-radio-group@^1.1.1": version "1.1.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-1.1.3.tgz#3197f5dcce143bcbf961471bf89320735c0212d3" @@ -5507,6 +5597,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-compose-refs" "1.0.1" +"@radix-ui/react-slot@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.0.tgz#7c5e48c36ef5496d97b08f1357bb26ed7c714b84" + integrity sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw== + dependencies: + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-switch@^1.0.1": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-switch/-/react-switch-1.0.3.tgz#6119f16656a9eafb4424c600fdb36efa5ec5837e" @@ -5547,6 +5644,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-use-callback-ref@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz#bce938ca413675bc937944b0d01ef6f4a6dc5bf1" + integrity sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw== + "@radix-ui/react-use-controllable-state@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz#ecd2ced34e6330caf89a82854aa2f77e07440286" @@ -5555,6 +5657,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-callback-ref" "1.0.1" +"@radix-ui/react-use-controllable-state@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz#1321446857bb786917df54c0d4d084877aab04b0" + integrity sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw== + dependencies: + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-use-escape-keydown@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz#217b840c250541609c66f67ed7bab2b733620755" @@ -5563,6 +5672,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-callback-ref" "1.0.1" +"@radix-ui/react-use-escape-keydown@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz#31a5b87c3b726504b74e05dac1edce7437b98754" + integrity sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw== + dependencies: + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-use-layout-effect@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz#be8c7bc809b0c8934acf6657b577daf948a75399" @@ -5570,6 +5686,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-use-layout-effect@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz#3c2c8ce04827b26a39e442ff4888d9212268bd27" + integrity sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w== + "@radix-ui/react-use-previous@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.0.1.tgz#b595c087b07317a4f143696c6a01de43b0d0ec66" @@ -16858,6 +16979,14 @@ react-remove-scroll-bar@^2.3.3: react-style-singleton "^2.2.1" tslib "^2.0.0" +react-remove-scroll-bar@^2.3.4: + version "2.3.6" + resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz#3e585e9d163be84a010180b18721e851ac81a29c" + integrity sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g== + dependencies: + react-style-singleton "^2.2.1" + tslib "^2.0.0" + react-remove-scroll@2.5.5: version "2.5.5" resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz#1e31a1260df08887a8a0e46d09271b52b3a37e77" @@ -16869,6 +16998,17 @@ react-remove-scroll@2.5.5: use-callback-ref "^1.3.0" use-sidecar "^1.1.2" +react-remove-scroll@2.5.7: + version "2.5.7" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz#15a1fd038e8497f65a695bf26a4a57970cac1ccb" + integrity sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA== + dependencies: + react-remove-scroll-bar "^2.3.4" + react-style-singleton "^2.2.1" + tslib "^2.1.0" + use-callback-ref "^1.3.0" + use-sidecar "^1.1.2" + react-router-dom@^6.10.0, react-router-dom@^6.8.0: version "6.20.0" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.20.0.tgz#7b9527a1e29c7fb90736a5f89d54ca01f40e264b" From f1316c5e634ba19ae214f16d6768df5fa4441051 Mon Sep 17 00:00:00 2001 From: RyukTheCoder Date: Sat, 14 Sep 2024 07:58:26 +0000 Subject: [PATCH 2/9] fix: remove overflow styling --- widget/ui/src/components/Modal/Modal.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/widget/ui/src/components/Modal/Modal.tsx b/widget/ui/src/components/Modal/Modal.tsx index 8b6e112ed0..648d3e739f 100644 --- a/widget/ui/src/components/Modal/Modal.tsx +++ b/widget/ui/src/components/Modal/Modal.tsx @@ -71,14 +71,12 @@ export function Modal(props: PropsWithChildren) { setStatus('deactivated'); modalContainerRef.current?.addEventListener('transitionend', () => { setStatus('unmounted'); - container.style.removeProperty('overflow'); }); } else { setStatus('unmounted'); } } else { setStatus('mounted'); - container.style.overflow = 'hidden'; } return () => { @@ -91,13 +89,6 @@ export function Modal(props: PropsWithChildren) { }; }, [open]); - useEffect(() => { - return () => { - //container might be null - container?.style.removeProperty('overflow'); - }; - }, []); - useEffect(() => { if (!!container && isMount) { if (modalContainerRef.current) { From a99d28b0c3d5b1ba06e668fcf52099e80bf0b569 Mon Sep 17 00:00:00 2001 From: RyukTheCoder Date: Tue, 17 Sep 2024 19:56:52 +0000 Subject: [PATCH 3/9] fix: use radix data attributes --- .../ui/src/components/Modal/Modal.helpers.ts | 4 - .../ui/src/components/Modal/Modal.styles.ts | 149 +++++++++++++----- widget/ui/src/components/Modal/Modal.tsx | 128 ++++----------- 3 files changed, 140 insertions(+), 141 deletions(-) delete mode 100644 widget/ui/src/components/Modal/Modal.helpers.ts diff --git a/widget/ui/src/components/Modal/Modal.helpers.ts b/widget/ui/src/components/Modal/Modal.helpers.ts deleted file mode 100644 index 516ff9a8d6..0000000000 --- a/widget/ui/src/components/Modal/Modal.helpers.ts +++ /dev/null @@ -1,4 +0,0 @@ -// https://github.com/reactjs/react-transition-group/blob/master/src/CSSTransition.js#L48-L55 -export function forceReflow(node: HTMLElement) { - return node.scrollTop; -} diff --git a/widget/ui/src/components/Modal/Modal.styles.ts b/widget/ui/src/components/Modal/Modal.styles.ts index da19637d15..5e88c0c381 100644 --- a/widget/ui/src/components/Modal/Modal.styles.ts +++ b/widget/ui/src/components/Modal/Modal.styles.ts @@ -1,8 +1,26 @@ import * as Dialog from '@radix-ui/react-dialog'; -import { styled } from '../../theme.js'; +import { keyframes, styled } from '../../theme.js'; import { IconButton } from '../IconButton/index.js'; +const DialogOverlayAnimateIn = keyframes({ + '0%': { + backgroundColor: 'transparent', + }, + '100%': { + backgroundColor: 'color-mix(in srgb, $neutral500 70%, transparent)', + }, +}); + +const DialogOverlayAnimateOut = keyframes({ + '0%': { + backgroundColor: 'color-mix(in srgb, $neutral500 70%, transparent)', + }, + '100%': { + backgroundColor: 'transparent', + }, +}); + export const DialogOverlay = styled(Dialog.Overlay, { position: 'absolute', top: '0', @@ -11,18 +29,74 @@ export const DialogOverlay = styled(Dialog.Overlay, { bottom: '0', width: '100%', height: '100%', - backgroundColor: 'transparent', zIndex: 10, borderRadius: '$primary', overflow: 'hidden', - transition: 'background .35s', + backgroundColor: 'transparent', - variants: { - active: { - true: { - backgroundColor: 'color-mix(in srgb, $neutral500 70%, transparent)', - }, - }, + "&[data-state='open']": { + backgroundColor: 'color-mix(in srgb, $neutral500 70%, transparent)', + animation: `${DialogOverlayAnimateIn} .45s ease-in-out`, + }, + "&[data-state='closed']": { + backgroundColor: 'transparent', + animation: `${DialogOverlayAnimateOut} .45s ease-in-out`, + }, +}); + +const DialogContentRightAnimateIn = keyframes({ + '0%': { + transform: 'translateX(100%)', + }, + '100%': { + transform: 'translateX(0%)', + }, +}); + +const DialogContentRightAnimateOut = keyframes({ + '0%': { + transform: 'translateX(0%)', + }, + '100%': { + transform: 'translateX(100%)', + }, +}); +const DialogContentCenterAnimateIn = keyframes({ + '0%': { + top: '100%', + transform: 'translate(-50%, 100%)', + }, + '100%': { + top: '50%', + transform: 'translate(-50%, -50%)', + }, +}); + +const DialogContentCenterAnimateOut = keyframes({ + '0%': { + top: '50%', + transform: 'translate(-50%, -50%)', + }, + '100%': { + top: '100%', + transform: 'translate(-50%, 100%)', + }, +}); +const DialogContentBottomAnimateIn = keyframes({ + '0%': { + transform: 'translateY(100%)', + }, + '100%': { + transform: 'translateY(0%)', + }, +}); + +const DialogContentBottomAnimateOut = keyframes({ + '0%': { + transform: 'translateY(0%)', + }, + '100%': { + transform: 'translateY(100%)', }, }); @@ -34,52 +108,41 @@ export const DialogContent = styled(Dialog.Content, { flexDirection: 'column', zIndex: 9999999, position: 'absolute', - transition: - 'transform .45s ease-in-out, top .45s ease-in-out, left .45s ease-in-out', variants: { anchor: { right: { - left: '100%', + right: '0%', + "&[data-state='open']": { + animation: `${DialogContentRightAnimateIn} .45s ease-in-out`, + }, + "&[data-state='closed']": { + animation: `${DialogContentRightAnimateOut} .45s ease-in-out`, + }, }, center: { - top: '100%', left: '50%', - transform: 'translateX(-50%)', + transform: 'translate(-50%, -50%)', + "&[data-state='open']": { + top: '50%', + animation: `${DialogContentCenterAnimateIn} .45s ease-in-out`, + }, + "&[data-state='closed']": { + top: '100%', + animation: `${DialogContentCenterAnimateOut} .45s ease-in-out`, + }, }, bottom: { - top: '100%', + bottom: 0, + "&[data-state='open']": { + animation: `${DialogContentBottomAnimateIn} .45s ease-in-out`, + }, + "&[data-state='closed']": { + animation: `${DialogContentBottomAnimateOut} .45s ease-in-out`, + }, }, }, - active: { - true: {}, - }, }, - compoundVariants: [ - { - active: true, - anchor: 'right', - css: { - transform: 'translateX(-100%)', - }, - }, - { - active: true, - anchor: 'center', - css: { - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', - }, - }, - { - active: true, - anchor: 'bottom', - css: { - transform: 'translateY(-100%)', - }, - }, - ], }); export const Flex = styled('div', { diff --git a/widget/ui/src/components/Modal/Modal.tsx b/widget/ui/src/components/Modal/Modal.tsx index 648d3e739f..ba24e87017 100644 --- a/widget/ui/src/components/Modal/Modal.tsx +++ b/widget/ui/src/components/Modal/Modal.tsx @@ -2,7 +2,7 @@ import type { ModalPropTypes } from './Modal.types.js'; import type { PropsWithChildren } from 'react'; import * as Dialog from '@radix-ui/react-dialog'; -import React, { useEffect, useRef, useState } from 'react'; +import React from 'react'; import { CloseIcon } from '../../icons/index.js'; import { BottomLogo } from '../BottomLogo/index.js'; @@ -10,7 +10,6 @@ import { Divider } from '../Divider/index.js'; import { IconButton } from '../IconButton/index.js'; import { Typography } from '../Typography/index.js'; -import { forceReflow } from './Modal.helpers.js'; import { Content, DialogContent, @@ -25,7 +24,6 @@ export function Modal(props: PropsWithChildren) { title, open, onClose, - onExit, styles, anchor = 'bottom', container = document.body, @@ -39,107 +37,49 @@ export function Modal(props: PropsWithChildren) { hasCloseIcon = true, } = props; - const [status, setStatus] = useState< - 'unmounted' | 'mounted' | 'activated' | 'deactivated' - >('unmounted'); - const active = status === 'activated'; - const isMount = - status == 'mounted' || status === 'activated' || status === 'deactivated'; - const modalContainerRef = useRef(null); - const exitCallbackRef = useRef(); - const handleBackDropClick = (open: boolean) => { if (dismissible && !open) { onClose(); } }; - useEffect(() => { - exitCallbackRef.current = onExit; - }, [onExit]); - - useEffect(() => { - if (exitCallbackRef.current) { - modalContainerRef.current?.addEventListener( - 'transitionend', - exitCallbackRef.current - ); - } - - if (!open) { - if (status === 'activated') { - setStatus('deactivated'); - modalContainerRef.current?.addEventListener('transitionend', () => { - setStatus('unmounted'); - }); - } else { - setStatus('unmounted'); - } - } else { - setStatus('mounted'); - } - - return () => { - if (exitCallbackRef.current) { - modalContainerRef.current?.removeEventListener( - 'transitionend', - exitCallbackRef.current - ); - } - }; - }, [open]); - - useEffect(() => { - if (!!container && isMount) { - if (modalContainerRef.current) { - forceReflow(modalContainerRef.current); - } - setStatus('activated'); - } - }, [isMount, container]); - return ( - + - - (modalContainerRef.current = ref)}> - {header ?? ( - - {prefix} - {title && ( - - {title} - - )} - - {suffix} - {dismissible && hasCloseIcon && ( - - - + + + {header ?? ( + + {prefix} + {title && ( + + {title} + )} - - - )} - {children} -
- {footer &&
{footer}
} + + {suffix} + {dismissible && hasCloseIcon && ( + + + + )} + + + )} + {children} +
+ {footer &&
{footer}
} -
- - -
-
- +
+ + +
+
+
+
); From 30203abb921a4fc7333ea1cfe93e1999726d158f Mon Sep 17 00:00:00 2001 From: RyukTheCoder Date: Sat, 21 Sep 2024 16:10:30 +0000 Subject: [PATCH 4/9] fix: fix modal overlay background color animation --- widget/embedded/src/utils/colors.ts | 9 ++++++++- widget/ui/src/components/Modal/Modal.styles.ts | 6 +++--- widget/ui/src/theme.ts | 6 ++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/widget/embedded/src/utils/colors.ts b/widget/embedded/src/utils/colors.ts index b58c4620bc..0174981678 100644 --- a/widget/embedded/src/utils/colors.ts +++ b/widget/embedded/src/utils/colors.ts @@ -171,13 +171,20 @@ export function expandToGenerateThemeColors( */ const isSingleColor = ['background', 'foreground'].includes(colorKey); + const expandedHexColor = expandShortHexColor(expandColor); if (!isSingleColor && !isOverridingColor(colorKey)) { - const expandedHexColor = expandShortHexColor(expandColor); Object.assign( output, createTintsAndShades(expandedHexColor, colorKey, isNeutralReversed) ); } + + if (colorKey === 'neutral') { + // add alpha to have 70 percent opacity + Object.assign(output, { + overlay: expandedHexColor + 'b3', + }); + } } return { ...output, ...expandColors }; diff --git a/widget/ui/src/components/Modal/Modal.styles.ts b/widget/ui/src/components/Modal/Modal.styles.ts index 5e88c0c381..fc23788b3c 100644 --- a/widget/ui/src/components/Modal/Modal.styles.ts +++ b/widget/ui/src/components/Modal/Modal.styles.ts @@ -8,13 +8,13 @@ const DialogOverlayAnimateIn = keyframes({ backgroundColor: 'transparent', }, '100%': { - backgroundColor: 'color-mix(in srgb, $neutral500 70%, transparent)', + backgroundColor: '$overlay', }, }); const DialogOverlayAnimateOut = keyframes({ '0%': { - backgroundColor: 'color-mix(in srgb, $neutral500 70%, transparent)', + backgroundColor: '$overlay', }, '100%': { backgroundColor: 'transparent', @@ -35,7 +35,7 @@ export const DialogOverlay = styled(Dialog.Overlay, { backgroundColor: 'transparent', "&[data-state='open']": { - backgroundColor: 'color-mix(in srgb, $neutral500 70%, transparent)', + backgroundColor: '$overlay', animation: `${DialogOverlayAnimateIn} .45s ease-in-out`, }, "&[data-state='closed']": { diff --git a/widget/ui/src/theme.ts b/widget/ui/src/theme.ts index a6da173b30..380da31ee6 100644 --- a/widget/ui/src/theme.ts +++ b/widget/ui/src/theme.ts @@ -60,6 +60,8 @@ export const theme = { background: '#FDFDFD', foreground: '#010101', + + overlay: '#E6E6E6b3', }, space: { 0: '0rem', @@ -205,6 +207,8 @@ export const darkColors = { background: '#010101', foreground: '#FDFDFD', + + overlay: '#222222b3', }; /* ----------------------- End of Values ----------------------- */ @@ -237,4 +241,6 @@ export const rangoDarkColors = { neutral900: '#E9E9E9', background: '#070917', foreground: '#FDFDFD', + + overlay: '#161C38b3', }; From 22fa352abfa90fce34122b39a8583badd9e4b9ae Mon Sep 17 00:00:00 2001 From: RyukTheCoder Date: Sun, 22 Sep 2024 14:14:12 +0000 Subject: [PATCH 5/9] fix: revert onExit changes --- widget/ui/src/components/Modal/Modal.tsx | 32 ++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/widget/ui/src/components/Modal/Modal.tsx b/widget/ui/src/components/Modal/Modal.tsx index ba24e87017..e149e1c9d4 100644 --- a/widget/ui/src/components/Modal/Modal.tsx +++ b/widget/ui/src/components/Modal/Modal.tsx @@ -2,7 +2,7 @@ import type { ModalPropTypes } from './Modal.types.js'; import type { PropsWithChildren } from 'react'; import * as Dialog from '@radix-ui/react-dialog'; -import React from 'react'; +import React, { useEffect, useRef } from 'react'; import { CloseIcon } from '../../icons/index.js'; import { BottomLogo } from '../BottomLogo/index.js'; @@ -24,6 +24,7 @@ export function Modal(props: PropsWithChildren) { title, open, onClose, + onExit, styles, anchor = 'bottom', container = document.body, @@ -36,6 +37,8 @@ export function Modal(props: PropsWithChildren) { hasWatermark = true, hasCloseIcon = true, } = props; + const exitCallbackRef = useRef(); + const modalContainerRef = useRef(null); const handleBackDropClick = (open: boolean) => { if (dismissible && !open) { @@ -43,11 +46,36 @@ export function Modal(props: PropsWithChildren) { } }; + useEffect(() => { + exitCallbackRef.current = onExit; + }, [onExit]); + + useEffect(() => { + if (exitCallbackRef.current) { + modalContainerRef.current?.addEventListener( + 'transitionend', + exitCallbackRef.current + ); + } + + return () => { + if (exitCallbackRef.current) { + modalContainerRef.current?.removeEventListener( + 'transitionend', + exitCallbackRef.current + ); + } + }; + }, [open]); + return ( - + {header ?? ( {prefix} From cfb7b236611ba10130dcefd334f61708e81007b2 Mon Sep 17 00:00:00 2001 From: RyukTheCoder Date: Sat, 28 Sep 2024 13:35:33 +0000 Subject: [PATCH 6/9] fix: fix onExit not firing problem --- widget/ui/src/components/Modal/Modal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/widget/ui/src/components/Modal/Modal.tsx b/widget/ui/src/components/Modal/Modal.tsx index e149e1c9d4..1ac90989d1 100644 --- a/widget/ui/src/components/Modal/Modal.tsx +++ b/widget/ui/src/components/Modal/Modal.tsx @@ -53,7 +53,7 @@ export function Modal(props: PropsWithChildren) { useEffect(() => { if (exitCallbackRef.current) { modalContainerRef.current?.addEventListener( - 'transitionend', + 'animationend', exitCallbackRef.current ); } @@ -61,7 +61,7 @@ export function Modal(props: PropsWithChildren) { return () => { if (exitCallbackRef.current) { modalContainerRef.current?.removeEventListener( - 'transitionend', + 'animationend', exitCallbackRef.current ); } From 4d55b42f25e18d534e2c983f2fe39a51ac573004 Mon Sep 17 00:00:00 2001 From: RyukTheCoder Date: Sat, 28 Sep 2024 19:12:10 +0000 Subject: [PATCH 7/9] fix: prevent console error --- widget/ui/package.json | 1 + widget/ui/src/components/Modal/Modal.tsx | 19 ++++++++++++++----- yarn.lock | 7 +++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/widget/ui/package.json b/widget/ui/package.json index ae23c21c16..f9646a4d42 100644 --- a/widget/ui/package.json +++ b/widget/ui/package.json @@ -54,6 +54,7 @@ "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-switch": "^1.0.1", "@radix-ui/react-tooltip": "^1.0.2", + "@radix-ui/react-visually-hidden": "^1.1.0", "@rango-dev/wallets-shared": "^0.37.1-next.1", "@stitches/react": "^1.2.8", "copy-to-clipboard": "^3.3.3", diff --git a/widget/ui/src/components/Modal/Modal.tsx b/widget/ui/src/components/Modal/Modal.tsx index 1ac90989d1..1ab0c4b249 100644 --- a/widget/ui/src/components/Modal/Modal.tsx +++ b/widget/ui/src/components/Modal/Modal.tsx @@ -2,6 +2,7 @@ import type { ModalPropTypes } from './Modal.types.js'; import type { PropsWithChildren } from 'react'; import * as Dialog from '@radix-ui/react-dialog'; +import { VisuallyHidden } from '@radix-ui/react-visually-hidden'; import React, { useEffect, useRef } from 'react'; import { CloseIcon } from '../../icons/index.js'; @@ -68,6 +69,18 @@ export function Modal(props: PropsWithChildren) { }; }, [open]); + const renderTitle = () => { + const result = ( + + + {title} + + + ); + // This is added to prevent error "`DialogContent` requires a `DialogTitle` for the component to be accessible for screen reader users." + return title ? result : {result}; + }; + return ( @@ -79,11 +92,7 @@ export function Modal(props: PropsWithChildren) { {header ?? ( {prefix} - {title && ( - - {title} - - )} + {renderTitle()} {suffix} {dismissible && hasCloseIcon && ( diff --git a/yarn.lock b/yarn.lock index 0e25b5c214..aa15465674 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5722,6 +5722,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-primitive" "1.0.3" +"@radix-ui/react-visually-hidden@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz#ad47a8572580f7034b3807c8e6740cd41038a5a2" + integrity sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ== + dependencies: + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/rect@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.0.1.tgz#bf8e7d947671996da2e30f4904ece343bc4a883f" From 637384ead5b23ba4843b3b510e6d1698dc15704d Mon Sep 17 00:00:00 2001 From: RyukTheCoder Date: Sun, 29 Sep 2024 10:45:26 +0000 Subject: [PATCH 8/9] fix: fix storybook & add style for dialog title & add todo comment to handle error for custom header --- widget/storybook/src/components/Modal.stories.tsx | 8 +++++--- widget/ui/src/components/Modal/Modal.styles.ts | 4 ++++ widget/ui/src/components/Modal/Modal.tsx | 9 +++++---- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/widget/storybook/src/components/Modal.stories.tsx b/widget/storybook/src/components/Modal.stories.tsx index a3931a275d..613340b633 100644 --- a/widget/storybook/src/components/Modal.stories.tsx +++ b/widget/storybook/src/components/Modal.stories.tsx @@ -2,7 +2,7 @@ import type { ModalPropTypes } from '@rango-dev/ui'; import type { Meta } from '@storybook/react'; import { MessageBox, Modal } from '@rango-dev/ui'; -import React, { useState } from 'react'; +import React, { useRef, useState } from 'react'; export default { name: 'Modal', @@ -51,12 +51,14 @@ export default { export const Main = (args: ModalPropTypes) => { const [open, setOpen] = useState(false); + const modalContainerRef = useRef(null); + return ( -
+
setOpen(false)}> ) { const renderTitle = () => { const result = ( - + {title} - + ); // This is added to prevent error "`DialogContent` requires a `DialogTitle` for the component to be accessible for screen reader users." - return title ? result : {result}; + return title ? result : {result}; }; return ( @@ -89,7 +90,7 @@ export function Modal(props: PropsWithChildren) { ref={modalContainerRef} css={styles?.container} anchor={anchor}> - {header ?? ( + {header ?? ( // TODO: error related to required `DialogTitle` should be handled for custom headers {prefix} {renderTitle()} From 02ea679a28e24234ad95f5fc6dd63180eda29f41 Mon Sep 17 00:00:00 2001 From: Shinji Date: Tue, 12 Nov 2024 08:12:13 +0000 Subject: [PATCH 9/9] fix: restrict the area where the backdrop and escape key functionalities are active --- widget/ui/src/components/Modal/Modal.tsx | 34 +++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/widget/ui/src/components/Modal/Modal.tsx b/widget/ui/src/components/Modal/Modal.tsx index d01d6e0ef6..37ba1fb25e 100644 --- a/widget/ui/src/components/Modal/Modal.tsx +++ b/widget/ui/src/components/Modal/Modal.tsx @@ -1,5 +1,5 @@ import type { ModalPropTypes } from './Modal.types.js'; -import type { PropsWithChildren } from 'react'; +import type { MouseEventHandler, PropsWithChildren } from 'react'; import * as Dialog from '@radix-ui/react-dialog'; import { VisuallyHidden } from '@radix-ui/react-visually-hidden'; @@ -41,6 +41,7 @@ export function Modal(props: PropsWithChildren) { } = props; const exitCallbackRef = useRef(); const modalContainerRef = useRef(null); + const overlayRef = useRef(null); const handleBackDropClick = (open: boolean) => { if (dismissible && !open) { @@ -48,6 +49,31 @@ export function Modal(props: PropsWithChildren) { } }; + // Restrict the overlay click area to just the overlay element, rather than the entire page + const handleOverlayClick: MouseEventHandler = (event) => { + if (event.target === overlayRef.current) { + handleBackDropClick(!open); + } + }; + + // The escape key functionality only works when the focus is within the overlay (modal). + const handleScapeKeyDown = () => { + if (overlayRef.current?.querySelector(':focus')) { + onClose(); + } + }; + + /** + * When the user clicks on the content, + * we return the focus to the modal content if no other element is focused, + * ensuring the escape key functionality works. + */ + const handleContentClick = () => { + if (!overlayRef.current?.querySelector(':focus')) { + modalContainerRef.current?.focus(); + } + }; + useEffect(() => { exitCallbackRef.current = onExit; }, [onExit]); @@ -83,10 +109,12 @@ export function Modal(props: PropsWithChildren) { }; return ( - + - +