react-miui 0.33.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.json +12 -0
- package/.claude/settings.local.json +5 -1
- package/.storybook/preview.tsx +10 -4
- package/CHANGELOG.md +22 -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/drawer/Drawer.d.ts +10 -1
- package/dist/components/ui/drawer/Drawer.d.ts.map +1 -1
- package/dist/components/ui/drawer/Drawer.js +135 -15
- package/dist/components/ui/drawer/Drawer.js.map +1 -1
- package/dist/components/ui/drawer/Drawer.styled.d.ts +86 -1
- package/dist/components/ui/drawer/Drawer.styled.d.ts.map +1 -1
- package/dist/components/ui/drawer/Drawer.styled.js +13 -1
- package/dist/components/ui/drawer/Drawer.styled.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/components/ui/toaster/Toaster.d.ts.map +1 -1
- package/dist/components/ui/toaster/Toaster.js +7 -1
- package/dist/components/ui/toaster/Toaster.js.map +1 -1
- package/dist/components/ui/tooltip/Tooltip.d.ts +30 -0
- package/dist/components/ui/tooltip/Tooltip.d.ts.map +1 -0
- package/dist/components/ui/tooltip/Tooltip.js +81 -0
- package/dist/components/ui/tooltip/Tooltip.js.map +1 -0
- package/dist/components/ui/tooltip/Tooltip.styled.d.ts +173 -0
- package/dist/components/ui/tooltip/Tooltip.styled.d.ts.map +1 -0
- package/dist/components/ui/tooltip/Tooltip.styled.js +65 -0
- package/dist/components/ui/tooltip/Tooltip.styled.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.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/highlight.css +7 -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 +4 -4
- package/docs/functions/index.Card.html +3 -3
- 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 +3 -3
- 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 +4 -4
- 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 +4 -4
- 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 +3 -3
- package/docs/functions/index.Section.html +4 -4
- package/docs/functions/index.Select.html +3 -3
- package/docs/functions/index.Selector.html +2 -2
- package/docs/functions/index.Spacer.html +3 -3
- package/docs/functions/index.Stats.html +2 -2
- package/docs/functions/index.StickyHeader.html +4 -4
- package/docs/functions/index.Table.html +3 -3
- 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 +4 -4
- package/docs/functions/index.Tooltip.html +18 -0
- package/docs/functions/index.TooltipProvider.html +6 -0
- 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 +36 -0
- package/docs/interfaces/index.TooltipProviderProps.html +13 -0
- 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 -0
- package/docs/types/index.DrawerProps.html +28 -1
- 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 -0
- 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/drawer/Drawer.d.ts +10 -1
- package/esm/components/ui/drawer/Drawer.d.ts.map +1 -1
- package/esm/components/ui/drawer/Drawer.js +139 -15
- package/esm/components/ui/drawer/Drawer.js.map +1 -1
- package/esm/components/ui/drawer/Drawer.styled.d.ts +86 -1
- package/esm/components/ui/drawer/Drawer.styled.d.ts.map +1 -1
- package/esm/components/ui/drawer/Drawer.styled.js +12 -1
- package/esm/components/ui/drawer/Drawer.styled.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/components/ui/toaster/Toaster.d.ts.map +1 -1
- package/esm/components/ui/toaster/Toaster.js +8 -2
- package/esm/components/ui/toaster/Toaster.js.map +1 -1
- package/esm/components/ui/tooltip/Tooltip.d.ts +30 -0
- package/esm/components/ui/tooltip/Tooltip.d.ts.map +1 -0
- package/esm/components/ui/tooltip/Tooltip.js +43 -0
- package/esm/components/ui/tooltip/Tooltip.js.map +1 -0
- package/esm/components/ui/tooltip/Tooltip.styled.d.ts +173 -0
- package/esm/components/ui/tooltip/Tooltip.styled.d.ts.map +1 -0
- package/esm/components/ui/tooltip/Tooltip.styled.js +28 -0
- package/esm/components/ui/tooltip/Tooltip.styled.js.map +1 -0
- package/esm/index.d.ts +1 -0
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +1 -0
- package/esm/index.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 +2 -1
- package/pnpm-workspace.yaml +3 -0
- package/src/bugfixes/ToastsFromModal.stories.tsx +59 -0
- 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/drawer/Drawer.stories.tsx +143 -59
- package/src/components/ui/drawer/Drawer.styled.ts +13 -0
- package/src/components/ui/drawer/Drawer.tsx +214 -20
- 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/components/ui/toaster/Toaster.tsx +12 -2
- package/src/components/ui/tooltip/Tooltip.stories.tsx +285 -0
- package/src/components/ui/tooltip/Tooltip.styled.ts +36 -0
- package/src/components/ui/tooltip/Tooltip.tsx +195 -0
- package/src/index.ts +1 -0
- package/src/theme.css-global.ts +0 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/useNativeValidity.ts +57 -0
|
@@ -13,20 +13,25 @@ const overlay = keyframes({
|
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
const OverlayStyled = styled("div", {
|
|
16
|
-
position: "fixed",
|
|
17
|
-
zIndex: 4,
|
|
18
|
-
top: 0,
|
|
19
|
-
bottom: 0,
|
|
20
|
-
left: 0,
|
|
21
|
-
right: 0,
|
|
22
|
-
display: "flex",
|
|
23
|
-
alignItems: "center",
|
|
24
|
-
justifyContent: "center",
|
|
25
|
-
animation: `${overlay.toString()} 300ms`,
|
|
26
|
-
animationFillMode: "forwards",
|
|
27
|
-
backdropFilter: "blur(5px)",
|
|
16
|
+
"position": "fixed",
|
|
17
|
+
"zIndex": 4,
|
|
18
|
+
"top": 0,
|
|
19
|
+
"bottom": 0,
|
|
20
|
+
"left": 0,
|
|
21
|
+
"right": 0,
|
|
22
|
+
"display": "flex",
|
|
23
|
+
"alignItems": "center",
|
|
24
|
+
"justifyContent": "center",
|
|
25
|
+
"animation": `${overlay.toString()} 300ms`,
|
|
26
|
+
"animationFillMode": "forwards",
|
|
27
|
+
"backdropFilter": "blur(5px)",
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
"@media (prefers-reduced-motion: reduce)": {
|
|
30
|
+
animation: "none",
|
|
31
|
+
background: "rgba($background, 0.3)",
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
"variants": {
|
|
30
35
|
position: {
|
|
31
36
|
bottom: {
|
|
32
37
|
alignItems: "flex-end",
|
|
@@ -57,18 +62,33 @@ const RemovePadding = styled("div", {
|
|
|
57
62
|
});
|
|
58
63
|
|
|
59
64
|
const ContainerStyled = styled("div", {
|
|
60
|
-
background: "$modalBg",
|
|
61
|
-
borderRadius: dimensionsPxToRem(12),
|
|
62
|
-
maxWidth: pxToRem(333),
|
|
63
|
-
maxHeight: "100%",
|
|
64
|
-
width: "calc(100% - 30px)",
|
|
65
|
-
padding: PADDING,
|
|
66
|
-
position: "relative",
|
|
67
|
-
boxSizing: "border-box",
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
"background": "$modalBg",
|
|
66
|
+
"borderRadius": dimensionsPxToRem(12),
|
|
67
|
+
"maxWidth": pxToRem(333),
|
|
68
|
+
"maxHeight": "100%",
|
|
69
|
+
"width": "calc(100% - 30px)",
|
|
70
|
+
"padding": PADDING,
|
|
71
|
+
"position": "relative",
|
|
72
|
+
"boxSizing": "border-box",
|
|
73
|
+
"overflowY": "auto",
|
|
74
|
+
"overscrollBehavior": "contain",
|
|
75
|
+
"animation": `${container.toString()} 300ms`,
|
|
76
|
+
"animationFillMode": "forwards",
|
|
77
|
+
|
|
78
|
+
// The dialog uses tabindex=-1 to be programmatically focusable for initial focus
|
|
79
|
+
// fallback (when there is no focusable child). Hide the outline since it's only
|
|
80
|
+
// there as a focus target, not a user-visible affordance.
|
|
81
|
+
"&:focus": {
|
|
82
|
+
outline: "none",
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
"@media (prefers-reduced-motion: reduce)": {
|
|
86
|
+
animation: "none",
|
|
87
|
+
opacity: 1,
|
|
88
|
+
transform: "none",
|
|
89
|
+
},
|
|
70
90
|
|
|
71
|
-
variants: {
|
|
91
|
+
"variants": {
|
|
72
92
|
// TODO this is very not rwd, it should be a media query
|
|
73
93
|
full: {
|
|
74
94
|
true: {
|
|
@@ -84,11 +104,12 @@ const ContainerStyled = styled("div", {
|
|
|
84
104
|
},
|
|
85
105
|
});
|
|
86
106
|
|
|
87
|
-
const TitleStyled = styled("
|
|
107
|
+
const TitleStyled = styled("h2", {
|
|
88
108
|
fontSize: fontPxToRem(40),
|
|
89
109
|
textAlign: "center",
|
|
90
110
|
color: "$text3",
|
|
91
111
|
margin: `${dimensionsPxToRem(90)} 0`,
|
|
112
|
+
fontWeight: "inherit",
|
|
92
113
|
});
|
|
93
114
|
|
|
94
115
|
export {
|
|
@@ -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>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import React, { useCallback } from "react";
|
|
1
|
+
import React, { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
2
3
|
|
|
3
4
|
import { toast, Toaster as SonnerToaster } from "sonner";
|
|
4
5
|
|
|
@@ -9,10 +10,19 @@ interface ToasterProviderProps extends SonnerToasterProps {
|
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
const ToasterProvider: React.FC<ToasterProviderProps> = ({ children, position = "bottom-center", ...rest }) => {
|
|
13
|
+
const [body, setBody] = useState<HTMLElement | null>(null);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
17
|
+
setBody(document.body);
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
const toaster = <SonnerToaster position={position} {...rest} />;
|
|
21
|
+
|
|
12
22
|
return (
|
|
13
23
|
<>
|
|
14
24
|
{children}
|
|
15
|
-
|
|
25
|
+
{body ? createPortal(toaster, body) : null}
|
|
16
26
|
</>
|
|
17
27
|
);
|
|
18
28
|
};
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
2
|
+
import React, { useCallback, useState } from "react";
|
|
3
|
+
|
|
4
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
5
|
+
|
|
6
|
+
import { Gap } from "../../utils/Gap";
|
|
7
|
+
import { Button } from "../button/Button";
|
|
8
|
+
import { Drawer } from "../drawer/Drawer";
|
|
9
|
+
import { Modal } from "../modal/Modal";
|
|
10
|
+
import { ModalButtons } from "../modal/ModalButtons";
|
|
11
|
+
import { Tooltip, TooltipProvider } from "./Tooltip";
|
|
12
|
+
|
|
13
|
+
const meta: Meta<typeof Tooltip> = {
|
|
14
|
+
title: "Components/UI/Tooltip",
|
|
15
|
+
component: Tooltip,
|
|
16
|
+
tags: ["autodocs", "ui"],
|
|
17
|
+
argTypes: {
|
|
18
|
+
side: {
|
|
19
|
+
control: "inline-radio",
|
|
20
|
+
options: ["top", "right", "bottom", "left"],
|
|
21
|
+
},
|
|
22
|
+
align: {
|
|
23
|
+
control: "inline-radio",
|
|
24
|
+
options: ["start", "center", "end"],
|
|
25
|
+
},
|
|
26
|
+
sideOffset: { control: { type: "number", min: 0, max: 40 } },
|
|
27
|
+
alignOffset: { control: { type: "number", min: -40, max: 40 } },
|
|
28
|
+
delayDuration: { control: { type: "number", min: 0, max: 2000, step: 100 } },
|
|
29
|
+
avoidCollisions: { type: "boolean" },
|
|
30
|
+
arrow: { type: "boolean" },
|
|
31
|
+
content: { control: "text" },
|
|
32
|
+
children: { table: { disable: true } },
|
|
33
|
+
open: { table: { disable: true } },
|
|
34
|
+
defaultOpen: { table: { disable: true } },
|
|
35
|
+
onOpenChange: { table: { disable: true } },
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type Story = StoryObj<typeof Tooltip>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Playground. Tweak `side`, `align`, `sideOffset`, `delayDuration`, `arrow`,
|
|
43
|
+
* and `avoidCollisions` from the controls panel.
|
|
44
|
+
*/
|
|
45
|
+
const Primary: Story = {
|
|
46
|
+
args: {
|
|
47
|
+
content: "Save your changes",
|
|
48
|
+
side: "top",
|
|
49
|
+
align: "center",
|
|
50
|
+
sideOffset: 6,
|
|
51
|
+
alignOffset: 0,
|
|
52
|
+
delayDuration: 500,
|
|
53
|
+
avoidCollisions: true,
|
|
54
|
+
arrow: true,
|
|
55
|
+
},
|
|
56
|
+
render: (args) => (
|
|
57
|
+
<div style={{ padding: 80, display: "flex", justifyContent: "center" }}>
|
|
58
|
+
<Tooltip {...args}>
|
|
59
|
+
<Button>Hover me</Button>
|
|
60
|
+
</Tooltip>
|
|
61
|
+
</div>
|
|
62
|
+
),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* All four sides shown at once.
|
|
67
|
+
*/
|
|
68
|
+
const AllSides: Story = {
|
|
69
|
+
render: () => (
|
|
70
|
+
<div
|
|
71
|
+
style={{
|
|
72
|
+
padding: 120,
|
|
73
|
+
display: "grid",
|
|
74
|
+
gridTemplateColumns: "repeat(2, max-content)",
|
|
75
|
+
gap: 40,
|
|
76
|
+
justifyContent: "center",
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
<Tooltip content={"Top"} side={"top"} arrow={true}>
|
|
80
|
+
<Button>Top</Button>
|
|
81
|
+
</Tooltip>
|
|
82
|
+
<Tooltip content={"Right"} side={"right"} arrow={true}>
|
|
83
|
+
<Button>Right</Button>
|
|
84
|
+
</Tooltip>
|
|
85
|
+
<Tooltip content={"Bottom"} side={"bottom"} arrow={true}>
|
|
86
|
+
<Button>Bottom</Button>
|
|
87
|
+
</Tooltip>
|
|
88
|
+
<Tooltip content={"Left"} side={"left"} arrow={true}>
|
|
89
|
+
<Button>Left</Button>
|
|
90
|
+
</Tooltip>
|
|
91
|
+
</div>
|
|
92
|
+
),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* `TooltipProvider` enables group-delay behavior: once any tooltip is open, the next ones
|
|
97
|
+
* appear without waiting for `delayDuration` (until `skipDelayDuration` elapses with all
|
|
98
|
+
* tooltips closed). Hover one button slowly, then sweep across the rest — they pop instantly.
|
|
99
|
+
*/
|
|
100
|
+
const GroupDelay: Story = {
|
|
101
|
+
render: () => (
|
|
102
|
+
<TooltipProvider delayDuration={500} skipDelayDuration={400}>
|
|
103
|
+
<div style={{ padding: 80, display: "flex", gap: 12, justifyContent: "center" }}>
|
|
104
|
+
<Tooltip content={"First"}>
|
|
105
|
+
<Button>One</Button>
|
|
106
|
+
</Tooltip>
|
|
107
|
+
<Tooltip content={"Second"}>
|
|
108
|
+
<Button>Two</Button>
|
|
109
|
+
</Tooltip>
|
|
110
|
+
<Tooltip content={"Third"}>
|
|
111
|
+
<Button>Three</Button>
|
|
112
|
+
</Tooltip>
|
|
113
|
+
<Tooltip content={"Fourth"}>
|
|
114
|
+
<Button>Four</Button>
|
|
115
|
+
</Tooltip>
|
|
116
|
+
</div>
|
|
117
|
+
</TooltipProvider>
|
|
118
|
+
),
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Triggers placed near viewport edges. With `avoidCollisions` (default) the tooltip
|
|
123
|
+
* flips to fit; toggle the arg in the playground story to see the difference.
|
|
124
|
+
*/
|
|
125
|
+
const NearViewportEdge: Story = {
|
|
126
|
+
render: () => (
|
|
127
|
+
<div style={{ height: "90vh", position: "relative" }}>
|
|
128
|
+
<div style={{ position: "absolute", top: 4, left: 4 }}>
|
|
129
|
+
<Tooltip content={"I would overflow up-left, so I flip"} side={"top"} arrow={true}>
|
|
130
|
+
<Button>Top-left corner</Button>
|
|
131
|
+
</Tooltip>
|
|
132
|
+
</div>
|
|
133
|
+
<div style={{ position: "absolute", top: 4, right: 4 }}>
|
|
134
|
+
<Tooltip content={"I would overflow up-right, so I flip"} side={"top"} arrow={true}>
|
|
135
|
+
<Button>Top-right corner</Button>
|
|
136
|
+
</Tooltip>
|
|
137
|
+
</div>
|
|
138
|
+
<div style={{ position: "absolute", bottom: 4, left: 4 }}>
|
|
139
|
+
<Tooltip content={"I would overflow down-left"} side={"bottom"} arrow={true}>
|
|
140
|
+
<Button>Bottom-left corner</Button>
|
|
141
|
+
</Tooltip>
|
|
142
|
+
</div>
|
|
143
|
+
<div style={{ position: "absolute", bottom: 4, right: 4 }}>
|
|
144
|
+
<Tooltip content={"I would overflow down-right"} side={"bottom"} arrow={true}>
|
|
145
|
+
<Button>Bottom-right corner</Button>
|
|
146
|
+
</Tooltip>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
),
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Long content wraps to the configured `maxWidth`.
|
|
154
|
+
*/
|
|
155
|
+
const LongContent: Story = {
|
|
156
|
+
render: () => (
|
|
157
|
+
<div style={{ padding: 80, display: "flex", justifyContent: "center" }}>
|
|
158
|
+
<Tooltip
|
|
159
|
+
content={"This tooltip has a longer description that wraps onto multiple lines once it "
|
|
160
|
+
+ "exceeds the maximum content width set in the styles."}
|
|
161
|
+
side={"bottom"}
|
|
162
|
+
>
|
|
163
|
+
<Button>Long description</Button>
|
|
164
|
+
</Tooltip>
|
|
165
|
+
</div>
|
|
166
|
+
),
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Edge case: tooltip rendered from inside a `Modal`. Because the content is portalled to
|
|
171
|
+
* `document.body`, it escapes the modal's stacking context and renders above it.
|
|
172
|
+
*/
|
|
173
|
+
const InModal: Story = {
|
|
174
|
+
render: () => {
|
|
175
|
+
const [open, setOpen] = useState(false);
|
|
176
|
+
|
|
177
|
+
const handleOpen = useCallback(() => { setOpen(true); }, []);
|
|
178
|
+
const handleClose = useCallback(() => { setOpen(false); }, []);
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<div>
|
|
182
|
+
<Button onClick={handleOpen}>Open modal</Button>
|
|
183
|
+
<Modal isOpen={open} onClose={handleClose} title={"Tooltip in a modal"}>
|
|
184
|
+
<Gap>
|
|
185
|
+
<div>Hover the buttons below — the tooltip should appear above the modal.</div>
|
|
186
|
+
<div style={{ display: "flex", gap: 12 }}>
|
|
187
|
+
<Tooltip content={"Above modal, side=top"} side={"top"} arrow={true}>
|
|
188
|
+
<Button>Top</Button>
|
|
189
|
+
</Tooltip>
|
|
190
|
+
<Tooltip content={"Above modal, side=right"} side={"right"} arrow={true}>
|
|
191
|
+
<Button>Right</Button>
|
|
192
|
+
</Tooltip>
|
|
193
|
+
<Tooltip content={"Above modal, side=bottom"} side={"bottom"} arrow={true}>
|
|
194
|
+
<Button>Bottom</Button>
|
|
195
|
+
</Tooltip>
|
|
196
|
+
</div>
|
|
197
|
+
</Gap>
|
|
198
|
+
<ModalButtons>
|
|
199
|
+
<ModalButtons.Button variant={"main"} onClick={handleClose}>Close</ModalButtons.Button>
|
|
200
|
+
</ModalButtons>
|
|
201
|
+
</Modal>
|
|
202
|
+
</div>
|
|
203
|
+
);
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Edge case: tooltip rendered from inside a `Drawer`. Same portal behavior — content
|
|
209
|
+
* floats above the drawer regardless of the drawer's stacking context.
|
|
210
|
+
*/
|
|
211
|
+
const InDrawer: Story = {
|
|
212
|
+
render: () => {
|
|
213
|
+
const [open, setOpen] = useState(false);
|
|
214
|
+
|
|
215
|
+
const handleOpen = useCallback(() => { setOpen(true); }, []);
|
|
216
|
+
const handleClose = useCallback(() => { setOpen(false); }, []);
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<div>
|
|
220
|
+
<Button onClick={handleOpen}>Open drawer</Button>
|
|
221
|
+
<Drawer isOpen={open} onClose={handleClose}>
|
|
222
|
+
<div style={{ padding: 24, display: "flex", flexDirection: "column", gap: 16 }}>
|
|
223
|
+
<div>Tooltips work from inside a drawer.</div>
|
|
224
|
+
<Tooltip content={"Hello from inside the drawer"} side={"bottom"} arrow={true}>
|
|
225
|
+
<Button>Hover me</Button>
|
|
226
|
+
</Tooltip>
|
|
227
|
+
<Button onClick={handleClose}>Close drawer</Button>
|
|
228
|
+
</div>
|
|
229
|
+
</Drawer>
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Controlled tooltip — open state is driven by parent. Useful for tutorials,
|
|
237
|
+
* onboarding tours, or pinning a tooltip open while a user interacts elsewhere.
|
|
238
|
+
*/
|
|
239
|
+
const Controlled: Story = {
|
|
240
|
+
render: () => {
|
|
241
|
+
const [open, setOpen] = useState(false);
|
|
242
|
+
|
|
243
|
+
const handleToggle = useCallback(() => { setOpen(p => !p); }, []);
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<div style={{ padding: 80, display: "flex", gap: 16, justifyContent: "center" }}>
|
|
247
|
+
<Button onClick={handleToggle}>{open ? "Hide" : "Show"} tooltip</Button>
|
|
248
|
+
<Tooltip content={"I'm controlled"} open={open} side={"right"}>
|
|
249
|
+
<Button>Target</Button>
|
|
250
|
+
</Tooltip>
|
|
251
|
+
</div>
|
|
252
|
+
);
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Tooltip wrapping a non-button trigger. Any element that can receive a ref works
|
|
258
|
+
* — here, an inline word with a dotted underline.
|
|
259
|
+
*/
|
|
260
|
+
const InlineTrigger: Story = {
|
|
261
|
+
render: () => (
|
|
262
|
+
<div style={{ padding: 80, fontSize: 18, lineHeight: 1.6, maxWidth: 500, margin: "0 auto" }}>
|
|
263
|
+
This sentence contains a{" "}
|
|
264
|
+
<Tooltip content={"A short, contextual explanation."}>
|
|
265
|
+
<span style={{ borderBottom: "1px dotted currentColor", cursor: "help" }}>
|
|
266
|
+
glossed term
|
|
267
|
+
</span>
|
|
268
|
+
</Tooltip>
|
|
269
|
+
{" "}that reveals more on hover or focus.
|
|
270
|
+
</div>
|
|
271
|
+
),
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
export default meta;
|
|
275
|
+
export {
|
|
276
|
+
Primary,
|
|
277
|
+
AllSides,
|
|
278
|
+
GroupDelay,
|
|
279
|
+
NearViewportEdge,
|
|
280
|
+
LongContent,
|
|
281
|
+
InModal,
|
|
282
|
+
InDrawer,
|
|
283
|
+
Controlled,
|
|
284
|
+
InlineTrigger,
|
|
285
|
+
};
|