diff --git a/packages/ui/package.json b/packages/ui/package.json index b8fc2dd49..b4cc61d61 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -41,6 +41,7 @@ "@rollup/plugin-json": "6.0.1", "@rollup/plugin-node-resolve": "15.2.3", "@rollup/plugin-typescript": "11.1.6", + "@types/lodash.kebabcase": "4.1.9", "@types/react-dom": "18.2.19", "@utrecht/component-library-react": "3.0.1-alpha.19", "@utrecht/web-component-library-react": "1.0.3-alpha.19", @@ -53,13 +54,11 @@ "rollup-plugin-postcss": "4.0.2", "rollup-plugin-scss": "4.0.0", "rollup-plugin-terser": "7.0.2", - "rollup-plugin-typescript2": "0.35.0", - "@types/lodash.kebabcase": "4.1.9" + "rollup-plugin-typescript2": "0.35.0" }, "dependencies": { - "focus-trap-react": "10.2.3", + "lodash.kebabcase": "4.1.1", "react-markdown": "9.0.1", - "rehype-raw": "7.0.0", - "lodash.kebabcase": "4.1.1" + "rehype-raw": "7.0.0" } } diff --git a/packages/ui/src/components/Drawer/index.module.scss b/packages/ui/src/components/Drawer/index.module.scss deleted file mode 100644 index 34d20fb57..000000000 --- a/packages/ui/src/components/Drawer/index.module.scss +++ /dev/null @@ -1,10 +0,0 @@ -.utrecht-drawer-custom { - background-color: rgb(0 0 0 / 25%); - display: block; - inset-block-end: 0; - inset-block-start: 0; - inset-inline-end: 0; - inset-inline-start: 0; - position: absolute; - z-index: 1000; -} diff --git a/packages/ui/src/components/Drawer/index.tsx b/packages/ui/src/components/Drawer/index.tsx deleted file mode 100644 index 4596b2570..000000000 --- a/packages/ui/src/components/Drawer/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import classnames from 'classnames/bind'; -import FocusTrap from 'focus-trap-react'; -import { ForwardedRef, forwardRef, HTMLAttributes, PropsWithChildren } from 'react'; -import styles from './index.module.scss'; -interface DrawerProps extends HTMLAttributes { - onDrawerClose: () => void; - open: boolean; - initialFocus?: string | false; -} - -const css = classnames.bind(styles); - -export const Drawer = forwardRef( - ( - { children, open, initialFocus = false, ...restProps }: PropsWithChildren, - ref: ForwardedRef, - ) => { - const focusTrapOptions = { - checkCanFocusTrap: (trapContainers: Element[]) => { - const results = trapContainers.map((trapContainer) => { - return new Promise((resolve) => { - const interval = setInterval(() => { - if (getComputedStyle(trapContainer).opacity !== '0') { - resolve(); - clearInterval(interval); - } - }, 10); - }); - }); - // Return a promise that resolves when all the trap containers are able to receive focus - return Promise.all(results); - }, - initialFocus, - } as any; - return ( - -
- {children} -
-
- ); - }, -); - -Drawer.displayName = 'Drawer'; diff --git a/packages/ui/src/components/Navigation/NavigationList/index.module.scss b/packages/ui/src/components/Navigation/NavigationList/index.module.scss index 8bd1a14d4..815dd5c67 100644 --- a/packages/ui/src/components/Navigation/NavigationList/index.module.scss +++ b/packages/ui/src/components/Navigation/NavigationList/index.module.scss @@ -23,10 +23,6 @@ background-color: var(--utrecht-navigation-list-mobile-background-color, var(--utrecht-color-white)); block-size: 100%; - - @media (width >= 550px) { - --utrecht-navigation-list-mobile-inline-size: 50%; - } } .utrecht-navigation__list--side-nav { diff --git a/packages/ui/src/components/Navigation/index.module.scss b/packages/ui/src/components/Navigation/index.module.scss index 2589a0976..dc9a73ba1 100644 --- a/packages/ui/src/components/Navigation/index.module.scss +++ b/packages/ui/src/components/Navigation/index.module.scss @@ -1,3 +1,5 @@ +@import "../Grid/index.module"; + .utrecht-navigation { border-block-end-color: var(--utrecht-navigation-border-block-end-color); border-block-end-style: solid; @@ -22,3 +24,17 @@ inline-size: 100%; justify-content: var(--utrecht-navigation-mobile-justify-content, flex-start); } + +.utrecht-drawer--nav { + --utrecht-drawer-min-inline-size: 100%; +} + +.utrecht-drawer--nav::backdrop { + --utrecht-backdrop-background-color: rgb(0 0 0 / 30%); +} + +@include breakpoint("sm") { + .utrecht-drawer--nav { + --utrecht-drawer-min-inline-size: 50%; + } +} diff --git a/packages/ui/src/components/Navigation/index.tsx b/packages/ui/src/components/Navigation/index.tsx index 1226c8d9c..c5211ccf4 100644 --- a/packages/ui/src/components/Navigation/index.tsx +++ b/packages/ui/src/components/Navigation/index.tsx @@ -1,11 +1,19 @@ +import { Drawer } from '@utrecht/component-library-react'; import classnames from 'classnames/bind'; -import { ForwardedRef, forwardRef, HTMLAttributes, PropsWithChildren, useEffect, useRef, useState } from 'react'; +import { + createRef, + ForwardedRef, + forwardRef, + HTMLAttributes, + PropsWithChildren, + useLayoutEffect, + useRef, + useState, +} from 'react'; import { NavigationList } from './NavigationList'; import { NavToggleButton } from './NavigationToggleButton'; import styles from './index.module.scss'; -import { useClickOutside, useKeyboardEvent, useScreenSize } from '../../hooks'; -import { Drawer } from '../Drawer'; -import { Portal } from '../Portal'; +import { useClickOutside, useScreenSize } from '../../hooks'; const css = classnames.bind(styles); export type NavigationListType = { @@ -32,17 +40,31 @@ export const Navigation = forwardRef( const screenSize = useScreenSize(); const [visible, setVisible] = useState(false); const [drawerVisible, setDrawerVisible] = useState(false); - const navigationListRef = useRef(null); - useKeyboardEvent('Escape', () => setDrawerVisible(false)); - useClickOutside(navigationListRef, () => setDrawerVisible(false)); + const navigationListRef = createRef(); + const drawerRef = useRef(null); + const hamburgerButtonRef = useRef(null); - useEffect(() => { + useClickOutside(drawerRef, hamburgerButtonRef); + + const showModal = () => { + if (drawerRef.current) { + if (drawerRef.current.open) { + drawerRef.current.close(); + setDrawerVisible(false); + } else { + setDrawerVisible(true); + drawerRef.current.showModal(); + } + } + }; + + useLayoutEffect(() => { if (mobileBreakpoint && screenSize) { setVisible(screenSize < mobileBreakpoint); } }, [screenSize, mobileBreakpoint]); - useEffect(() => { + useLayoutEffect(() => { // TODO improve the scroll body lock when the menu is open // this is one of the packages that maybe fix the issue https://github.com/willmcpo/body-scroll-lock#readme if (drawerVisible && visible) { @@ -65,6 +87,13 @@ export const Navigation = forwardRef( }; }, [visible, drawerVisible]); + useLayoutEffect(() => { + if (!visible && drawerRef.current) { + setDrawerVisible(false); + drawerRef.current.close(); + } + }, [visible, drawerRef.current]); + return ( <> - {visible && drawerVisible && ( - - setDrawerVisible(!drawerVisible)} open={drawerVisible}> - - - - )} + + + ); }, diff --git a/packages/ui/src/components/Portal/index.tsx b/packages/ui/src/components/Portal/index.tsx deleted file mode 100644 index 72246fb6b..000000000 --- a/packages/ui/src/components/Portal/index.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React, { ForwardedRef, forwardRef, useEffect, useMemo } from 'react'; -import ReactDOM from 'react-dom'; - -interface PortalProps { - children?: React.ReactNode; - parent?: string; - className?: string; -} - -export const Portal = forwardRef( - ({ children, parent, className }, ref: ForwardedRef) => { - const el = useMemo(() => document.createElement('div'), []); - - useEffect(() => { - const target = parent ? document.querySelector(parent) : document.body; - const classList = ['utrecht-portal']; - if (className) className.split(' ').forEach((item: string) => classList.push(item)); - - classList.forEach((item) => el.classList.add(item)); - - target?.appendChild(el); - - return () => { - target?.removeChild(el); - }; - }, [el, parent, className]); - - // Forward the ref to the div element - React.useImperativeHandle(ref, () => el); - - return ReactDOM.createPortal(children, el); - }, -); - -Portal.displayName = 'Portal'; diff --git a/packages/ui/src/hooks/index.tsx b/packages/ui/src/hooks/index.tsx index 921cd455a..e58f3cf98 100644 --- a/packages/ui/src/hooks/index.tsx +++ b/packages/ui/src/hooks/index.tsx @@ -1,3 +1,2 @@ export * from './useClickOutside'; -export * from './useKeyboardEvent'; export * from './useScreenSize'; diff --git a/packages/ui/src/hooks/useClickOutside.ts b/packages/ui/src/hooks/useClickOutside.ts index 2a5cae677..e86138a85 100644 --- a/packages/ui/src/hooks/useClickOutside.ts +++ b/packages/ui/src/hooks/useClickOutside.ts @@ -1,9 +1,18 @@ import { RefObject, useEffect } from 'react'; -export const useClickOutside = (ref: RefObject, callback: () => void) => { - const handleClickOutside = (event: Event) => { - if (ref.current && !ref.current.contains(event.target as Node)) { - callback(); +export const useClickOutside = ( + ref: RefObject, + prevFocusableElement?: RefObject, +) => { + const handleClickOutside = (event: any) => { + event.preventDefault(); + if (ref.current && ref.current === event.target) { + ref.current.close(); + if (prevFocusableElement) { + prevFocusableElement.current?.focus(); + } else { + (document.activeElement as HTMLElement).focus(); + } } }; diff --git a/packages/ui/src/hooks/useKeyboardEvent.ts b/packages/ui/src/hooks/useKeyboardEvent.ts deleted file mode 100644 index b6d31c90e..000000000 --- a/packages/ui/src/hooks/useKeyboardEvent.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useEffect } from 'react'; - -export const useKeyboardEvent = (key: string, callback: () => void) => { - useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === key) { - callback(); - } - }; - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); - }, [key, callback]); -}; diff --git a/yarn.lock b/yarn.lock index 6fb25b25a..6907abdc6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11167,21 +11167,6 @@ fn.name@1.x.x: resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== -focus-trap-react@10.2.3: - version "10.2.3" - resolved "https://registry.yarnpkg.com/focus-trap-react/-/focus-trap-react-10.2.3.tgz#a5a2ea7fbb042ffa4337fde72758325ed0fb793a" - integrity sha512-YXBpFu/hIeSu6NnmV2xlXzOYxuWkoOtar9jzgp3lOmjWLWY59C/b8DtDHEAV4SPU07Nd/t+nS/SBNGkhUBFmEw== - dependencies: - focus-trap "^7.5.4" - tabbable "^6.2.0" - -focus-trap@^7.5.4: - version "7.5.4" - resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-7.5.4.tgz#6c4e342fe1dae6add9c2aa332a6e7a0bbd495ba2" - integrity sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w== - dependencies: - tabbable "^6.2.0" - follow-redirects@^1.0.0, follow-redirects@^1.15.0, follow-redirects@^1.15.4: version "1.15.5" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" @@ -21538,7 +21523,7 @@ synckit@^0.8.5: "@pkgr/core" "^0.1.0" tslib "^2.6.2" -tabbable@^6.0.1, tabbable@^6.2.0: +tabbable@^6.0.1: version "6.2.0" resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==