react-miui 0.33.0 → 0.34.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 +4 -1
- package/.storybook/preview.tsx +10 -4
- package/CHANGELOG.md +9 -0
- 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/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/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 +2 -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.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.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/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/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/package.json +2 -1
- package/pnpm-workspace.yaml +3 -0
- package/src/bugfixes/ToastsFromModal.stories.tsx +59 -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/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
|
@@ -1,12 +1,49 @@
|
|
|
1
1
|
import React, { useCallback, useState } from "react";
|
|
2
2
|
|
|
3
3
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
4
|
+
import type { DrawerProps } from "./Drawer";
|
|
4
5
|
|
|
5
6
|
import { styled } from "../../../theme";
|
|
6
7
|
import { Section } from "../../layout/section/Section";
|
|
7
8
|
import { Button } from "../button/Button";
|
|
8
9
|
import { Drawer } from "./Drawer";
|
|
9
10
|
|
|
11
|
+
type PlaygroundArgs = Omit<DrawerProps, "isOpen" | "onClose">;
|
|
12
|
+
|
|
13
|
+
const Playground = (args: PlaygroundArgs) => {
|
|
14
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
15
|
+
|
|
16
|
+
const handleToggleOpen = useCallback(() => {
|
|
17
|
+
setIsOpen((prev) => !prev);
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
const handleClose = useCallback(() => {
|
|
21
|
+
setIsOpen(false);
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div style={{ padding: "20px" }}>
|
|
26
|
+
<h2>Page content</h2>
|
|
27
|
+
<p>Open the drawer and tweak its props in the Controls panel.</p>
|
|
28
|
+
{Array(8).fill(0).map((_: number, i) => (
|
|
29
|
+
<Section key={i} vertical={true} horizontal={true}> {/* eslint-disable-line react/no-array-index-key */}
|
|
30
|
+
<p>Filler row {i + 1}</p>
|
|
31
|
+
</Section>
|
|
32
|
+
))}
|
|
33
|
+
<Button onClick={handleToggleOpen}>
|
|
34
|
+
{isOpen ? "Close Drawer" : "Open Drawer"}
|
|
35
|
+
</Button>
|
|
36
|
+
<Drawer {...args} isOpen={isOpen} onClose={handleClose}>
|
|
37
|
+
<div style={{ padding: "20px" }}>
|
|
38
|
+
<h2>Drawer</h2>
|
|
39
|
+
<p>Adjust controls to customize behavior.</p>
|
|
40
|
+
<Button onClick={handleClose}>Close</Button>
|
|
41
|
+
</div>
|
|
42
|
+
</Drawer>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
10
47
|
const meta: Meta<typeof Drawer> = {
|
|
11
48
|
title: "UI/Drawer",
|
|
12
49
|
component: Drawer,
|
|
@@ -14,72 +51,111 @@ const meta: Meta<typeof Drawer> = {
|
|
|
14
51
|
layout: "fullscreen",
|
|
15
52
|
},
|
|
16
53
|
tags: ["autodocs"],
|
|
54
|
+
argTypes: {
|
|
55
|
+
from: {
|
|
56
|
+
control: "inline-radio",
|
|
57
|
+
options: ["top", "right", "bottom", "left"],
|
|
58
|
+
},
|
|
59
|
+
size: {
|
|
60
|
+
control: "text",
|
|
61
|
+
description: "CSS length, e.g. \"200px\", \"50%\", \"calc(100% - 80px)\". Leave empty for full viewport.",
|
|
62
|
+
},
|
|
63
|
+
scaleContent: {
|
|
64
|
+
control: "boolean",
|
|
65
|
+
},
|
|
66
|
+
scaleSelectors: {
|
|
67
|
+
control: "object",
|
|
68
|
+
},
|
|
69
|
+
scaleSelectorsMode: {
|
|
70
|
+
control: "inline-radio",
|
|
71
|
+
options: ["first", "all"],
|
|
72
|
+
},
|
|
73
|
+
onOverlayClick: {
|
|
74
|
+
control: "select",
|
|
75
|
+
options: ["close", null],
|
|
76
|
+
},
|
|
77
|
+
closeOnEsc: {
|
|
78
|
+
control: "boolean",
|
|
79
|
+
},
|
|
80
|
+
isOpen: { table: { disable: true } },
|
|
81
|
+
onClose: { table: { disable: true } },
|
|
82
|
+
children: { table: { disable: true } },
|
|
83
|
+
portal: { table: { disable: true } },
|
|
84
|
+
className: { table: { disable: true } },
|
|
85
|
+
},
|
|
86
|
+
args: {
|
|
87
|
+
from: "bottom",
|
|
88
|
+
closeOnEsc: true,
|
|
89
|
+
onOverlayClick: "close",
|
|
90
|
+
scaleContent: false,
|
|
91
|
+
scaleSelectorsMode: "first",
|
|
92
|
+
},
|
|
93
|
+
render: (args) => <Playground {...args as PlaygroundArgs} />,
|
|
17
94
|
};
|
|
18
95
|
|
|
19
96
|
type Story = StoryObj<typeof Drawer>;
|
|
20
97
|
|
|
21
98
|
/**
|
|
22
|
-
*
|
|
99
|
+
* Default full-viewport drawer sliding up from the bottom.
|
|
23
100
|
*/
|
|
24
|
-
const Default: Story = {
|
|
25
|
-
render: () => {
|
|
26
|
-
const [isOpen, setisOpen] = useState(false);
|
|
101
|
+
const Default: Story = {};
|
|
27
102
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
103
|
+
/**
|
|
104
|
+
* ESC key is disabled, drawer can only be closed via its own UI.
|
|
105
|
+
*/
|
|
106
|
+
const NoEscClose: Story = {
|
|
107
|
+
args: { closeOnEsc: false },
|
|
108
|
+
};
|
|
31
109
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Slides in from the top, partial height.
|
|
112
|
+
*/
|
|
113
|
+
const FromTop: Story = {
|
|
114
|
+
args: { from: "top", size: "240px" },
|
|
115
|
+
};
|
|
35
116
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
<p>This is the content inside the drawer.</p>
|
|
42
|
-
<p>Press ESC to close.</p>
|
|
43
|
-
</div>
|
|
44
|
-
</Drawer>
|
|
45
|
-
<Button onClick={handleToggleOpen}>
|
|
46
|
-
{isOpen ? "Close Drawer" : "Open Drawer"}
|
|
47
|
-
</Button>
|
|
48
|
-
</div>
|
|
49
|
-
);
|
|
50
|
-
},
|
|
117
|
+
/**
|
|
118
|
+
* Slides in from the right side.
|
|
119
|
+
*/
|
|
120
|
+
const FromRight: Story = {
|
|
121
|
+
args: { from: "right", size: "320px" },
|
|
51
122
|
};
|
|
52
123
|
|
|
53
124
|
/**
|
|
54
|
-
*
|
|
125
|
+
* Slides in from the left side.
|
|
55
126
|
*/
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
127
|
+
const FromLeft: Story = {
|
|
128
|
+
args: { from: "left", size: "260px" },
|
|
129
|
+
};
|
|
59
130
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Partial-size drawer with the new rounded corner treatment on the visible edge.
|
|
133
|
+
*/
|
|
134
|
+
const PartialSize: Story = {
|
|
135
|
+
args: { from: "bottom", size: "200px" },
|
|
136
|
+
};
|
|
63
137
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Drawer that leaves 80px of the page visible at the top while scaling the page
|
|
140
|
+
* content down — the classic stacked "3D" sheet look.
|
|
141
|
+
*/
|
|
142
|
+
const ScaleContent: Story = {
|
|
143
|
+
args: {
|
|
144
|
+
from: "bottom",
|
|
145
|
+
size: "calc(100% - 80px)",
|
|
146
|
+
scaleContent: true,
|
|
147
|
+
},
|
|
148
|
+
};
|
|
67
149
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
</Drawer>
|
|
78
|
-
<Button onClick={handleToggleOpen}>
|
|
79
|
-
{open ? "Close Drawer" : "Open Drawer (No ESC Close)"}
|
|
80
|
-
</Button>
|
|
81
|
-
</div>
|
|
82
|
-
);
|
|
150
|
+
/**
|
|
151
|
+
* Overlay clicks are passed through to the underlying page (`onOverlayClick={null}`),
|
|
152
|
+
* so users can keep interacting with content next to the drawer.
|
|
153
|
+
*/
|
|
154
|
+
const PassThroughOverlay: Story = {
|
|
155
|
+
args: {
|
|
156
|
+
from: "bottom",
|
|
157
|
+
size: "200px",
|
|
158
|
+
onOverlayClick: null,
|
|
83
159
|
},
|
|
84
160
|
};
|
|
85
161
|
|
|
@@ -87,7 +163,7 @@ const NoEscClose: Story = {
|
|
|
87
163
|
* Drawer with content that demonstrates scrolling behavior.
|
|
88
164
|
*/
|
|
89
165
|
const WithLongContent: Story = {
|
|
90
|
-
render: () => {
|
|
166
|
+
render: (args) => {
|
|
91
167
|
const [isOpen, setIsOpen] = useState(false);
|
|
92
168
|
|
|
93
169
|
const handleToggleOpen = useCallback(() => {
|
|
@@ -100,7 +176,7 @@ const WithLongContent: Story = {
|
|
|
100
176
|
|
|
101
177
|
return (
|
|
102
178
|
<div style={{ padding: "20px" }}>
|
|
103
|
-
<Drawer isOpen={isOpen} onClose={handleClose}>
|
|
179
|
+
<Drawer {...args as PlaygroundArgs} isOpen={isOpen} onClose={handleClose}>
|
|
104
180
|
<div style={{ padding: "20px" }}>
|
|
105
181
|
<h2>Drawer with Long Content</h2>
|
|
106
182
|
<p>This drawer has enough content to demonstrate scrolling behavior.</p>
|
|
@@ -120,9 +196,6 @@ const WithLongContent: Story = {
|
|
|
120
196
|
},
|
|
121
197
|
};
|
|
122
198
|
|
|
123
|
-
/**
|
|
124
|
-
* Custom styled Drawer with a different background color and border radius.
|
|
125
|
-
*/
|
|
126
199
|
const CustomStyledDrawer = styled(Drawer, {
|
|
127
200
|
"&&": {
|
|
128
201
|
background: "linear-gradient(to bottom, #c5f7fa, #63cfe2)",
|
|
@@ -130,10 +203,10 @@ const CustomStyledDrawer = styled(Drawer, {
|
|
|
130
203
|
});
|
|
131
204
|
|
|
132
205
|
/**
|
|
133
|
-
* A custom styled version of the Drawer component with a gradient background
|
|
206
|
+
* A custom styled version of the Drawer component with a gradient background.
|
|
134
207
|
*/
|
|
135
208
|
const CustomStyled: Story = {
|
|
136
|
-
render: () => {
|
|
209
|
+
render: (args) => {
|
|
137
210
|
const [isOpen, setIsOpen] = useState(false);
|
|
138
211
|
|
|
139
212
|
const handleToggleOpen = useCallback(() => {
|
|
@@ -146,7 +219,7 @@ const CustomStyled: Story = {
|
|
|
146
219
|
|
|
147
220
|
return (
|
|
148
221
|
<div style={{ padding: "20px" }}>
|
|
149
|
-
<CustomStyledDrawer isOpen={isOpen} onClose={handleClose}>
|
|
222
|
+
<CustomStyledDrawer {...args as PlaygroundArgs} isOpen={isOpen} onClose={handleClose}>
|
|
150
223
|
<div style={{ padding: "20px" }}>
|
|
151
224
|
<h2>Custom Styled Drawer</h2>
|
|
152
225
|
<p>This drawer has custom styling with a gradient background</p>
|
|
@@ -161,5 +234,16 @@ const CustomStyled: Story = {
|
|
|
161
234
|
},
|
|
162
235
|
};
|
|
163
236
|
|
|
164
|
-
export {
|
|
237
|
+
export {
|
|
238
|
+
Default,
|
|
239
|
+
NoEscClose,
|
|
240
|
+
FromTop,
|
|
241
|
+
FromRight,
|
|
242
|
+
FromLeft,
|
|
243
|
+
PartialSize,
|
|
244
|
+
ScaleContent,
|
|
245
|
+
PassThroughOverlay,
|
|
246
|
+
WithLongContent,
|
|
247
|
+
CustomStyled,
|
|
248
|
+
};
|
|
165
249
|
export default meta;
|
|
@@ -12,11 +12,24 @@ const StyledDrawer = styled("div", {
|
|
|
12
12
|
overflow: "auto",
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
+
const Tint = styled("div", {
|
|
16
|
+
position: "fixed",
|
|
17
|
+
top: 0,
|
|
18
|
+
left: 0,
|
|
19
|
+
right: 0,
|
|
20
|
+
bottom: 0,
|
|
21
|
+
background: "$text",
|
|
22
|
+
opacity: 0,
|
|
23
|
+
transition: "opacity 300ms",
|
|
24
|
+
zIndex: 0,
|
|
25
|
+
});
|
|
26
|
+
|
|
15
27
|
const Content = styled("div", {
|
|
16
28
|
height: "100%",
|
|
17
29
|
});
|
|
18
30
|
|
|
19
31
|
export {
|
|
20
32
|
StyledDrawer,
|
|
33
|
+
Tint,
|
|
21
34
|
Content,
|
|
22
35
|
};
|
|
@@ -1,22 +1,147 @@
|
|
|
1
|
-
import React, { forwardRef, useEffect, useRef, useState } from "react";
|
|
1
|
+
import React, { forwardRef, useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
2
3
|
|
|
4
|
+
import { useForwardedRef } from "@bedrock-layout/use-forwarded-ref";
|
|
3
5
|
import { Timeout } from "oop-timers";
|
|
4
6
|
|
|
5
7
|
import { HandleEsc } from "../../utils/HandleEsc";
|
|
6
|
-
import { Content, StyledDrawer } from "./Drawer.styled";
|
|
8
|
+
import { Content, StyledDrawer, Tint } from "./Drawer.styled";
|
|
7
9
|
|
|
8
10
|
const RENDER_TIMEOUT = 500;
|
|
11
|
+
const TRANSITION_MS = 300;
|
|
12
|
+
const DEFAULT_CONTENT_SCALE = 0.92;
|
|
13
|
+
const DEFAULT_TINT_OPACITY = 0.1;
|
|
14
|
+
const PARTIAL_RADIUS = "16px";
|
|
15
|
+
const DEFAULT_SCALE_SELECTORS = ["#__next", "#root", "#storybook-root", "body>*:first-child"];
|
|
16
|
+
|
|
17
|
+
type DrawerFrom = "top" | "right" | "bottom" | "left";
|
|
18
|
+
type ScaleMode = "first" | "all";
|
|
9
19
|
|
|
10
20
|
type DrawerProps = {
|
|
11
21
|
isOpen: boolean;
|
|
12
22
|
closeOnEsc?: boolean;
|
|
13
23
|
onClose: () => void;
|
|
14
24
|
className?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Which edge the drawer slides in from. Defaults to `"bottom"`.
|
|
27
|
+
*/
|
|
28
|
+
from?: DrawerFrom;
|
|
29
|
+
/**
|
|
30
|
+
* How far the drawer extends along its slide axis. Defaults to filling the whole viewport.
|
|
31
|
+
* - positive number — size in pixels (e.g. `300` ⇒ 300px tall/wide).
|
|
32
|
+
* - negative number — viewport minus pixels (e.g. `-100` ⇒ `calc(100% - 100px)`).
|
|
33
|
+
* - string — any CSS length (e.g. `"50%"`, `"20rem"`).
|
|
34
|
+
*
|
|
35
|
+
* When set, the drawer's visible edge gets rounded corners.
|
|
36
|
+
*/
|
|
37
|
+
size?: number | string;
|
|
38
|
+
/**
|
|
39
|
+
* When set, scales the targeted page content down while the drawer is open
|
|
40
|
+
* (use with `scaleSelectors`) and fades in a subtle tint over it for a
|
|
41
|
+
* stacked "3D" look. `true` uses the default scale (~0.92), or pass a number
|
|
42
|
+
* to customize (e.g. `0.95`).
|
|
43
|
+
*/
|
|
44
|
+
scaleContent?: boolean | number;
|
|
45
|
+
/**
|
|
46
|
+
* CSS selector (or array tried in order) for what to scale when `scaleContent`
|
|
47
|
+
* is set. The first selector that matches anything wins. Defaults to common
|
|
48
|
+
* app-root selectors `["#__next", "#root", "body>*:first-child"]`.
|
|
49
|
+
*/
|
|
50
|
+
scaleSelectors?: string | string[];
|
|
51
|
+
/**
|
|
52
|
+
* Whether to scale only the first match of the chosen selector (`"first"`,
|
|
53
|
+
* default) or every match (`"all"`).
|
|
54
|
+
*/
|
|
55
|
+
scaleSelectorsMode?: ScaleMode;
|
|
56
|
+
/**
|
|
57
|
+
* What happens when the user clicks (or taps) on the area outside the drawer.
|
|
58
|
+
* Mirrors `Modal`'s prop: `"close"` (default) calls `onClose`, a function is
|
|
59
|
+
* invoked directly, and `null` makes the overlay transparent to pointer/touch
|
|
60
|
+
* events so the underlying page stays interactive. The overlay only renders
|
|
61
|
+
* when there is an "outside" — i.e. `size` is set or `scaleContent` is enabled.
|
|
62
|
+
*/
|
|
63
|
+
onOverlayClick?: (() => void) | "close" | null;
|
|
64
|
+
/**
|
|
65
|
+
* Where to portal the drawer. `true` (default) portals to `document.body` so the
|
|
66
|
+
* drawer's `position: fixed` always anchors to the viewport — needed when any
|
|
67
|
+
* ancestor establishes a containing block (e.g. via `transform`, `filter`, or
|
|
68
|
+
* `will-change`). Pass an `HTMLElement` to portal to a specific node, or `false`
|
|
69
|
+
* to render in place.
|
|
70
|
+
*/
|
|
71
|
+
portal?: boolean | HTMLElement;
|
|
15
72
|
children: React.ReactNode;
|
|
16
73
|
};
|
|
17
74
|
|
|
75
|
+
const closedTransform: Record<DrawerFrom, string> = {
|
|
76
|
+
bottom: "translate(0, 100%)",
|
|
77
|
+
top: "translate(0, -100%)",
|
|
78
|
+
left: "translate(-100%, 0)",
|
|
79
|
+
right: "translate(100%, 0)",
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const getSizeCss = (size: number | string | undefined): string | undefined => {
|
|
83
|
+
if (size === undefined) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
if (typeof size === "string") {
|
|
87
|
+
return size;
|
|
88
|
+
}
|
|
89
|
+
if (size >= 0) {
|
|
90
|
+
return `${size}px`;
|
|
91
|
+
}
|
|
92
|
+
return `calc(100% + ${size}px)`;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const positioningPresets: Record<DrawerFrom, (size: string) => React.CSSProperties> = {
|
|
96
|
+
bottom: (size) => ({ top: "auto", height: size }),
|
|
97
|
+
top: (size) => ({ bottom: "auto", height: size }),
|
|
98
|
+
left: (size) => ({ right: "auto", width: size }),
|
|
99
|
+
right: (size) => ({ left: "auto", width: size }),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const radiusPresets: Record<DrawerFrom, React.CSSProperties> = {
|
|
103
|
+
bottom: { borderTopLeftRadius: PARTIAL_RADIUS, borderTopRightRadius: PARTIAL_RADIUS },
|
|
104
|
+
top: { borderBottomLeftRadius: PARTIAL_RADIUS, borderBottomRightRadius: PARTIAL_RADIUS },
|
|
105
|
+
left: { borderTopRightRadius: PARTIAL_RADIUS, borderBottomRightRadius: PARTIAL_RADIUS },
|
|
106
|
+
right: { borderTopLeftRadius: PARTIAL_RADIUS, borderBottomLeftRadius: PARTIAL_RADIUS },
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const getStaticStyle = (from: DrawerFrom, size: number | string | undefined): React.CSSProperties => {
|
|
110
|
+
const sizeCss = getSizeCss(size);
|
|
111
|
+
if (sizeCss === undefined) {
|
|
112
|
+
return {};
|
|
113
|
+
}
|
|
114
|
+
return { ...positioningPresets[from](sizeCss), ...radiusPresets[from] };
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const findScaleTargets = (selectors: string[], mode: ScaleMode): HTMLElement[] => {
|
|
118
|
+
for (const sel of selectors) {
|
|
119
|
+
if (mode === "first") {
|
|
120
|
+
const el = document.querySelector<HTMLElement>(sel);
|
|
121
|
+
if (el) {
|
|
122
|
+
return [el];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const els = Array.from(document.querySelectorAll<HTMLElement>(sel));
|
|
127
|
+
if (els.length > 0) {
|
|
128
|
+
return els;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return [];
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const applyScale = (target: HTMLElement, transform: string) => {
|
|
136
|
+
target.style.setProperty("transition", `transform ${TRANSITION_MS}ms`);
|
|
137
|
+
target.style.setProperty("transform-origin", "50% 0");
|
|
138
|
+
target.style.setProperty("transform", transform);
|
|
139
|
+
};
|
|
140
|
+
|
|
18
141
|
const Drawer = forwardRef<HTMLDivElement, DrawerProps>((props, ref) => {
|
|
142
|
+
const drawerRef = useForwardedRef(ref);
|
|
19
143
|
const [shouldRenderWhenClosed, setShouldRenderWhenClosed] = useState(false);
|
|
144
|
+
const [isVisible, setIsVisible] = useState(props.isOpen);
|
|
20
145
|
const timeoutRef = useRef<Timeout | null>(null);
|
|
21
146
|
|
|
22
147
|
useEffect(() => {
|
|
@@ -31,36 +156,105 @@ const Drawer = forwardRef<HTMLDivElement, DrawerProps>((props, ref) => {
|
|
|
31
156
|
}, []);
|
|
32
157
|
|
|
33
158
|
useEffect(() => {
|
|
34
|
-
if (props.isOpen) {
|
|
35
|
-
timeoutRef.current?.stop();
|
|
36
|
-
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
37
|
-
setShouldRenderWhenClosed(true);
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
159
|
+
if (!props.isOpen) {
|
|
40
160
|
timeoutRef.current?.start();
|
|
161
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
162
|
+
setIsVisible(false);
|
|
163
|
+
return undefined;
|
|
41
164
|
}
|
|
165
|
+
timeoutRef.current?.stop();
|
|
166
|
+
setShouldRenderWhenClosed(true);
|
|
167
|
+
const id = requestAnimationFrame(() => {
|
|
168
|
+
setIsVisible(true);
|
|
169
|
+
});
|
|
170
|
+
return () => {
|
|
171
|
+
cancelAnimationFrame(id);
|
|
172
|
+
};
|
|
42
173
|
}, [props.isOpen]);
|
|
43
174
|
|
|
44
|
-
|
|
45
|
-
|
|
175
|
+
const { scaleContent, scaleSelectors, scaleSelectorsMode } = props;
|
|
176
|
+
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
if (!scaleContent || !props.isOpen) {
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
const selectors = scaleSelectors === undefined
|
|
182
|
+
? DEFAULT_SCALE_SELECTORS
|
|
183
|
+
: (Array.isArray(scaleSelectors) ? scaleSelectors : [scaleSelectors]);
|
|
184
|
+
const mode = scaleSelectorsMode ?? "first";
|
|
185
|
+
const targets = findScaleTargets(selectors, mode);
|
|
186
|
+
if (targets.length === 0) {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
const scale = typeof scaleContent === "number" ? scaleContent : DEFAULT_CONTENT_SCALE;
|
|
190
|
+
targets.forEach((el) => {
|
|
191
|
+
applyScale(el, `scale(${scale})`);
|
|
192
|
+
});
|
|
193
|
+
return () => {
|
|
194
|
+
targets.forEach((el) => {
|
|
195
|
+
applyScale(el, "");
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
}, [props.isOpen, scaleContent, scaleSelectors, scaleSelectorsMode]);
|
|
199
|
+
|
|
200
|
+
const from = props.from ?? "bottom";
|
|
46
201
|
const style = {
|
|
47
|
-
|
|
202
|
+
...getStaticStyle(from, props.size),
|
|
203
|
+
transform: isVisible ? "translate(0, 0)" : closedTransform[from],
|
|
204
|
+
transition: props.isOpen && !isVisible ? "none" : undefined,
|
|
48
205
|
};
|
|
49
206
|
|
|
50
207
|
const shouldRender = props.isOpen || shouldRenderWhenClosed;
|
|
51
208
|
|
|
52
209
|
const closeOnEsc = props.closeOnEsc ?? true;
|
|
53
210
|
const esc = closeOnEsc && <HandleEsc onPress={props.onClose} />;
|
|
211
|
+
const portal = props.portal ?? true;
|
|
212
|
+
|
|
213
|
+
const overlayClick = props.onOverlayClick === undefined ? "close" : props.onOverlayClick;
|
|
214
|
+
const { onClose } = props;
|
|
215
|
+
const handleOverlayClick = useCallback(() => {
|
|
216
|
+
if (overlayClick === "close") {
|
|
217
|
+
onClose();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (typeof overlayClick === "function") {
|
|
221
|
+
overlayClick();
|
|
222
|
+
}
|
|
223
|
+
}, [overlayClick, onClose]);
|
|
54
224
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
225
|
+
const hasOverlay = props.size !== undefined || Boolean(scaleContent);
|
|
226
|
+
const overlayInteractive = overlayClick !== null && props.isOpen;
|
|
227
|
+
const tintStyle = {
|
|
228
|
+
opacity: isVisible ? DEFAULT_TINT_OPACITY : 0,
|
|
229
|
+
pointerEvents: (overlayInteractive ? "auto" : "none") as React.CSSProperties["pointerEvents"],
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const tree = (
|
|
233
|
+
<>
|
|
234
|
+
{/* eslint-disable-next-line react/jsx-no-leaked-render */}
|
|
235
|
+
{hasOverlay && (
|
|
236
|
+
<Tint
|
|
237
|
+
style={tintStyle}
|
|
238
|
+
onClick={handleOverlayClick}
|
|
239
|
+
aria-hidden={true}
|
|
240
|
+
/>
|
|
241
|
+
)}
|
|
242
|
+
<StyledDrawer className={props.className} style={style} ref={drawerRef}>
|
|
243
|
+
{esc}
|
|
244
|
+
<Content>
|
|
245
|
+
{/* eslint-disable-next-line react/jsx-no-leaked-render */}
|
|
246
|
+
{shouldRender && props.children}
|
|
247
|
+
</Content>
|
|
248
|
+
</StyledDrawer>
|
|
249
|
+
</>
|
|
63
250
|
);
|
|
251
|
+
|
|
252
|
+
if (portal) {
|
|
253
|
+
const root = typeof portal === "boolean" ? document.body : portal;
|
|
254
|
+
return createPortal(tree, root);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return tree;
|
|
64
258
|
});
|
|
65
259
|
|
|
66
260
|
Drawer.displayName = "Drawer";
|
|
@@ -69,4 +263,4 @@ Drawer.toString = () => StyledDrawer.toString();
|
|
|
69
263
|
const DrawerContentSelector = Content.toString();
|
|
70
264
|
|
|
71
265
|
export { Drawer, DrawerContentSelector };
|
|
72
|
-
export type { DrawerProps };
|
|
266
|
+
export type { DrawerProps, DrawerFrom };
|
|
@@ -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
|
};
|