@mrshmllw/smores-react 16.6.0 → 16.7.1
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.
|
@@ -51,10 +51,12 @@ export const Dropdown = forwardRef(function Dropdown({ id: idProp, placeholder,
|
|
|
51
51
|
onSelect?.(value);
|
|
52
52
|
onInputChange?.(event);
|
|
53
53
|
setValue(value);
|
|
54
|
-
}, $error: error, ref: ref, onBlur: onBlur, name: name, autoComplete: autoComplete, "aria-label": ariaLabel, "aria-invalid": error || undefined, "aria-required": required || undefined, "aria-describedby": describedBy, $frontIcon: !!iconToRender, $fallbackStyle: fallbackStyle, value: value ?? '', children: [_jsx("option", { value: "", hidden: !showDefaultOption, disabled: true, children: defaultOptionLabel() }), dropdownItemsGroups.map((groupItems, i) => hasOptGroups ? (_jsx("optgroup", { label: groupItems[0].optionGroupLabel ?? 'Other', children: groupItems.map((el, j) => (_jsx("option", { value: el.value, children: el.label }, `${i}-${j}`))) }, i)) : (groupItems.map((el, j) => (_jsx("option", { value: el.value, children: el.label }, j)))))] }), _jsx(Caret, { children: _jsx(IconContainer, { $size:
|
|
54
|
+
}, $error: error, ref: ref, onBlur: onBlur, name: name, autoComplete: autoComplete, "aria-label": ariaLabel, "aria-invalid": error || undefined, "aria-required": required || undefined, "aria-describedby": describedBy, $frontIcon: !!iconToRender, $fallbackStyle: fallbackStyle, value: value ?? '', children: [_jsx("option", { value: "", hidden: !showDefaultOption, disabled: true, children: defaultOptionLabel() }), dropdownItemsGroups.map((groupItems, i) => hasOptGroups ? (_jsx("optgroup", { label: groupItems[0].optionGroupLabel ?? 'Other', children: groupItems.map((el, j) => (_jsx("option", { value: el.value, children: el.label }, `${i}-${j}`))) }, i)) : (groupItems.map((el, j) => (_jsx("option", { value: el.value, children: el.label }, j)))))] }), _jsx(Caret, { children: _jsx(IconContainer, { $size: CHEVRON_SIZE, children: _jsx(FontAwesomeIcon, { icon: faChevronDown, color: theme.color.icon.subtle }) }) })] }) }));
|
|
55
55
|
});
|
|
56
|
-
|
|
56
|
+
const CHEVRON_SIZE = 16;
|
|
57
57
|
const CHEVRON_INSET = 20;
|
|
58
|
+
const CHEVRON_TEXT_GAP = 4;
|
|
59
|
+
const TEXT_PADDING_RIGHT = CHEVRON_INSET + CHEVRON_SIZE + CHEVRON_TEXT_GAP;
|
|
58
60
|
const StyledSelect = styled.select.attrs(fieldFocusAttrs)(({ theme, $error, $frontIcon, $fallbackStyle, value }) => {
|
|
59
61
|
const borderColor = (color) => $error ? theme.color.feedback.negative[200] : color;
|
|
60
62
|
return css `
|
|
@@ -62,7 +64,7 @@ const StyledSelect = styled.select.attrs(fieldFocusAttrs)(({ theme, $error, $fro
|
|
|
62
64
|
width: 100%;
|
|
63
65
|
height: 48px;
|
|
64
66
|
margin: 0;
|
|
65
|
-
padding: 12px
|
|
67
|
+
padding: 12px ${TEXT_PADDING_RIGHT}px 12px ${$frontIcon ? '42px' : '12px'};
|
|
66
68
|
border: 2px solid ${borderColor(theme.color.border.subtle)};
|
|
67
69
|
border-radius: 12px;
|
|
68
70
|
background-color: ${$fallbackStyle
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dropdown.js","sourceRoot":"","sources":["../../src/Dropdown/Dropdown.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n FocusEvent,\n FormEvent,\n ForwardedRef,\n forwardRef,\n ReactNode,\n useMemo,\n} from 'react'\nimport styled, { css, useTheme } from 'styled-components'\n\nimport { Box } from '../Box'\nimport { Icons } from '../Icon'\n\nimport { Field } from '../fields/Field'\nimport { CommonFieldProps } from '../fields/commonFieldTypes'\nimport {\n InputLeadingIconContainer,\n StyledFrontIcon,\n} from '../fields/components/CommonInput'\nimport {\n fieldDisabledStyle,\n fieldFocusAttrs,\n fieldFocusRing,\n} from '../fields/components/fieldStyles'\nimport { useUniqueId } from '../utils/id'\nimport { useControllableState } from '../utils/useControlledState'\nimport { IconContainer } from '../sharedStyles/shared.styles'\nimport { faChevronDown } from '@awesome.me/kit-46ca99185c/icons/classic/solid'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\n\nexport type DropdownItem = {\n optionGroupLabel?: string\n label: string\n value: string\n}\n\nexport interface Props extends CommonFieldProps {\n placeholder?: string\n showDefaultOption?: boolean\n customDefaultOption?: string\n name?: string\n value?: string | null\n defaultValue?: string\n disabled?: boolean\n list: DropdownItem[]\n frontIcon?: Icons\n iconComponent?: ReactNode\n fallbackStyle?: boolean\n /** Accessible name, for when there is no visible `label` */\n 'aria-label'?: string\n /** Lets the browser autofill the field, e.g. `honorific-prefix` */\n autoComplete?: string\n onSelect?: (element: string) => void\n onBlur?: (e: FocusEvent<HTMLSelectElement>) => void\n}\n\ntype TruncateProps =\n | {\n onSelect: (e: string) => void\n onInputChange?: (e: FormEvent<HTMLSelectElement>) => void\n }\n | {\n onSelect?: (e: string) => void\n onInputChange: (e: FormEvent<HTMLSelectElement>) => void\n }\n\nexport type DropdownProps = Props & TruncateProps\n\nexport const Dropdown = forwardRef(function Dropdown(\n {\n id: idProp,\n placeholder,\n showDefaultOption = false,\n customDefaultOption,\n name,\n value: valueProp,\n defaultValue,\n disabled = false,\n list,\n onSelect,\n error = false,\n onInputChange,\n onBlur,\n frontIcon,\n iconComponent,\n fallbackStyle,\n required,\n 'aria-label': ariaLabel,\n autoComplete,\n ...fieldProps\n }: DropdownProps,\n ref: ForwardedRef<HTMLSelectElement>,\n) {\n const theme = useTheme()\n const [value, setValue] = useControllableState({\n initialState: defaultValue,\n stateProp: valueProp,\n })\n const id = useUniqueId(idProp)\n const assistiveTextId = `${id}-assistive-text`\n const errorMsgId = `${id}-error`\n const hasOptGroups = list.findIndex((item) => !!item.optionGroupLabel) !== -1\n\n const dropdownItemsGroups = useMemo(() => {\n const itemsPerGroupLabel = new Map<string, DropdownItem[]>()\n\n list.forEach((item) => {\n const key = item.optionGroupLabel ?? ''\n const group = itemsPerGroupLabel.get(key) ?? []\n\n group.push(item)\n itemsPerGroupLabel.set(key, group)\n })\n\n return Array.from(itemsPerGroupLabel.values())\n }, [list])\n\n const defaultOptionLabel = () => {\n if (!showDefaultOption) {\n return placeholder\n }\n return customDefaultOption ?? 'Select an option'\n }\n\n // Only reference messages that the field actually renders.\n const describedBy =\n [\n fieldProps.assistiveText &&\n (!fieldProps.renderAsTitle || fieldProps.label) &&\n assistiveTextId,\n error && fieldProps.errorMsg && errorMsgId,\n ]\n .filter(Boolean)\n .join(' ') || undefined\n\n const iconToRender = iconComponent ? (\n <InputLeadingIconContainer $size={16} $iconColor={theme.color.text.base}>\n {iconComponent}\n </InputLeadingIconContainer>\n ) : frontIcon ? (\n <StyledFrontIcon render={frontIcon} color=\"color.icon.subtle\" />\n ) : null\n\n return (\n <Field\n {...fieldProps}\n htmlFor={id}\n error={error}\n required={required}\n assistiveTextId={assistiveTextId}\n errorMsgId={errorMsgId}\n >\n <Box flex alignItems=\"center\" style={{ position: 'relative' }}>\n {iconToRender}\n <StyledSelect\n id={id}\n disabled={disabled || list.length < 1}\n onChange={(event) => {\n const value = event.currentTarget.value\n\n onSelect?.(value)\n onInputChange?.(event)\n setValue(value)\n }}\n $error={error}\n ref={ref}\n onBlur={onBlur}\n name={name}\n autoComplete={autoComplete}\n aria-label={ariaLabel}\n aria-invalid={error || undefined}\n aria-required={required || undefined}\n aria-describedby={describedBy}\n $frontIcon={!!iconToRender}\n $fallbackStyle={fallbackStyle}\n value={value ?? ''}\n >\n <option value=\"\" hidden={!showDefaultOption} disabled>\n {defaultOptionLabel()}\n </option>\n\n {dropdownItemsGroups.map((groupItems, i) =>\n hasOptGroups ? (\n <optgroup\n key={i}\n label={groupItems[0].optionGroupLabel ?? 'Other'}\n >\n {groupItems.map((el, j) => (\n <option key={`${i}-${j}`} value={el.value}>\n {el.label}\n </option>\n ))}\n </optgroup>\n ) : (\n groupItems.map((el, j) => (\n <option key={j} value={el.value}>\n {el.label}\n </option>\n ))\n ),\n )}\n </StyledSelect>\n <Caret>\n <IconContainer $size={16}>\n <FontAwesomeIcon\n icon={faChevronDown}\n color={theme.color.icon.subtle}\n />\n </IconContainer>\n </Caret>\n </Box>\n </Field>\n )\n})\n\ninterface SSelect {\n $error: boolean\n $frontIcon: boolean\n $fallbackStyle?: boolean\n value?: string | null\n}\n\n// Figma's 32px chevron slot sits 12px from the edge, 16px from the text\nconst CHEVRON_INSET = 20\n\nconst StyledSelect = styled.select.attrs(fieldFocusAttrs)<SSelect>(\n ({ theme, $error, $frontIcon, $fallbackStyle, value }) => {\n const borderColor = (color: string) =>\n $error ? theme.color.feedback.negative[200] : color\n\n return css`\n appearance: none;\n width: 100%;\n height: 48px;\n margin: 0;\n padding: 12px 60px 12px ${$frontIcon ? '42px' : '12px'};\n border: 2px solid ${borderColor(theme.color.border.subtle)};\n border-radius: 12px;\n background-color: ${\n $fallbackStyle\n ? theme.color.surface.base[300]\n : theme.color.background['000']\n };\n color: ${value === '' ? theme.color.text.subtle : theme.color.text.base};\n font-family: inherit;\n font-size: 16px;\n line-height: 20px;\n text-overflow: ellipsis;\n cursor: pointer;\n outline: none;\n\n option,\n optgroup {\n color: ${theme.color.text.base};\n }\n\n // Hover mustn't override the focus border while the pointer is over a\n // focused field.\n &:hover:not(:disabled):not(:focus) {\n border-color: ${borderColor(theme.color.border.base)};\n }\n\n &:focus {\n border-color: ${borderColor(theme.color.border.contrast)};\n }\n\n ${fieldFocusRing}\n ${fieldDisabledStyle}\n `\n },\n)\n\nconst Caret = styled.div`\n position: absolute;\n z-index: 1;\n right: ${CHEVRON_INSET}px;\n display: flex;\n pointer-events: none;\n`\n"]}
|
|
1
|
+
{"version":3,"file":"Dropdown.js","sourceRoot":"","sources":["../../src/Dropdown/Dropdown.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n FocusEvent,\n FormEvent,\n ForwardedRef,\n forwardRef,\n ReactNode,\n useMemo,\n} from 'react'\nimport styled, { css, useTheme } from 'styled-components'\n\nimport { Box } from '../Box'\nimport { Icons } from '../Icon'\n\nimport { Field } from '../fields/Field'\nimport { CommonFieldProps } from '../fields/commonFieldTypes'\nimport {\n InputLeadingIconContainer,\n StyledFrontIcon,\n} from '../fields/components/CommonInput'\nimport {\n fieldDisabledStyle,\n fieldFocusAttrs,\n fieldFocusRing,\n} from '../fields/components/fieldStyles'\nimport { useUniqueId } from '../utils/id'\nimport { useControllableState } from '../utils/useControlledState'\nimport { IconContainer } from '../sharedStyles/shared.styles'\nimport { faChevronDown } from '@awesome.me/kit-46ca99185c/icons/classic/solid'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\n\nexport type DropdownItem = {\n optionGroupLabel?: string\n label: string\n value: string\n}\n\nexport interface Props extends CommonFieldProps {\n placeholder?: string\n showDefaultOption?: boolean\n customDefaultOption?: string\n name?: string\n value?: string | null\n defaultValue?: string\n disabled?: boolean\n list: DropdownItem[]\n frontIcon?: Icons\n iconComponent?: ReactNode\n fallbackStyle?: boolean\n /** Accessible name, for when there is no visible `label` */\n 'aria-label'?: string\n /** Lets the browser autofill the field, e.g. `honorific-prefix` */\n autoComplete?: string\n onSelect?: (element: string) => void\n onBlur?: (e: FocusEvent<HTMLSelectElement>) => void\n}\n\ntype TruncateProps =\n | {\n onSelect: (e: string) => void\n onInputChange?: (e: FormEvent<HTMLSelectElement>) => void\n }\n | {\n onSelect?: (e: string) => void\n onInputChange: (e: FormEvent<HTMLSelectElement>) => void\n }\n\nexport type DropdownProps = Props & TruncateProps\n\nexport const Dropdown = forwardRef(function Dropdown(\n {\n id: idProp,\n placeholder,\n showDefaultOption = false,\n customDefaultOption,\n name,\n value: valueProp,\n defaultValue,\n disabled = false,\n list,\n onSelect,\n error = false,\n onInputChange,\n onBlur,\n frontIcon,\n iconComponent,\n fallbackStyle,\n required,\n 'aria-label': ariaLabel,\n autoComplete,\n ...fieldProps\n }: DropdownProps,\n ref: ForwardedRef<HTMLSelectElement>,\n) {\n const theme = useTheme()\n const [value, setValue] = useControllableState({\n initialState: defaultValue,\n stateProp: valueProp,\n })\n const id = useUniqueId(idProp)\n const assistiveTextId = `${id}-assistive-text`\n const errorMsgId = `${id}-error`\n const hasOptGroups = list.findIndex((item) => !!item.optionGroupLabel) !== -1\n\n const dropdownItemsGroups = useMemo(() => {\n const itemsPerGroupLabel = new Map<string, DropdownItem[]>()\n\n list.forEach((item) => {\n const key = item.optionGroupLabel ?? ''\n const group = itemsPerGroupLabel.get(key) ?? []\n\n group.push(item)\n itemsPerGroupLabel.set(key, group)\n })\n\n return Array.from(itemsPerGroupLabel.values())\n }, [list])\n\n const defaultOptionLabel = () => {\n if (!showDefaultOption) {\n return placeholder\n }\n return customDefaultOption ?? 'Select an option'\n }\n\n // Only reference messages that the field actually renders.\n const describedBy =\n [\n fieldProps.assistiveText &&\n (!fieldProps.renderAsTitle || fieldProps.label) &&\n assistiveTextId,\n error && fieldProps.errorMsg && errorMsgId,\n ]\n .filter(Boolean)\n .join(' ') || undefined\n\n const iconToRender = iconComponent ? (\n <InputLeadingIconContainer $size={16} $iconColor={theme.color.text.base}>\n {iconComponent}\n </InputLeadingIconContainer>\n ) : frontIcon ? (\n <StyledFrontIcon render={frontIcon} color=\"color.icon.subtle\" />\n ) : null\n\n return (\n <Field\n {...fieldProps}\n htmlFor={id}\n error={error}\n required={required}\n assistiveTextId={assistiveTextId}\n errorMsgId={errorMsgId}\n >\n <Box flex alignItems=\"center\" style={{ position: 'relative' }}>\n {iconToRender}\n <StyledSelect\n id={id}\n disabled={disabled || list.length < 1}\n onChange={(event) => {\n const value = event.currentTarget.value\n\n onSelect?.(value)\n onInputChange?.(event)\n setValue(value)\n }}\n $error={error}\n ref={ref}\n onBlur={onBlur}\n name={name}\n autoComplete={autoComplete}\n aria-label={ariaLabel}\n aria-invalid={error || undefined}\n aria-required={required || undefined}\n aria-describedby={describedBy}\n $frontIcon={!!iconToRender}\n $fallbackStyle={fallbackStyle}\n value={value ?? ''}\n >\n <option value=\"\" hidden={!showDefaultOption} disabled>\n {defaultOptionLabel()}\n </option>\n\n {dropdownItemsGroups.map((groupItems, i) =>\n hasOptGroups ? (\n <optgroup\n key={i}\n label={groupItems[0].optionGroupLabel ?? 'Other'}\n >\n {groupItems.map((el, j) => (\n <option key={`${i}-${j}`} value={el.value}>\n {el.label}\n </option>\n ))}\n </optgroup>\n ) : (\n groupItems.map((el, j) => (\n <option key={j} value={el.value}>\n {el.label}\n </option>\n ))\n ),\n )}\n </StyledSelect>\n <Caret>\n <IconContainer $size={CHEVRON_SIZE}>\n <FontAwesomeIcon\n icon={faChevronDown}\n color={theme.color.icon.subtle}\n />\n </IconContainer>\n </Caret>\n </Box>\n </Field>\n )\n})\n\ninterface SSelect {\n $error: boolean\n $frontIcon: boolean\n $fallbackStyle?: boolean\n value?: string | null\n}\n\nconst CHEVRON_SIZE = 16\nconst CHEVRON_INSET = 20\nconst CHEVRON_TEXT_GAP = 4\nconst TEXT_PADDING_RIGHT = CHEVRON_INSET + CHEVRON_SIZE + CHEVRON_TEXT_GAP\n\nconst StyledSelect = styled.select.attrs(fieldFocusAttrs)<SSelect>(\n ({ theme, $error, $frontIcon, $fallbackStyle, value }) => {\n const borderColor = (color: string) =>\n $error ? theme.color.feedback.negative[200] : color\n\n return css`\n appearance: none;\n width: 100%;\n height: 48px;\n margin: 0;\n padding: 12px ${TEXT_PADDING_RIGHT}px 12px ${$frontIcon ? '42px' : '12px'};\n border: 2px solid ${borderColor(theme.color.border.subtle)};\n border-radius: 12px;\n background-color: ${\n $fallbackStyle\n ? theme.color.surface.base[300]\n : theme.color.background['000']\n };\n color: ${value === '' ? theme.color.text.subtle : theme.color.text.base};\n font-family: inherit;\n font-size: 16px;\n line-height: 20px;\n text-overflow: ellipsis;\n cursor: pointer;\n outline: none;\n\n option,\n optgroup {\n color: ${theme.color.text.base};\n }\n\n // Hover mustn't override the focus border while the pointer is over a\n // focused field.\n &:hover:not(:disabled):not(:focus) {\n border-color: ${borderColor(theme.color.border.base)};\n }\n\n &:focus {\n border-color: ${borderColor(theme.color.border.contrast)};\n }\n\n ${fieldFocusRing}\n ${fieldDisabledStyle}\n `\n },\n)\n\nconst Caret = styled.div`\n position: absolute;\n z-index: 1;\n right: ${CHEVRON_INSET}px;\n display: flex;\n pointer-events: none;\n`\n"]}
|
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":"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","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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrshmllw/smores-react",
|
|
3
|
-
"version": "16.
|
|
3
|
+
"version": "16.7.1",
|
|
4
4
|
"description": "Collection of React components used by Marshmallow Technology",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"@testing-library/jest-dom": "^7.0.1",
|
|
79
79
|
"@testing-library/react": "^16.3.3",
|
|
80
80
|
"@types/body-scroll-lock": "^3.1.2",
|
|
81
|
-
"@types/node": "^26.6.
|
|
81
|
+
"@types/node": "^26.6.4",
|
|
82
82
|
"@types/react": "^19.3.0",
|
|
83
83
|
"@types/react-dom": "^19.3.0",
|
|
84
84
|
"axe-playwright": "^2.2.2",
|