@mrshmllw/smores-react 16.6.0 → 16.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Modal/Modal.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useRef } from 'react';
|
|
2
|
+
import { useEffect, useId, useRef, useState } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
import styled, { css, useTheme } from 'styled-components';
|
|
5
5
|
import { Box } from '../Box';
|
|
@@ -14,16 +14,74 @@ const getDefaultTitleProps = (title) => ({
|
|
|
14
14
|
typo: 'heading-small',
|
|
15
15
|
align: 'left',
|
|
16
16
|
});
|
|
17
|
+
const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
17
18
|
export const Modal = ({ title = '', children, rightPanel, showModal = false, handleClick, drawer = true, cross = true, width, containerClass, portalContainer = document.body, closeOnOverlayClick = true, stickyHeader = false, footer, stickyFooter = false, }) => {
|
|
18
|
-
const
|
|
19
|
+
const [modalNode, setModalNode] = useState(null);
|
|
20
|
+
const containerRef = useRef(null);
|
|
21
|
+
const titleId = useId();
|
|
19
22
|
const theme = useTheme();
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
useBodyScrollLock({ node: modalNode, showModal });
|
|
24
|
+
// Record the opener while rendering the open state, before any child can take focus.
|
|
25
|
+
const [wasOpen, setWasOpen] = useState(false);
|
|
26
|
+
const [opener, setOpener] = useState(null);
|
|
27
|
+
if (showModal !== wasOpen) {
|
|
28
|
+
setWasOpen(showModal);
|
|
29
|
+
if (showModal)
|
|
30
|
+
setOpener(document.activeElement);
|
|
31
|
+
}
|
|
32
|
+
const latestRef = useRef({ handleClick, closeOnOverlayClick });
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
latestRef.current = { handleClick, closeOnOverlayClick };
|
|
35
|
+
});
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const container = containerRef.current;
|
|
38
|
+
if (!showModal || !container)
|
|
39
|
+
return;
|
|
40
|
+
// A child with autoFocus may already have focus, so leave it there.
|
|
41
|
+
if (!container.contains(document.activeElement))
|
|
42
|
+
container.focus();
|
|
43
|
+
const handleKeyDown = (event) => {
|
|
44
|
+
const active = document.activeElement;
|
|
45
|
+
// Only the modal that has focus responds, so stacked modals close one at a time.
|
|
46
|
+
if (!container.contains(active))
|
|
47
|
+
return;
|
|
48
|
+
if (event.key === 'Escape') {
|
|
49
|
+
if (event.defaultPrevented || event.isComposing)
|
|
50
|
+
return;
|
|
51
|
+
if (latestRef.current.closeOnOverlayClick) {
|
|
52
|
+
latestRef.current.handleClick();
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (event.key !== 'Tab')
|
|
57
|
+
return;
|
|
58
|
+
const focusable = container.querySelectorAll(FOCUSABLE_SELECTOR);
|
|
59
|
+
const first = focusable[0];
|
|
60
|
+
const last = focusable[focusable.length - 1];
|
|
61
|
+
if (!first) {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
}
|
|
64
|
+
else if (event.shiftKey && (active === first || active === container)) {
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
last.focus();
|
|
67
|
+
}
|
|
68
|
+
else if (!event.shiftKey && active === last) {
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
first.focus();
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
74
|
+
return () => {
|
|
75
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
76
|
+
opener?.focus();
|
|
77
|
+
};
|
|
78
|
+
}, [showModal, opener]);
|
|
22
79
|
const isTitleString = typeof title === 'string';
|
|
23
80
|
const titleProps = isTitleString ? getDefaultTitleProps(title) : title;
|
|
81
|
+
const labelId = titleProps.id ?? titleId;
|
|
24
82
|
if (!showModal)
|
|
25
83
|
return null;
|
|
26
|
-
return createPortal(_jsxs(Wrapper, { ref:
|
|
84
|
+
return createPortal(_jsxs(Wrapper, { ref: setModalNode, children: [_jsx(Overlay, { onClick: () => closeOnOverlayClick && handleClick(), $closeOnOverlayClick: closeOnOverlayClick }), _jsx(Container, { ref: containerRef, tabIndex: -1, role: "dialog", "aria-modal": "true", "aria-labelledby": title ? labelId : undefined, $drawer: drawer, $width: width || '460px', className: containerClass, children: _jsxs(ScrollArea, { children: [_jsxs(Header, { flex: true, alignItems: "flex-start", justifyContent: "space-between", $sticky: stickyHeader, children: [_jsx(TitleElements, { flex: true, direction: "column", children: _jsx(Text, { ...titleProps, id: labelId }) }), _jsxs(Box, { flex: true, alignItems: "center", gap: 'space.100', children: [rightPanel, cross && (_jsx(IconContainer, { as: "button", onClick: handleClick, role: "button", title: "Close modal", $size: 32, style: {
|
|
27
85
|
background: theme.color.illustration.neutral[300],
|
|
28
86
|
borderRadius: '100%',
|
|
29
87
|
padding: '6px',
|
|
@@ -63,6 +121,7 @@ const Container = styled.div(({ $drawer, $width }) => css `
|
|
|
63
121
|
position: fixed;
|
|
64
122
|
max-height: calc(100vh - 64px);
|
|
65
123
|
overflow: hidden;
|
|
124
|
+
outline: none;
|
|
66
125
|
transition: all 0.3s ease-in-out;
|
|
67
126
|
|
|
68
127
|
${$drawer === true &&
|
package/dist/Modal/Modal.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../src/Modal/Modal.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAiB,MAAM,EAAE,MAAM,OAAO,CAAA;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,MAAM,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAEzD,OAAO,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAA;AAC5B,OAAO,EAAE,IAAI,EAAkB,MAAM,SAAS,CAAA;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,kDAAkD,CAAA;AAuD1E,MAAM,oBAAoB,GAAG,CAAC,KAAa,EAAc,EAAE,CAAC,CAAC;IAC3D,QAAQ,EAAE,KAAK;IACf,GAAG,EAAE,IAAI;IACT,IAAI,EAAE,eAAe;IACrB,KAAK,EAAE,MAAM;CACd,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,KAAK,GAAmB,CAAC,EACpC,KAAK,GAAG,EAAE,EACV,QAAQ,EACR,UAAU,EACV,SAAS,GAAG,KAAK,EACjB,WAAW,EACX,MAAM,GAAG,IAAI,EACb,KAAK,GAAG,IAAI,EACZ,KAAK,EACL,cAAc,EACd,eAAe,GAAG,QAAQ,CAAC,IAAI,EAC/B,mBAAmB,GAAG,IAAI,EAC1B,YAAY,GAAG,KAAK,EACpB,MAAM,EACN,YAAY,GAAG,KAAK,GACrB,EAAE,EAAE;IACH,MAAM,QAAQ,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAA;IAC7C,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAA;IAExB,gDAAgD;IAChD,iBAAiB,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;IAExD,MAAM,aAAa,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAA;IAC/C,MAAM,UAAU,GAAG,aAAa,CAAC,CAAC,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;IAEtE,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAA;IAE3B,OAAO,YAAY,CACjB,MAAC,OAAO,IAAC,GAAG,EAAE,QAAQ,aACpB,KAAC,OAAO,IACN,OAAO,EAAE,GAAG,EAAE,CAAC,mBAAmB,IAAI,WAAW,EAAE,EACnD,oBAAoB,EAAE,mBAAmB,GACzC,EACF,KAAC,SAAS,IACR,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,KAAK,IAAI,OAAO,EACxB,SAAS,EAAE,cAAc,YAEzB,MAAC,UAAU,eACT,MAAC,MAAM,IACL,IAAI,QACJ,UAAU,EAAC,YAAY,EACvB,cAAc,EAAC,eAAe,EAC9B,OAAO,EAAE,YAAY,aAErB,KAAC,aAAa,IAAC,IAAI,QAAC,SAAS,EAAC,QAAQ,YACpC,KAAC,IAAI,OAAK,UAAU,GAAI,GACV,EAChB,MAAC,GAAG,IAAC,IAAI,QAAC,UAAU,EAAC,QAAQ,EAAC,GAAG,EAAE,WAAW,aAC3C,UAAU,EACV,KAAK,IAAI,CACR,KAAC,aAAa,IACZ,EAAE,EAAC,QAAQ,EACX,OAAO,EAAE,WAAW,EACpB,IAAI,EAAC,QAAQ,EACb,KAAK,EAAC,aAAa,EACnB,KAAK,EAAE,EAAE,EACT,KAAK,EAAE;gDACL,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC;gDACjD,YAAY,EAAE,MAAM;gDACpB,OAAO,EAAE,KAAK;gDACd,MAAM,EAAE,MAAM;gDACd,MAAM,EAAE,SAAS;6CAClB,YAED,KAAC,eAAe,IACd,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAC5B,GACY,CACjB,IACG,IACC,EACT,KAAC,WAAW,IAAC,IAAI,QAAC,SAAS,EAAC,QAAQ,EAAC,UAAU,EAAE,CAAC,CAAC,MAAM,YACtD,QAAQ,GACG,EACb,MAAM,CAAC,CAAC,CAAC,KAAC,MAAM,IAAC,OAAO,EAAE,YAAY,YAAG,MAAM,GAAU,CAAC,CAAC,CAAC,IAAI,IACtD,GACH,IACJ,EACV,eAAe,CAChB,CAAA;AACH,CAAC,CAAA;AAED,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;;;;;;;;;;CAU1B,CAAA;AAED,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAmC;;gBAE7C,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;YAChD,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;;;;;;CAM1E,CAAA;AAED,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAC1B,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,GAAG,CAAA;;;kBAGZ,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;;;;iBAI3C,MAAM;;;;;;MAOjB,OAAO,KAAK,IAAI;IAChB,GAAG,CAAA;;;;;;;;;;;OAYL;GACD,CACF,CAAA;AAED,qEAAqE;AACrE,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;CAK5B,CAAA;AAED,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CACxB,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,GAAG,CAAA;eACd,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;MAGjE,OAAO;IACP,GAAG,CAAA;;;;sBAIa,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;OAE7C;GACD,CACF,CAAA;AAED,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,CAC7B,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,GAAG,CAAA;iBACf,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;GACrE,CACF,CAAA;AAED,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CACvB,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,GAAG,CAAA;;WAElB,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;eACZ,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;;;;MAO7C,OAAO;IACP,GAAG,CAAA;;;;sBAIa,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;OAE7C;GACD,CACF,CAAA;AAED,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;;CAEhC,CAAA","sourcesContent":["import { FC, ReactNode, useRef } from 'react'\nimport { createPortal } from 'react-dom'\nimport styled, { css, useTheme } from 'styled-components'\n\nimport { Box } from '../Box'\nimport { Text, type TextProps } from '../Text'\nimport { useBodyScrollLock } from '../hooks/useBodyScrollLock'\nimport { IconContainer } from '../sharedStyles/shared.styles'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\nimport { faXmark } from '@awesome.me/kit-46ca99185c/icons/classic/regular'\n\ninterface IModalContainer {\n // drawer state\n $drawer: boolean\n // modal width\n $width: string\n}\n\nexport type ModalProps = {\n /**\n * Title of the modal\n * @default \"\" (empty string)\n *\n * @example\n * ```tsx\n * <Modal title=\"MultiCar Account\" />\n * ```\n *\n * @example\n * ```tsx\n * <Modal title={{ typo: 'hero', children: 'MultiCar Account' }} />\n * ```\n */\n title?: string | TitleProps\n icon?: string\n children?: ReactNode\n rightPanel?: ReactNode\n showModal?: boolean\n handleClick: () => void\n drawer?: boolean\n cross?: boolean\n width?: string\n containerClass?: string\n portalContainer?: Element | DocumentFragment\n closeOnOverlayClick?: boolean\n /**\n * Keeps the title and close button visible while the content scrolls.\n * @default false\n */\n stickyHeader?: boolean\n /**\n * Rendered below the content, e.g. one or two buttons, which share the\n * width equally. Put the primary action last.\n */\n footer?: ReactNode\n /**\n * Keeps `footer` visible while the content scrolls.\n * @default false\n */\n stickyFooter?: boolean\n}\n\nexport type TitleProps = TextProps\n\nconst getDefaultTitleProps = (title: string): TitleProps => ({\n children: title,\n tag: 'h2',\n typo: 'heading-small',\n align: 'left',\n})\n\nexport const Modal: FC<ModalProps> = ({\n title = '',\n children,\n rightPanel,\n showModal = false,\n handleClick,\n drawer = true,\n cross = true,\n width,\n containerClass,\n portalContainer = document.body,\n closeOnOverlayClick = true,\n stickyHeader = false,\n footer,\n stickyFooter = false,\n}) => {\n const modalRef = useRef<HTMLDivElement>(null)\n const theme = useTheme()\n\n // oxlint-disable-next-line react/react-compiler\n useBodyScrollLock({ node: modalRef.current, showModal })\n\n const isTitleString = typeof title === 'string'\n const titleProps = isTitleString ? getDefaultTitleProps(title) : title\n\n if (!showModal) return null\n\n return createPortal(\n <Wrapper ref={modalRef}>\n <Overlay\n onClick={() => closeOnOverlayClick && handleClick()}\n $closeOnOverlayClick={closeOnOverlayClick}\n />\n <Container\n $drawer={drawer}\n $width={width || '460px'}\n className={containerClass}\n >\n <ScrollArea>\n <Header\n flex\n alignItems=\"flex-start\"\n justifyContent=\"space-between\"\n $sticky={stickyHeader}\n >\n <TitleElements flex direction=\"column\">\n <Text {...titleProps} />\n </TitleElements>\n <Box flex alignItems=\"center\" gap={'space.100'}>\n {rightPanel}\n {cross && (\n <IconContainer\n as=\"button\"\n onClick={handleClick}\n role=\"button\"\n title=\"Close modal\"\n $size={32}\n style={{\n background: theme.color.illustration.neutral[300],\n borderRadius: '100%',\n padding: '6px',\n border: 'none',\n cursor: 'pointer',\n }}\n >\n <FontAwesomeIcon\n icon={faXmark}\n color={theme.color.icon.base}\n />\n </IconContainer>\n )}\n </Box>\n </Header>\n <ContentArea flex direction=\"column\" $hasFooter={!!footer}>\n {children}\n </ContentArea>\n {footer ? <Footer $sticky={stickyFooter}>{footer}</Footer> : null}\n </ScrollArea>\n </Container>\n </Wrapper>,\n portalContainer,\n )\n}\n\nconst Wrapper = styled(Box)`\n display: flex;\n position: absolute;\n z-index: 999;\n top: 0;\n left: 0;\n height: 100vh;\n width: 100%;\n justify-content: center;\n align-items: center;\n`\n\nconst Overlay = styled.div<{ $closeOnOverlayClick: boolean }>`\n position: fixed;\n background: ${({ theme }) => theme.color.surface.base[900]};\n cursor: ${(props) => (props.$closeOnOverlayClick ? 'pointer' : 'default')};\n opacity: 0.4;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n`\n\nconst Container = styled.div<IModalContainer>(\n ({ $drawer, $width }) => css`\n display: flex;\n flex-direction: column;\n background: ${({ theme }) => theme.color.background[100]};\n box-sizing: border-box;\n border-radius: 16px;\n width: 100%;\n max-width: ${$width};\n position: fixed;\n max-height: calc(100vh - 64px);\n overflow: hidden;\n transition: all 0.3s ease-in-out;\n\n ${\n $drawer === true &&\n css`\n @media (max-width: 768px) {\n max-width: none;\n border-radius: 16px 16px 0px 0px;\n max-height: 90vh;\n\n position: fixed;\n right: 0;\n left: 0;\n bottom: 0;\n }\n `\n }\n `,\n)\n\n// The scrollbar lives here so Container's border-radius can clip it.\nconst ScrollArea = styled.div`\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n overscroll-behavior-y: none;\n`\n\nconst Header = styled(Box)<{ $sticky: boolean }>(\n ({ $sticky, theme }) => css`\n padding: ${theme.space[300]} ${theme.space[200]} ${theme.space[200]};\n\n ${\n $sticky &&\n css`\n position: sticky;\n top: 0;\n z-index: 1;\n background: ${theme.color.background[100]};\n `\n }\n `,\n)\n\nconst ContentArea = styled(Box)<{ $hasFooter: boolean }>(\n ({ $hasFooter, theme }) => css`\n padding: 0 ${theme.space[200]} ${$hasFooter ? '0' : theme.space[300]};\n `,\n)\n\nconst Footer = styled.div<{ $sticky: boolean }>(\n ({ $sticky, theme }) => css`\n display: flex;\n gap: ${theme.space[100]};\n padding: ${theme.space[300]} ${theme.space[200]};\n\n & > * {\n flex: 1;\n }\n\n ${\n $sticky &&\n css`\n position: sticky;\n bottom: 0;\n z-index: 1;\n background: ${theme.color.background[100]};\n `\n }\n `,\n)\n\nconst TitleElements = styled(Box)`\n align-self: center;\n`\n"]}
|
|
1
|
+
{"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../src/Modal/Modal.tsx"],"names":[],"mappings":"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","sourcesContent":["import { FC, ReactNode, useEffect, useId, useRef, useState } from 'react'\nimport { createPortal } from 'react-dom'\nimport styled, { css, useTheme } from 'styled-components'\n\nimport { Box } from '../Box'\nimport { Text, type TextProps } from '../Text'\nimport { useBodyScrollLock } from '../hooks/useBodyScrollLock'\nimport { IconContainer } from '../sharedStyles/shared.styles'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\nimport { faXmark } from '@awesome.me/kit-46ca99185c/icons/classic/regular'\n\ninterface IModalContainer {\n // drawer state\n $drawer: boolean\n // modal width\n $width: string\n}\n\nexport type ModalProps = {\n /**\n * Title of the modal\n * @default \"\" (empty string)\n *\n * @example\n * ```tsx\n * <Modal title=\"MultiCar Account\" />\n * ```\n *\n * @example\n * ```tsx\n * <Modal title={{ typo: 'hero', children: 'MultiCar Account' }} />\n * ```\n */\n title?: string | TitleProps\n icon?: string\n children?: ReactNode\n rightPanel?: ReactNode\n showModal?: boolean\n handleClick: () => void\n drawer?: boolean\n cross?: boolean\n width?: string\n containerClass?: string\n portalContainer?: Element | DocumentFragment\n closeOnOverlayClick?: boolean\n /**\n * Keeps the title and close button visible while the content scrolls.\n * @default false\n */\n stickyHeader?: boolean\n /**\n * Rendered below the content, e.g. one or two buttons, which share the\n * width equally. Put the primary action last.\n */\n footer?: ReactNode\n /**\n * Keeps `footer` visible while the content scrolls.\n * @default false\n */\n stickyFooter?: boolean\n}\n\nexport type TitleProps = TextProps\n\nconst getDefaultTitleProps = (title: string): TitleProps => ({\n children: title,\n tag: 'h2',\n typo: 'heading-small',\n align: 'left',\n})\n\nconst FOCUSABLE_SELECTOR =\n 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]):not([type=\"hidden\"]), select:not([disabled]), [tabindex]:not([tabindex=\"-1\"])'\n\nexport const Modal: FC<ModalProps> = ({\n title = '',\n children,\n rightPanel,\n showModal = false,\n handleClick,\n drawer = true,\n cross = true,\n width,\n containerClass,\n portalContainer = document.body,\n closeOnOverlayClick = true,\n stickyHeader = false,\n footer,\n stickyFooter = false,\n}) => {\n const [modalNode, setModalNode] = useState<HTMLDivElement | null>(null)\n const containerRef = useRef<HTMLDivElement>(null)\n const titleId = useId()\n const theme = useTheme()\n\n useBodyScrollLock({ node: modalNode, showModal })\n\n // Record the opener while rendering the open state, before any child can take focus.\n const [wasOpen, setWasOpen] = useState(false)\n const [opener, setOpener] = useState<HTMLElement | null>(null)\n if (showModal !== wasOpen) {\n setWasOpen(showModal)\n if (showModal) setOpener(document.activeElement as HTMLElement | null)\n }\n\n const latestRef = useRef({ handleClick, closeOnOverlayClick })\n useEffect(() => {\n latestRef.current = { handleClick, closeOnOverlayClick }\n })\n\n useEffect(() => {\n const container = containerRef.current\n if (!showModal || !container) return\n\n // A child with autoFocus may already have focus, so leave it there.\n if (!container.contains(document.activeElement)) container.focus()\n\n const handleKeyDown = (event: KeyboardEvent) => {\n const active = document.activeElement\n // Only the modal that has focus responds, so stacked modals close one at a time.\n if (!container.contains(active)) return\n\n if (event.key === 'Escape') {\n if (event.defaultPrevented || event.isComposing) return\n if (latestRef.current.closeOnOverlayClick) {\n latestRef.current.handleClick()\n }\n return\n }\n\n if (event.key !== 'Tab') return\n\n const focusable =\n container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)\n const first = focusable[0]\n const last = focusable[focusable.length - 1]\n\n if (!first) {\n event.preventDefault()\n } else if (event.shiftKey && (active === first || active === container)) {\n event.preventDefault()\n last.focus()\n } else if (!event.shiftKey && active === last) {\n event.preventDefault()\n first.focus()\n }\n }\n\n document.addEventListener('keydown', handleKeyDown)\n\n return () => {\n document.removeEventListener('keydown', handleKeyDown)\n opener?.focus()\n }\n }, [showModal, opener])\n\n const isTitleString = typeof title === 'string'\n const titleProps = isTitleString ? getDefaultTitleProps(title) : title\n const labelId = titleProps.id ?? titleId\n\n if (!showModal) return null\n\n return createPortal(\n <Wrapper ref={setModalNode}>\n <Overlay\n onClick={() => closeOnOverlayClick && handleClick()}\n $closeOnOverlayClick={closeOnOverlayClick}\n />\n <Container\n ref={containerRef}\n tabIndex={-1}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? labelId : undefined}\n $drawer={drawer}\n $width={width || '460px'}\n className={containerClass}\n >\n <ScrollArea>\n <Header\n flex\n alignItems=\"flex-start\"\n justifyContent=\"space-between\"\n $sticky={stickyHeader}\n >\n <TitleElements flex direction=\"column\">\n <Text {...titleProps} id={labelId} />\n </TitleElements>\n <Box flex alignItems=\"center\" gap={'space.100'}>\n {rightPanel}\n {cross && (\n <IconContainer\n as=\"button\"\n onClick={handleClick}\n role=\"button\"\n title=\"Close modal\"\n $size={32}\n style={{\n background: theme.color.illustration.neutral[300],\n borderRadius: '100%',\n padding: '6px',\n border: 'none',\n cursor: 'pointer',\n }}\n >\n <FontAwesomeIcon\n icon={faXmark}\n color={theme.color.icon.base}\n />\n </IconContainer>\n )}\n </Box>\n </Header>\n <ContentArea flex direction=\"column\" $hasFooter={!!footer}>\n {children}\n </ContentArea>\n {footer ? <Footer $sticky={stickyFooter}>{footer}</Footer> : null}\n </ScrollArea>\n </Container>\n </Wrapper>,\n portalContainer,\n )\n}\n\nconst Wrapper = styled(Box)`\n display: flex;\n position: absolute;\n z-index: 999;\n top: 0;\n left: 0;\n height: 100vh;\n width: 100%;\n justify-content: center;\n align-items: center;\n`\n\nconst Overlay = styled.div<{ $closeOnOverlayClick: boolean }>`\n position: fixed;\n background: ${({ theme }) => theme.color.surface.base[900]};\n cursor: ${(props) => (props.$closeOnOverlayClick ? 'pointer' : 'default')};\n opacity: 0.4;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n`\n\nconst Container = styled.div<IModalContainer>(\n ({ $drawer, $width }) => css`\n display: flex;\n flex-direction: column;\n background: ${({ theme }) => theme.color.background[100]};\n box-sizing: border-box;\n border-radius: 16px;\n width: 100%;\n max-width: ${$width};\n position: fixed;\n max-height: calc(100vh - 64px);\n overflow: hidden;\n outline: none;\n transition: all 0.3s ease-in-out;\n\n ${\n $drawer === true &&\n css`\n @media (max-width: 768px) {\n max-width: none;\n border-radius: 16px 16px 0px 0px;\n max-height: 90vh;\n\n position: fixed;\n right: 0;\n left: 0;\n bottom: 0;\n }\n `\n }\n `,\n)\n\n// The scrollbar lives here so Container's border-radius can clip it.\nconst ScrollArea = styled.div`\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n overscroll-behavior-y: none;\n`\n\nconst Header = styled(Box)<{ $sticky: boolean }>(\n ({ $sticky, theme }) => css`\n padding: ${theme.space[300]} ${theme.space[200]} ${theme.space[200]};\n\n ${\n $sticky &&\n css`\n position: sticky;\n top: 0;\n z-index: 1;\n background: ${theme.color.background[100]};\n `\n }\n `,\n)\n\nconst ContentArea = styled(Box)<{ $hasFooter: boolean }>(\n ({ $hasFooter, theme }) => css`\n padding: 0 ${theme.space[200]} ${$hasFooter ? '0' : theme.space[300]};\n `,\n)\n\nconst Footer = styled.div<{ $sticky: boolean }>(\n ({ $sticky, theme }) => css`\n display: flex;\n gap: ${theme.space[100]};\n padding: ${theme.space[300]} ${theme.space[200]};\n\n & > * {\n flex: 1;\n }\n\n ${\n $sticky &&\n css`\n position: sticky;\n bottom: 0;\n z-index: 1;\n background: ${theme.color.background[100]};\n `\n }\n `,\n)\n\nconst TitleElements = styled(Box)`\n align-self: center;\n`\n"]}
|
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
|
-
import { clearAllBodyScrollLocks, disableBodyScroll
|
|
2
|
+
import { clearAllBodyScrollLocks, disableBodyScroll } from 'body-scroll-lock';
|
|
3
|
+
// body-scroll-lock restores the body on every unlock, even while other locks
|
|
4
|
+
// remain, so count open locks and only release the body when the last closes.
|
|
5
|
+
let openLocks = 0;
|
|
3
6
|
const enhancedDisabeBodyScroll = (node) => {
|
|
4
7
|
disableBodyScroll(node, {
|
|
5
8
|
reserveScrollBarGap: true,
|
|
6
9
|
allowTouchMove: () => true,
|
|
7
10
|
});
|
|
8
|
-
|
|
11
|
+
if (openLocks === 0)
|
|
12
|
+
document.body.style.top = `-${window.scrollY}px`;
|
|
13
|
+
openLocks++;
|
|
14
|
+
};
|
|
15
|
+
const enhancedEnableBodyScroll = () => {
|
|
16
|
+
openLocks--;
|
|
17
|
+
if (openLocks > 0)
|
|
18
|
+
return;
|
|
19
|
+
clearAllBodyScrollLocks();
|
|
20
|
+
document.body.style.top = '';
|
|
9
21
|
};
|
|
10
22
|
export function useBodyScrollLock({ node, showModal, }) {
|
|
11
23
|
useEffect(() => {
|
|
12
|
-
|
|
13
|
-
}, []);
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
if (node === null)
|
|
24
|
+
if (node === null || !showModal)
|
|
16
25
|
return;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
enableBodyScroll(node);
|
|
22
|
-
}
|
|
26
|
+
enhancedDisabeBodyScroll(node);
|
|
27
|
+
return enhancedEnableBodyScroll;
|
|
23
28
|
}, [node, showModal]);
|
|
24
29
|
}
|
|
25
30
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/hooks/useBodyScrollLock/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACjC,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/hooks/useBodyScrollLock/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACjC,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAE7E,6EAA6E;AAC7E,8EAA8E;AAC9E,IAAI,SAAS,GAAG,CAAC,CAAA;AAEjB,MAAM,wBAAwB,GAAG,CAAC,IAA2B,EAAE,EAAE;IAC/D,iBAAiB,CAAC,IAAI,EAAE;QACtB,mBAAmB,EAAE,IAAI;QACzB,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI;KAC3B,CAAC,CAAA;IAEF,IAAI,SAAS,KAAK,CAAC;QAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,MAAM,CAAC,OAAO,IAAI,CAAA;IACrE,SAAS,EAAE,CAAA;AACb,CAAC,CAAA;AAED,MAAM,wBAAwB,GAAG,GAAG,EAAE;IACpC,SAAS,EAAE,CAAA;IACX,IAAI,SAAS,GAAG,CAAC;QAAE,OAAM;IAEzB,uBAAuB,EAAE,CAAA;IACzB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,EAAE,CAAA;AAC9B,CAAC,CAAA;AAED,MAAM,UAAU,iBAAiB,CAAC,EAChC,IAAI,EACJ,SAAS,GAIV;IACC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,SAAS;YAAE,OAAM;QAEvC,wBAAwB,CAAC,IAAI,CAAC,CAAA;QAE9B,OAAO,wBAAwB,CAAA;IACjC,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAA;AACvB,CAAC","sourcesContent":["import { useEffect } from 'react'\nimport { clearAllBodyScrollLocks, disableBodyScroll } from 'body-scroll-lock'\n\n// body-scroll-lock restores the body on every unlock, even while other locks\n// remain, so count open locks and only release the body when the last closes.\nlet openLocks = 0\n\nconst enhancedDisabeBodyScroll = (node: HTMLElement | Element) => {\n disableBodyScroll(node, {\n reserveScrollBarGap: true,\n allowTouchMove: () => true,\n })\n\n if (openLocks === 0) document.body.style.top = `-${window.scrollY}px`\n openLocks++\n}\n\nconst enhancedEnableBodyScroll = () => {\n openLocks--\n if (openLocks > 0) return\n\n clearAllBodyScrollLocks()\n document.body.style.top = ''\n}\n\nexport function useBodyScrollLock({\n node,\n showModal,\n}: {\n node: HTMLDivElement | null\n showModal: boolean\n}) {\n useEffect(() => {\n if (node === null || !showModal) return\n\n enhancedDisabeBodyScroll(node)\n\n return enhancedEnableBodyScroll\n }, [node, showModal])\n}\n"]}
|