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
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as RadixTooltip from "@radix-ui/react-tooltip";
|
|
2
|
+
|
|
3
|
+
import { dimensionsPxToRem, fontPxToRem, keyframes, pxToRem, styled } from "../../../theme";
|
|
4
|
+
|
|
5
|
+
const fadeIn = keyframes({
|
|
6
|
+
from: { opacity: 0, transform: "scale(0.96)" },
|
|
7
|
+
to: { opacity: 1, transform: "scale(1)" },
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const StyledContent = styled(RadixTooltip.Content, {
|
|
11
|
+
"zIndex": 10,
|
|
12
|
+
"background": "$text2",
|
|
13
|
+
"color": "$background",
|
|
14
|
+
"padding": `${dimensionsPxToRem(18)} ${dimensionsPxToRem(36)}`,
|
|
15
|
+
"borderRadius": dimensionsPxToRem(12),
|
|
16
|
+
"fontSize": fontPxToRem(24),
|
|
17
|
+
"lineHeight": 1.3,
|
|
18
|
+
"maxWidth": pxToRem(280),
|
|
19
|
+
"boxShadow": `0 ${pxToRem(2)} ${pxToRem(8)} rgba(0, 0, 0, 0.18)`,
|
|
20
|
+
"userSelect": "none",
|
|
21
|
+
"transformOrigin": "var(--radix-tooltip-content-transform-origin)",
|
|
22
|
+
"animation": `${fadeIn.toString()} 120ms ease-out`,
|
|
23
|
+
|
|
24
|
+
"&[data-state=\"closed\"]": {
|
|
25
|
+
animationDirection: "reverse",
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const StyledArrow = styled(RadixTooltip.Arrow, {
|
|
30
|
+
fill: "$text2",
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
StyledContent,
|
|
35
|
+
StyledArrow,
|
|
36
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import React, { forwardRef } from "react";
|
|
2
|
+
|
|
3
|
+
import * as RadixTooltip from "@radix-ui/react-tooltip";
|
|
4
|
+
|
|
5
|
+
import { StyledArrow, StyledContent } from "./Tooltip.styled";
|
|
6
|
+
|
|
7
|
+
type Side = "top" | "right" | "bottom" | "left";
|
|
8
|
+
type Align = "start" | "center" | "end";
|
|
9
|
+
|
|
10
|
+
interface TooltipProviderProps {
|
|
11
|
+
/**
|
|
12
|
+
* The duration from when the pointer enters the trigger until the tooltip opens, in milliseconds.
|
|
13
|
+
* Default: `500`.
|
|
14
|
+
*/
|
|
15
|
+
delayDuration?: number;
|
|
16
|
+
/**
|
|
17
|
+
* If a tooltip has opened, the duration during which other tooltips open instantly
|
|
18
|
+
* (without waiting for `delayDuration`), in milliseconds.
|
|
19
|
+
* Default: `300`.
|
|
20
|
+
*/
|
|
21
|
+
skipDelayDuration?: number;
|
|
22
|
+
/**
|
|
23
|
+
* When `true`, the tooltip content stays open while the pointer is over it.
|
|
24
|
+
* Set to `false` for stricter "hover the trigger only" behavior.
|
|
25
|
+
* Default: `true`.
|
|
26
|
+
*/
|
|
27
|
+
disableHoverableContent?: boolean;
|
|
28
|
+
children: React.ReactNode;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Required wrapper for `<Tooltip>`. Render it once near the root of your app — usually
|
|
33
|
+
* around (or just inside) your top-level layout. It enables group-delay behavior via
|
|
34
|
+
* `skipDelayDuration`: once one tooltip in the subtree has been seen, the next ones
|
|
35
|
+
* open without waiting for `delayDuration`.
|
|
36
|
+
*
|
|
37
|
+
* A single `<Tooltip>` without a `<TooltipProvider>` ancestor will throw at runtime.
|
|
38
|
+
*/
|
|
39
|
+
const TooltipProvider = (props: TooltipProviderProps) => {
|
|
40
|
+
const radixProps: Omit<React.ComponentProps<typeof RadixTooltip.Provider>, "children"> = {};
|
|
41
|
+
if (props.delayDuration !== undefined) { radixProps.delayDuration = props.delayDuration; }
|
|
42
|
+
if (props.skipDelayDuration !== undefined) { radixProps.skipDelayDuration = props.skipDelayDuration; }
|
|
43
|
+
if (props.disableHoverableContent !== undefined) {
|
|
44
|
+
radixProps.disableHoverableContent = props.disableHoverableContent;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<RadixTooltip.Provider {...radixProps}>
|
|
49
|
+
{props.children}
|
|
50
|
+
</RadixTooltip.Provider>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
interface TooltipProps {
|
|
55
|
+
/**
|
|
56
|
+
* The content to render inside the tooltip.
|
|
57
|
+
*/
|
|
58
|
+
content: React.ReactNode;
|
|
59
|
+
/**
|
|
60
|
+
* Preferred side of the trigger to render the tooltip against. Will be flipped
|
|
61
|
+
* automatically on collision unless `avoidCollisions` is `false`.
|
|
62
|
+
* Default: `"top"`.
|
|
63
|
+
*/
|
|
64
|
+
side?: Side;
|
|
65
|
+
/**
|
|
66
|
+
* Distance in pixels between the trigger and the tooltip.
|
|
67
|
+
* Default: `6`.
|
|
68
|
+
*/
|
|
69
|
+
sideOffset?: number;
|
|
70
|
+
/**
|
|
71
|
+
* Alignment along the chosen side.
|
|
72
|
+
* Default: `"center"`.
|
|
73
|
+
*/
|
|
74
|
+
align?: Align;
|
|
75
|
+
/**
|
|
76
|
+
* Offset in pixels from the `align` edge.
|
|
77
|
+
* Default: `0`.
|
|
78
|
+
*/
|
|
79
|
+
alignOffset?: number;
|
|
80
|
+
/**
|
|
81
|
+
* The duration from when the pointer enters the trigger until the tooltip opens.
|
|
82
|
+
* Overrides the provider's `delayDuration` for this tooltip only.
|
|
83
|
+
*/
|
|
84
|
+
delayDuration?: number;
|
|
85
|
+
/**
|
|
86
|
+
* When `true`, the tooltip will not flip to the opposite side on collision.
|
|
87
|
+
* Default: `true` (collisions are avoided).
|
|
88
|
+
*/
|
|
89
|
+
avoidCollisions?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* When `true`, renders an arrow pointing at the trigger.
|
|
92
|
+
* Default: `true`.
|
|
93
|
+
*/
|
|
94
|
+
arrow?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Controlled open state. If omitted, the tooltip is uncontrolled.
|
|
97
|
+
*/
|
|
98
|
+
open?: boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Initial open state for the uncontrolled mode.
|
|
101
|
+
*/
|
|
102
|
+
defaultOpen?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Called when the open state changes.
|
|
105
|
+
*/
|
|
106
|
+
onOpenChange?: (open: boolean) => void;
|
|
107
|
+
/**
|
|
108
|
+
* Additional class name applied to the tooltip content.
|
|
109
|
+
*/
|
|
110
|
+
className?: string;
|
|
111
|
+
/**
|
|
112
|
+
* The element that triggers the tooltip on hover/focus. Must be a single React element
|
|
113
|
+
* that can receive a ref and event handlers (a DOM element or a component using forwardRef).
|
|
114
|
+
*/
|
|
115
|
+
children: React.ReactElement;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* A tooltip that appears on hover or keyboard focus of its child trigger.
|
|
120
|
+
*
|
|
121
|
+
* Powered by Radix UI under the hood, so accessibility (ARIA, focus, escape, group delay)
|
|
122
|
+
* is handled. The content is portalled to `document.body`, so it escapes stacking contexts
|
|
123
|
+
* and renders above modals/drawers.
|
|
124
|
+
*
|
|
125
|
+
* Requires a `<TooltipProvider>` somewhere up the tree.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```tsx
|
|
129
|
+
* <TooltipProvider>
|
|
130
|
+
* <Tooltip content="Save changes" side="bottom">
|
|
131
|
+
* <Button>Save</Button>
|
|
132
|
+
* </Tooltip>
|
|
133
|
+
* </TooltipProvider>
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
// eslint-disable-next-line react/no-multi-comp
|
|
137
|
+
const Tooltip = forwardRef<HTMLDivElement, TooltipProps>((props, ref) => {
|
|
138
|
+
const {
|
|
139
|
+
content,
|
|
140
|
+
side = "top",
|
|
141
|
+
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
|
142
|
+
sideOffset = 6,
|
|
143
|
+
align = "center",
|
|
144
|
+
alignOffset = 0,
|
|
145
|
+
delayDuration,
|
|
146
|
+
avoidCollisions = true,
|
|
147
|
+
arrow = true,
|
|
148
|
+
open,
|
|
149
|
+
defaultOpen,
|
|
150
|
+
onOpenChange,
|
|
151
|
+
className,
|
|
152
|
+
children,
|
|
153
|
+
} = props;
|
|
154
|
+
|
|
155
|
+
const rootProps: React.ComponentProps<typeof RadixTooltip.Root> = {};
|
|
156
|
+
if (open !== undefined) { rootProps.open = open; }
|
|
157
|
+
if (defaultOpen !== undefined) { rootProps.defaultOpen = defaultOpen; }
|
|
158
|
+
if (onOpenChange !== undefined) { rootProps.onOpenChange = onOpenChange; }
|
|
159
|
+
if (delayDuration !== undefined) { rootProps.delayDuration = delayDuration; }
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<RadixTooltip.Root {...rootProps}>
|
|
163
|
+
<RadixTooltip.Trigger asChild={true}>
|
|
164
|
+
{children}
|
|
165
|
+
</RadixTooltip.Trigger>
|
|
166
|
+
<RadixTooltip.Portal>
|
|
167
|
+
<StyledContent
|
|
168
|
+
ref={ref}
|
|
169
|
+
side={side}
|
|
170
|
+
sideOffset={sideOffset}
|
|
171
|
+
align={align}
|
|
172
|
+
alignOffset={alignOffset}
|
|
173
|
+
avoidCollisions={avoidCollisions}
|
|
174
|
+
className={className}
|
|
175
|
+
>
|
|
176
|
+
{content}
|
|
177
|
+
{/* eslint-disable-next-line react/jsx-no-leaked-render */}
|
|
178
|
+
{arrow && <StyledArrow width={10} height={5} />}
|
|
179
|
+
</StyledContent>
|
|
180
|
+
</RadixTooltip.Portal>
|
|
181
|
+
</RadixTooltip.Root>
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
Tooltip.displayName = "Tooltip";
|
|
186
|
+
Tooltip.toString = () => StyledContent.toString();
|
|
187
|
+
|
|
188
|
+
const TooltipContentSelector = StyledContent.toString();
|
|
189
|
+
|
|
190
|
+
export {
|
|
191
|
+
Tooltip,
|
|
192
|
+
TooltipProvider,
|
|
193
|
+
TooltipContentSelector,
|
|
194
|
+
};
|
|
195
|
+
export type { TooltipProps, TooltipProviderProps };
|
package/src/index.ts
CHANGED
|
@@ -42,6 +42,7 @@ export * from "./components/ui/stats/Stats";
|
|
|
42
42
|
export * from "./components/ui/tabs/Selector";
|
|
43
43
|
export * from "./components/ui/toaster/Toaster";
|
|
44
44
|
export * from "./components/ui/toolButton/ToolButton";
|
|
45
|
+
export * from "./components/ui/tooltip/Tooltip";
|
|
45
46
|
|
|
46
47
|
export * from "./components/utils/Gap";
|
|
47
48
|
export * from "./components/utils/HandleEsc";
|