react-miui 0.34.0 → 0.35.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/.claude/settings.local.json +2 -1
- package/CHANGELOG.md +13 -0
- package/dist/components/form/index.d.ts +1 -0
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +1 -0
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/form/input/Input.d.ts.map +1 -1
- package/dist/components/form/input/Input.js +9 -5
- package/dist/components/form/input/Input.js.map +1 -1
- package/dist/components/form/timepicker/TimePicker.css.d.ts +99 -0
- package/dist/components/form/timepicker/TimePicker.css.d.ts.map +1 -0
- package/dist/components/form/timepicker/TimePicker.css.js +116 -0
- package/dist/components/form/timepicker/TimePicker.css.js.map +1 -0
- package/dist/components/form/timepicker/TimePicker.d.ts +22 -0
- package/dist/components/form/timepicker/TimePicker.d.ts.map +1 -0
- package/dist/components/form/timepicker/TimePicker.js +141 -0
- package/dist/components/form/timepicker/TimePicker.js.map +1 -0
- package/dist/components/form/timepicker/TimePicker.styled.d.ts +936 -0
- package/dist/components/form/timepicker/TimePicker.styled.d.ts.map +1 -0
- package/dist/components/form/timepicker/TimePicker.styled.js +29 -0
- package/dist/components/form/timepicker/TimePicker.styled.js.map +1 -0
- package/dist/components/form/timepicker/TimePickerModal.d.ts +17 -0
- package/dist/components/form/timepicker/TimePickerModal.d.ts.map +1 -0
- package/dist/components/form/timepicker/TimePickerModal.js +92 -0
- package/dist/components/form/timepicker/TimePickerModal.js.map +1 -0
- package/dist/components/form/timepicker/Wheel.d.ts +12 -0
- package/dist/components/form/timepicker/Wheel.d.ts.map +1 -0
- package/dist/components/form/timepicker/Wheel.js +187 -0
- package/dist/components/form/timepicker/Wheel.js.map +1 -0
- package/dist/components/form/timepicker/utils.d.ts +4 -0
- package/dist/components/form/timepicker/utils.d.ts.map +1 -0
- package/dist/components/form/timepicker/utils.js +62 -0
- package/dist/components/form/timepicker/utils.js.map +1 -0
- package/dist/components/icons/Clock.d.ts +7 -0
- package/dist/components/icons/Clock.d.ts.map +1 -0
- package/dist/components/icons/Clock.js +45 -0
- package/dist/components/icons/Clock.js.map +1 -0
- package/dist/components/icons/Icon.d.ts +2 -1
- package/dist/components/icons/Icon.d.ts.map +1 -1
- package/dist/components/icons/Icon.js +3 -0
- package/dist/components/icons/Icon.js.map +1 -1
- package/dist/components/ui/modal/Modal.d.ts +1 -2
- package/dist/components/ui/modal/Modal.d.ts.map +1 -1
- package/dist/components/ui/modal/Modal.js +114 -42
- package/dist/components/ui/modal/Modal.js.map +1 -1
- package/dist/components/ui/modal/Modal.styled.d.ts +1 -1
- package/dist/components/ui/modal/Modal.styled.d.ts.map +1 -1
- package/dist/components/ui/modal/Modal.styled.js +40 -25
- package/dist/components/ui/modal/Modal.styled.js.map +1 -1
- package/dist/theme.css-global.d.ts.map +1 -1
- package/dist/theme.css-global.js +0 -1
- package/dist/theme.css-global.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/useNativeValidity.d.ts +11 -0
- package/dist/utils/useNativeValidity.d.ts.map +1 -0
- package/dist/utils/useNativeValidity.js +32 -0
- package/dist/utils/useNativeValidity.js.map +1 -0
- package/docs/assets/navigation.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/index.Pop.html +7 -7
- package/docs/documents/Test.html +2 -2
- package/docs/enums/index.ICON.html +3 -2
- package/docs/functions/index.Action.html +3 -3
- package/docs/functions/index.Button.html +3 -3
- package/docs/functions/index.Card.html +2 -2
- package/docs/functions/index.Checkbox.html +3 -3
- package/docs/functions/index.Choice.html +2 -2
- package/docs/functions/index.ColorPicker.html +3 -3
- package/docs/functions/index.CoveringLoader.html +3 -3
- package/docs/functions/index.DirectionPad.html +2 -2
- package/docs/functions/index.Drawer.html +2 -2
- package/docs/functions/index.EqualActions.html +2 -2
- package/docs/functions/index.FullLoader.html +3 -3
- package/docs/functions/index.Gap.html +2 -2
- package/docs/functions/index.HandleEsc.html +3 -3
- package/docs/functions/index.Header.html +3 -3
- package/docs/functions/index.HeaderIconAction.html +3 -3
- package/docs/functions/index.Icon-1.html +2 -2
- package/docs/functions/index.If.html +3 -3
- package/docs/functions/index.Input.html +1 -1
- package/docs/functions/index.KeyValue.html +2 -2
- package/docs/functions/index.Label.html +2 -2
- package/docs/functions/index.Line.html +3 -3
- package/docs/functions/index.List.html +2 -2
- package/docs/functions/index.Loader.html +3 -3
- package/docs/functions/index.Loading.html +3 -3
- package/docs/functions/index.Message.html +3 -3
- package/docs/functions/index.Modal.html +2 -2
- package/docs/functions/index.ModalButtons.html +3 -3
- package/docs/functions/index.PopLoader.html +3 -3
- package/docs/functions/index.PopOption.html +2 -2
- package/docs/functions/index.Progress.html +2 -2
- package/docs/functions/index.SearchContainer.html +2 -2
- package/docs/functions/index.Section.html +4 -4
- package/docs/functions/index.Select.html +2 -2
- package/docs/functions/index.Selector.html +2 -2
- package/docs/functions/index.Spacer.html +2 -2
- package/docs/functions/index.Stats.html +2 -2
- package/docs/functions/index.StickyHeader.html +4 -4
- package/docs/functions/index.Table.html +2 -2
- package/docs/functions/index.TextArea.html +2 -2
- package/docs/functions/index.TimePicker.html +10 -0
- package/docs/functions/index.ToasterProvider.html +3 -3
- package/docs/functions/index.Toggle.html +3 -3
- package/docs/functions/index.ToolButton.html +3 -3
- package/docs/functions/index.Tooltip.html +3 -3
- package/docs/functions/index.TooltipProvider.html +2 -2
- package/docs/functions/index.borderPxToRem.html +1 -1
- package/docs/functions/index.createTheme.html +1 -1
- package/docs/functions/index.css.html +1 -1
- package/docs/functions/index.dimensionsPxToRem.html +1 -1
- package/docs/functions/index.fontPxToRem.html +1 -1
- package/docs/functions/index.getCssText.html +1 -1
- package/docs/functions/index.globalCss.html +2 -2
- package/docs/functions/index.injectGlobalStyles.html +1 -1
- package/docs/functions/index.keyframes.html +1 -1
- package/docs/functions/index.pxToRem.html +1 -1
- package/docs/functions/index.styled.html +1 -1
- package/docs/functions/index.toast.html +2 -2
- package/docs/functions/index.useToaster.html +1 -1
- package/docs/index.html +2 -2
- package/docs/interfaces/index.IconProps.html +2 -2
- package/docs/interfaces/index.InputCustomProps.html +3 -3
- package/docs/interfaces/index.LoaderProps.html +6 -6
- package/docs/interfaces/index.StickyHeaderProps.html +4 -4
- package/docs/interfaces/index.ToasterProviderProps.html +3 -3
- package/docs/interfaces/index.TooltipProps.html +14 -14
- package/docs/interfaces/index.TooltipProviderProps.html +5 -5
- package/docs/modules/index.html +1 -1
- package/docs/modules.html +1 -1
- package/docs/types/index.ActionProps.html +1 -1
- package/docs/types/index.CardProps.html +1 -1
- package/docs/types/index.CheckboxProps.html +2 -2
- package/docs/types/index.ChoiceProps.html +1 -1
- package/docs/types/index.ColorPickerProps.html +1 -1
- package/docs/types/index.DirectionPadProps.html +1 -1
- package/docs/types/index.DrawerFrom.html +1 -1
- package/docs/types/index.DrawerProps.html +2 -2
- package/docs/types/index.EqualActionsProps.html +1 -1
- package/docs/types/index.HeaderProps.html +1 -1
- package/docs/types/index.InputProps.html +1 -1
- package/docs/types/index.KeyValueProps.html +1 -1
- package/docs/types/index.LabelProps.html +1 -1
- package/docs/types/index.OverwriteProps.html +1 -1
- package/docs/types/index.ProgressProps.html +2 -2
- package/docs/types/index.SelectProps.html +1 -1
- package/docs/types/index.SelectorProps.html +1 -1
- package/docs/types/index.Stat.html +1 -1
- package/docs/types/index.StatsProps.html +1 -1
- package/docs/types/index.TextAreaProps.html +1 -1
- package/docs/types/index.ThemeCSS.html +1 -1
- package/docs/types/index.TimePickerProps.html +1 -0
- package/docs/types/index.ToggleProps.html +2 -2
- package/docs/variables/index.ActionBadgeSelector.html +1 -1
- package/docs/variables/index.ActionCircleSelector.html +1 -1
- package/docs/variables/index.CheckboxCheckmarkWrapperSelector.html +1 -1
- package/docs/variables/index.CheckboxTextLabelSelector.html +1 -1
- package/docs/variables/index.ChoiceItemSelector.html +1 -1
- package/docs/variables/index.ColorPickerColorDisplaySelector.html +1 -1
- package/docs/variables/index.DirectionPadButtonDotSelector.html +1 -1
- package/docs/variables/index.DirectionPadButtonSelector.html +1 -1
- package/docs/variables/index.DirectionPadLineSelector.html +1 -1
- package/docs/variables/index.DirectionPadMiddleSelector.html +1 -1
- package/docs/variables/index.DrawerContentSelector.html +1 -1
- package/docs/variables/index.HeaderAfterSelector.html +1 -1
- package/docs/variables/index.HeaderBeforeSelector.html +1 -1
- package/docs/variables/index.HeaderContentsSelector.html +1 -1
- package/docs/variables/index.HeaderIconActionIconSelector.html +1 -1
- package/docs/variables/index.InputContainerSelector.html +1 -1
- package/docs/variables/index.InputInputSelector.html +1 -1
- package/docs/variables/index.InputLabelSelector.html +1 -1
- package/docs/variables/index.InputPrefixSelector.html +1 -1
- package/docs/variables/index.InputSuffixSelector.html +1 -1
- package/docs/variables/index.KeyValueIconSelector.html +1 -1
- package/docs/variables/index.KeyValueItemSelector.html +1 -1
- package/docs/variables/index.KeyValueKeySelector.html +1 -1
- package/docs/variables/index.KeyValuePairSelector.html +1 -1
- package/docs/variables/index.KeyValueValueSelector.html +1 -1
- package/docs/variables/index.LabelTextSelector.html +1 -1
- package/docs/variables/index.ListItemInnerContainerClassNameSelector.html +1 -1
- package/docs/variables/index.ModalContainerSelector.html +1 -1
- package/docs/variables/index.ModalRemovePaddingSelector.html +1 -1
- package/docs/variables/index.ModalTitleSelector.html +1 -1
- package/docs/variables/index.PopListSelector.html +1 -1
- package/docs/variables/index.PopOptionButtonSelector.html +1 -1
- package/docs/variables/index.PopOptionIconSelector.html +1 -1
- package/docs/variables/index.PopOverlaySelector.html +1 -1
- package/docs/variables/index.ProgressBackgroundSelector.html +1 -1
- package/docs/variables/index.ProgressValueSelector.html +1 -1
- package/docs/variables/index.SelectorItemSelector.html +1 -1
- package/docs/variables/index.StatsItemSelector.html +1 -1
- package/docs/variables/index.StatsLabelSelector.html +1 -1
- package/docs/variables/index.StatsSeparatorSelector.html +1 -1
- package/docs/variables/index.StatsValueSelector.html +1 -1
- package/docs/variables/index.TextAreaLabelSelector.html +1 -1
- package/docs/variables/index.TextAreaTextAreaSelector.html +1 -1
- package/docs/variables/index.TextAreaWrapperSelector.html +1 -1
- package/docs/variables/index.ToggleStyledToggleSelector.html +1 -1
- package/docs/variables/index.TooltipContentSelector.html +1 -1
- package/docs/variables/index.config.html +1 -1
- package/docs/variables/index.cssReset.html +2 -2
- package/docs/variables/index.darkTheme.html +1 -1
- package/docs/variables/index.miuiScrollbars.html +1 -1
- package/docs/variables/index.theme.html +1 -1
- package/esm/components/form/index.d.ts +1 -0
- package/esm/components/form/index.d.ts.map +1 -1
- package/esm/components/form/index.js +1 -0
- package/esm/components/form/index.js.map +1 -1
- package/esm/components/form/input/Input.d.ts.map +1 -1
- package/esm/components/form/input/Input.js +9 -5
- package/esm/components/form/input/Input.js.map +1 -1
- package/esm/components/form/timepicker/TimePicker.css.d.ts +99 -0
- package/esm/components/form/timepicker/TimePicker.css.d.ts.map +1 -0
- package/esm/components/form/timepicker/TimePicker.css.js +102 -0
- package/esm/components/form/timepicker/TimePicker.css.js.map +1 -0
- package/esm/components/form/timepicker/TimePicker.d.ts +22 -0
- package/esm/components/form/timepicker/TimePicker.d.ts.map +1 -0
- package/esm/components/form/timepicker/TimePicker.js +93 -0
- package/esm/components/form/timepicker/TimePicker.js.map +1 -0
- package/esm/components/form/timepicker/TimePicker.styled.d.ts +936 -0
- package/esm/components/form/timepicker/TimePicker.styled.d.ts.map +1 -0
- package/esm/components/form/timepicker/TimePicker.styled.js +20 -0
- package/esm/components/form/timepicker/TimePicker.styled.js.map +1 -0
- package/esm/components/form/timepicker/TimePickerModal.d.ts +17 -0
- package/esm/components/form/timepicker/TimePickerModal.d.ts.map +1 -0
- package/esm/components/form/timepicker/TimePickerModal.js +56 -0
- package/esm/components/form/timepicker/TimePickerModal.js.map +1 -0
- package/esm/components/form/timepicker/Wheel.d.ts +12 -0
- package/esm/components/form/timepicker/Wheel.d.ts.map +1 -0
- package/esm/components/form/timepicker/Wheel.js +151 -0
- package/esm/components/form/timepicker/Wheel.js.map +1 -0
- package/esm/components/form/timepicker/utils.d.ts +4 -0
- package/esm/components/form/timepicker/utils.d.ts.map +1 -0
- package/esm/components/form/timepicker/utils.js +58 -0
- package/esm/components/form/timepicker/utils.js.map +1 -0
- package/esm/components/icons/Clock.d.ts +7 -0
- package/esm/components/icons/Clock.d.ts.map +1 -0
- package/esm/components/icons/Clock.js +9 -0
- package/esm/components/icons/Clock.js.map +1 -0
- package/esm/components/icons/Icon.d.ts +2 -1
- package/esm/components/icons/Icon.d.ts.map +1 -1
- package/esm/components/icons/Icon.js +3 -0
- package/esm/components/icons/Icon.js.map +1 -1
- package/esm/components/ui/modal/Modal.d.ts +1 -2
- package/esm/components/ui/modal/Modal.d.ts.map +1 -1
- package/esm/components/ui/modal/Modal.js +103 -43
- package/esm/components/ui/modal/Modal.js.map +1 -1
- package/esm/components/ui/modal/Modal.styled.d.ts +1 -1
- package/esm/components/ui/modal/Modal.styled.d.ts.map +1 -1
- package/esm/components/ui/modal/Modal.styled.js +40 -25
- package/esm/components/ui/modal/Modal.styled.js.map +1 -1
- package/esm/theme.css-global.d.ts.map +1 -1
- package/esm/theme.css-global.js +0 -1
- package/esm/theme.css-global.js.map +1 -1
- package/esm/utils/index.d.ts +1 -0
- package/esm/utils/index.d.ts.map +1 -1
- package/esm/utils/index.js +1 -0
- package/esm/utils/index.js.map +1 -1
- package/esm/utils/useNativeValidity.d.ts +11 -0
- package/esm/utils/useNativeValidity.d.ts.map +1 -0
- package/esm/utils/useNativeValidity.js +29 -0
- package/esm/utils/useNativeValidity.js.map +1 -0
- package/package.json +1 -1
- package/src/components/form/index.ts +1 -0
- package/src/components/form/input/Input.stories.tsx +47 -1
- package/src/components/form/input/Input.tsx +11 -5
- package/src/components/form/timepicker/TimePicker.css.ts +132 -0
- package/src/components/form/timepicker/TimePicker.stories.tsx +107 -0
- package/src/components/form/timepicker/TimePicker.styled.ts +52 -0
- package/src/components/form/timepicker/TimePicker.tsx +229 -0
- package/src/components/form/timepicker/TimePickerModal.tsx +131 -0
- package/src/components/form/timepicker/Wheel.tsx +201 -0
- package/src/components/form/timepicker/utils.ts +66 -0
- package/src/components/icons/Clock.tsx +38 -0
- package/src/components/icons/Icon.tsx +3 -0
- package/src/components/ui/modal/Modal.stories.tsx +43 -7
- package/src/components/ui/modal/Modal.styled.ts +46 -25
- package/src/components/ui/modal/Modal.tsx +135 -52
- package/src/theme.css-global.ts +0 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/useNativeValidity.ts +57 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { forwardRef, useCallback, useEffect, useRef, useState } from "react";
|
|
1
|
+
import React, { forwardRef, useCallback, useEffect, useId, useRef, useState } from "react";
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
3
|
|
|
4
4
|
import { useForwardedRef } from "@bedrock-layout/use-forwarded-ref";
|
|
@@ -6,18 +6,34 @@ import { useForwardedRef } from "@bedrock-layout/use-forwarded-ref";
|
|
|
6
6
|
import type { ThemeCSS } from "../../../theme";
|
|
7
7
|
|
|
8
8
|
import { fnWithProps } from "../../../types/fnWithProps";
|
|
9
|
+
import { HandleEsc } from "../../utils/HandleEsc";
|
|
9
10
|
import { ContainerStyled, NEGATIVE_PADDING, OverlayStyled, RemovePadding, TitleStyled } from "./Modal.styled";
|
|
10
11
|
|
|
11
12
|
type OverlayProps = React.ComponentProps<typeof OverlayStyled>;
|
|
12
13
|
type ContainerProps = React.ComponentProps<typeof ContainerStyled>;
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
const FOCUSABLE_SELECTOR = [
|
|
16
|
+
"a[href]",
|
|
17
|
+
"button:not([disabled])",
|
|
18
|
+
"input:not([disabled])",
|
|
19
|
+
"select:not([disabled])",
|
|
20
|
+
"textarea:not([disabled])",
|
|
21
|
+
"[tabindex]:not([tabindex=\"-1\"])",
|
|
22
|
+
].join(",");
|
|
23
|
+
|
|
24
|
+
const prefersReducedMotion = () => {
|
|
25
|
+
if (typeof window === "undefined") {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
interface Props extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
|
|
15
32
|
onOverlayClick?: (() => void) | "close" | null;
|
|
16
33
|
closeOnEsc?: boolean;
|
|
17
34
|
onClose: () => void;
|
|
18
35
|
isOpen: boolean;
|
|
19
36
|
title?: React.ReactNode;
|
|
20
|
-
className?: string;
|
|
21
37
|
portal?: boolean | HTMLElement;
|
|
22
38
|
children: React.ReactNode;
|
|
23
39
|
|
|
@@ -39,57 +55,107 @@ const ModalBase = forwardRef<HTMLDivElement, Props>(({
|
|
|
39
55
|
portal = true,
|
|
40
56
|
position,
|
|
41
57
|
full,
|
|
58
|
+
...rest
|
|
42
59
|
}, ref) => {
|
|
43
60
|
const [isClosing, setIsClosing] = useState(false);
|
|
44
61
|
const [isRendered, setIsRendered] = useState(false);
|
|
45
62
|
const overlayRef = useRef<HTMLDivElement>(null);
|
|
46
63
|
const containerRef = useForwardedRef(ref);
|
|
64
|
+
const previouslyFocusedRef = useRef<HTMLElement | null>(null);
|
|
65
|
+
const titleId = useId();
|
|
47
66
|
|
|
48
67
|
useEffect(() => {
|
|
49
|
-
if (
|
|
68
|
+
if (isOpen) {
|
|
69
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
70
|
+
setIsRendered(true);
|
|
71
|
+
setIsClosing(false);
|
|
50
72
|
return;
|
|
51
73
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
};
|
|
58
|
-
document.addEventListener("keydown", onKeyDown);
|
|
59
|
-
return () => {
|
|
60
|
-
document.removeEventListener("keydown", onKeyDown);
|
|
61
|
-
};
|
|
62
|
-
}, [isOpen, closeOnEsc, onClose]);
|
|
63
|
-
|
|
64
|
-
useEffect(() => {
|
|
65
|
-
if (!isOpen) {
|
|
66
|
-
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
67
|
-
setIsClosing(true);
|
|
74
|
+
// Under reduced motion the close animation is disabled, so onAnimationEnd will
|
|
75
|
+
// never fire — unmount synchronously instead.
|
|
76
|
+
if (prefersReducedMotion()) {
|
|
77
|
+
setIsRendered(false);
|
|
78
|
+
setIsClosing(false);
|
|
68
79
|
return;
|
|
69
80
|
}
|
|
70
|
-
|
|
71
|
-
setIsClosing(false);
|
|
81
|
+
setIsClosing(true);
|
|
72
82
|
}, [isOpen]);
|
|
73
83
|
|
|
74
84
|
useEffect(() => {
|
|
75
85
|
if (!isClosing) {
|
|
76
86
|
return;
|
|
77
87
|
}
|
|
78
|
-
|
|
88
|
+
const overlay = overlayRef.current;
|
|
89
|
+
const container = containerRef.current;
|
|
79
90
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
80
|
-
if (!
|
|
91
|
+
if (!overlay || !container) {
|
|
81
92
|
return;
|
|
82
93
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
94
|
+
// animationFillMode: forwards leaves the keyframe in its end state, so flipping
|
|
95
|
+
// animationDirection to reverse alone wouldn't replay it. Force a restart by
|
|
96
|
+
// clearing animation, triggering reflow, then letting the variant's reverse run.
|
|
97
|
+
overlay.style.animation = "none";
|
|
98
|
+
container.style.animation = "none";
|
|
86
99
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
100
|
+
overlay.offsetHeight;
|
|
101
|
+
overlay.style.removeProperty("animation");
|
|
102
|
+
container.style.removeProperty("animation");
|
|
90
103
|
}, [isClosing, containerRef]);
|
|
91
104
|
|
|
92
|
-
|
|
105
|
+
// Focus management: capture previous focus on open, set initial focus inside the
|
|
106
|
+
// dialog, restore focus on close. Tab containment + AT-hiding is delegated to the
|
|
107
|
+
// `inert` attribute on everything outside the modal subtree (native browser handles
|
|
108
|
+
// Tab cycling, screen-reader pruning, and pointer-event blocking).
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (!isOpen || !isRendered) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const container = containerRef.current;
|
|
114
|
+
const overlay = overlayRef.current;
|
|
115
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
116
|
+
if (!container || !overlay) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
previouslyFocusedRef.current = document.activeElement instanceof HTMLElement
|
|
121
|
+
? document.activeElement
|
|
122
|
+
: null;
|
|
123
|
+
|
|
124
|
+
const focusables = container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR);
|
|
125
|
+
(focusables[0] ?? container).focus();
|
|
126
|
+
|
|
127
|
+
// Walk up from the overlay to <body>; at each level mark every sibling of the
|
|
128
|
+
// cursor as inert. Already-inert nodes (e.g. from a nesting modal) are skipped
|
|
129
|
+
// so that the outer owner remains responsible for cleanup — this gives correct
|
|
130
|
+
// stacking for nested modals without a separate registry.
|
|
131
|
+
const inerted: HTMLElement[] = [];
|
|
132
|
+
let cursor: HTMLElement = overlay;
|
|
133
|
+
while (cursor !== document.body) {
|
|
134
|
+
const parentEl: HTMLElement | null = cursor.parentElement;
|
|
135
|
+
if (!parentEl) {
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
for (const child of Array.from(parentEl.children)) {
|
|
139
|
+
if (child === cursor || !(child instanceof HTMLElement)) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (!child.hasAttribute("inert")) {
|
|
143
|
+
child.setAttribute("inert", "");
|
|
144
|
+
inerted.push(child);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
cursor = parentEl;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return () => {
|
|
151
|
+
inerted.forEach((el) => {
|
|
152
|
+
el.removeAttribute("inert");
|
|
153
|
+
});
|
|
154
|
+
previouslyFocusedRef.current?.focus();
|
|
155
|
+
};
|
|
156
|
+
}, [isOpen, isRendered, containerRef]);
|
|
157
|
+
|
|
158
|
+
const titleElem = title ? <TitleStyled id={titleId}>{title}</TitleStyled> : null;
|
|
93
159
|
|
|
94
160
|
const handleOverlayClick = useCallback((e: React.MouseEvent) => {
|
|
95
161
|
if (e.target !== e.currentTarget) {
|
|
@@ -98,16 +164,18 @@ const ModalBase = forwardRef<HTMLDivElement, Props>(({
|
|
|
98
164
|
if (onOverlayClick === "close") {
|
|
99
165
|
onClose();
|
|
100
166
|
}
|
|
101
|
-
if (typeof onOverlayClick === "function") {
|
|
167
|
+
else if (typeof onOverlayClick === "function") {
|
|
102
168
|
onOverlayClick();
|
|
103
169
|
}
|
|
104
170
|
}, [onOverlayClick, onClose]);
|
|
105
171
|
|
|
106
|
-
const handleAnimationEnd = useCallback(() => {
|
|
172
|
+
const handleAnimationEnd = useCallback((e: React.AnimationEvent) => {
|
|
173
|
+
if (e.target !== e.currentTarget) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
107
176
|
if (isOpen) {
|
|
108
177
|
return;
|
|
109
178
|
}
|
|
110
|
-
|
|
111
179
|
setIsRendered(false);
|
|
112
180
|
}, [isOpen]);
|
|
113
181
|
|
|
@@ -116,30 +184,35 @@ const ModalBase = forwardRef<HTMLDivElement, Props>(({
|
|
|
116
184
|
}
|
|
117
185
|
|
|
118
186
|
const overlayVariants: Pick<OverlayProps, "isClosing" | "position"> = {};
|
|
119
|
-
|
|
120
|
-
|
|
187
|
+
if (isClosing) {
|
|
188
|
+
overlayVariants.isClosing = true;
|
|
189
|
+
}
|
|
190
|
+
if (position != null) {
|
|
191
|
+
overlayVariants.position = position;
|
|
192
|
+
}
|
|
121
193
|
|
|
122
194
|
const containerVariants: Pick<ContainerProps, "isClosing" | "full"> = {};
|
|
123
|
-
|
|
124
|
-
|
|
195
|
+
if (isClosing) {
|
|
196
|
+
containerVariants.isClosing = true;
|
|
197
|
+
}
|
|
198
|
+
if (full != null) {
|
|
199
|
+
containerVariants.full = full;
|
|
200
|
+
}
|
|
125
201
|
|
|
126
202
|
const childrenCount = React.Children.count(children);
|
|
127
203
|
|
|
128
204
|
const chld = React.Children.map(children, (child, index) => {
|
|
129
|
-
if (React.isValidElement(child)) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
css.marginTop = NEGATIVE_PADDING;
|
|
135
|
-
}
|
|
136
|
-
if (index === childrenCount - 1) {
|
|
137
|
-
css.marginBottom = NEGATIVE_PADDING;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return React.cloneElement(child, { css });
|
|
205
|
+
if (React.isValidElement(child) && child.type === RemovePadding) {
|
|
206
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
|
|
207
|
+
const css: ThemeCSS = { ...(child.props.css ?? {}) };
|
|
208
|
+
if (index === 0 && titleElem == null) {
|
|
209
|
+
css.marginTop = NEGATIVE_PADDING;
|
|
141
210
|
}
|
|
142
|
-
|
|
211
|
+
if (index === childrenCount - 1) {
|
|
212
|
+
css.marginBottom = NEGATIVE_PADDING;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return React.cloneElement(child, { css });
|
|
143
216
|
}
|
|
144
217
|
return child;
|
|
145
218
|
});
|
|
@@ -151,7 +224,17 @@ const ModalBase = forwardRef<HTMLDivElement, Props>(({
|
|
|
151
224
|
ref={overlayRef}
|
|
152
225
|
onAnimationEnd={handleAnimationEnd}
|
|
153
226
|
>
|
|
154
|
-
<
|
|
227
|
+
{closeOnEsc ? <HandleEsc onPress={onClose} /> : null}
|
|
228
|
+
<ContainerStyled
|
|
229
|
+
role={"dialog"}
|
|
230
|
+
aria-modal={true}
|
|
231
|
+
aria-labelledby={titleElem ? titleId : undefined}
|
|
232
|
+
tabIndex={-1}
|
|
233
|
+
className={className}
|
|
234
|
+
{...containerVariants}
|
|
235
|
+
ref={containerRef}
|
|
236
|
+
{...rest}
|
|
237
|
+
>
|
|
155
238
|
{titleElem}
|
|
156
239
|
{chld}
|
|
157
240
|
</ContainerStyled>
|
package/src/theme.css-global.ts
CHANGED
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import type React from "react";
|
|
4
|
+
|
|
5
|
+
interface UseNativeValidityResult {
|
|
6
|
+
/** Combined: the explicit `error` prop OR the input's native invalid state. Pass to your `error`/styling. */
|
|
7
|
+
finalError: boolean;
|
|
8
|
+
/** Re-checks validity. Call from your `onBlur`. */
|
|
9
|
+
onBlur: (e: React.FocusEvent<HTMLInputElement>) => void;
|
|
10
|
+
/** Clears the invalid flag if the value just became valid. Call from your `onChange`. */
|
|
11
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
12
|
+
/** Fires when the form tries to submit an invalid value. Call from your `onInvalid`. */
|
|
13
|
+
onInvalid: () => void;
|
|
14
|
+
/** Clears the invalid flag if the input is currently valid. Useful after programmatic value changes. */
|
|
15
|
+
revalidate: (input: HTMLInputElement | null) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Tracks native HTML5 validity (pattern, required, type=email/url, etc.) and surfaces it
|
|
20
|
+
* as an `error` boolean — the rule mirrors the CSS `:user-invalid` pseudo-class:
|
|
21
|
+
* invalid is sticky after blur or a failed submit, and clears as soon as the value becomes valid.
|
|
22
|
+
*
|
|
23
|
+
* Compose the returned handlers with your component's own `onBlur` / `onChange` / `onInvalid`.
|
|
24
|
+
*/
|
|
25
|
+
const useNativeValidity = (error: boolean | undefined): UseNativeValidityResult => {
|
|
26
|
+
const [nativeInvalid, setNativeInvalid] = useState(false);
|
|
27
|
+
|
|
28
|
+
const onBlur = useCallback((e: React.FocusEvent<HTMLInputElement>) => {
|
|
29
|
+
setNativeInvalid(!e.currentTarget.checkValidity());
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
const onChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
33
|
+
if (e.currentTarget.checkValidity()) {
|
|
34
|
+
setNativeInvalid(false);
|
|
35
|
+
}
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
const onInvalid = useCallback(() => {
|
|
39
|
+
setNativeInvalid(true);
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
const revalidate = useCallback((input: HTMLInputElement | null) => {
|
|
43
|
+
if (input?.checkValidity()) {
|
|
44
|
+
setNativeInvalid(false);
|
|
45
|
+
}
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
finalError: Boolean(error) || nativeInvalid,
|
|
50
|
+
onBlur,
|
|
51
|
+
onChange,
|
|
52
|
+
onInvalid,
|
|
53
|
+
revalidate,
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export { useNativeValidity };
|