datocms-react-ui 2.1.0-alpha.0 → 2.1.0-alpha.2
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/dist/cjs/HotKey/index.js +105 -0
- package/dist/cjs/HotKey/index.js.map +1 -0
- package/dist/cjs/HotKey/styles.module.css.json +1 -0
- package/dist/cjs/SplitView/SplitViewPane/index.js +14 -0
- package/dist/cjs/SplitView/SplitViewPane/index.js.map +1 -0
- package/dist/cjs/SplitView/SplitViewPane/styles.module.css.json +1 -0
- package/dist/cjs/SplitView/SplitViewSash/index.js +76 -0
- package/dist/cjs/SplitView/SplitViewSash/index.js.map +1 -0
- package/dist/cjs/SplitView/SplitViewSash/styles.module.css.json +1 -0
- package/dist/cjs/SplitView/index.js +206 -0
- package/dist/cjs/SplitView/index.js.map +1 -0
- package/dist/cjs/SplitView/styles.module.css.json +1 -0
- package/dist/cjs/SplitView/types.js +3 -0
- package/dist/cjs/SplitView/types.js.map +1 -0
- package/dist/cjs/Tooltip/Tooltip/index.js +116 -0
- package/dist/cjs/Tooltip/Tooltip/index.js.map +1 -0
- package/dist/cjs/Tooltip/TooltipContent/index.js +147 -0
- package/dist/cjs/Tooltip/TooltipContent/index.js.map +1 -0
- package/dist/cjs/Tooltip/TooltipContent/styles.module.css.json +1 -0
- package/dist/cjs/Tooltip/TooltipDelayGroup/index.js +140 -0
- package/dist/cjs/Tooltip/TooltipDelayGroup/index.js.map +1 -0
- package/dist/cjs/Tooltip/TooltipTrigger/index.js +102 -0
- package/dist/cjs/Tooltip/TooltipTrigger/index.js.map +1 -0
- package/dist/cjs/Tooltip/index.js +12 -0
- package/dist/cjs/Tooltip/index.js.map +1 -0
- package/dist/cjs/Tooltip/utils.js +165 -0
- package/dist/cjs/Tooltip/utils.js.map +1 -0
- package/dist/cjs/VerticalSplit/index.js +337 -0
- package/dist/cjs/VerticalSplit/index.js.map +1 -0
- package/dist/cjs/VerticalSplit/styles.module.css.json +1 -0
- package/dist/cjs/icons.js +19 -1
- package/dist/cjs/icons.js.map +1 -1
- package/dist/cjs/index.js +7 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/HotKey/index.d.ts +70 -0
- package/dist/esm/HotKey/index.js +75 -0
- package/dist/esm/HotKey/index.js.map +1 -0
- package/dist/esm/HotKey/styles.module.css.json +1 -0
- package/dist/esm/SplitView/SplitViewPane/index.d.ts +7 -0
- package/dist/esm/SplitView/SplitViewPane/index.js +7 -0
- package/dist/esm/SplitView/SplitViewPane/index.js.map +1 -0
- package/dist/esm/SplitView/SplitViewPane/styles.module.css.json +1 -0
- package/dist/esm/SplitView/SplitViewSash/index.d.ts +17 -0
- package/dist/esm/SplitView/SplitViewSash/index.js +46 -0
- package/dist/esm/SplitView/SplitViewSash/index.js.map +1 -0
- package/dist/esm/SplitView/SplitViewSash/styles.module.css.json +1 -0
- package/dist/esm/SplitView/index.d.ts +16 -0
- package/dist/esm/SplitView/index.js +176 -0
- package/dist/esm/SplitView/index.js.map +1 -0
- package/dist/esm/SplitView/styles.module.css.json +1 -0
- package/dist/esm/SplitView/types.d.ts +8 -0
- package/dist/esm/SplitView/types.js +2 -0
- package/dist/esm/SplitView/types.js.map +1 -0
- package/dist/esm/Tooltip/Tooltip/index.d.ts +74 -0
- package/dist/esm/Tooltip/Tooltip/index.js +89 -0
- package/dist/esm/Tooltip/Tooltip/index.js.map +1 -0
- package/dist/esm/Tooltip/TooltipContent/index.d.ts +68 -0
- package/dist/esm/Tooltip/TooltipContent/index.js +118 -0
- package/dist/esm/Tooltip/TooltipContent/index.js.map +1 -0
- package/dist/esm/Tooltip/TooltipContent/styles.module.css.json +1 -0
- package/dist/esm/Tooltip/TooltipDelayGroup/index.d.ts +118 -0
- package/dist/esm/Tooltip/TooltipDelayGroup/index.js +113 -0
- package/dist/esm/Tooltip/TooltipDelayGroup/index.js.map +1 -0
- package/dist/esm/Tooltip/TooltipTrigger/index.d.ts +45 -0
- package/dist/esm/Tooltip/TooltipTrigger/index.js +76 -0
- package/dist/esm/Tooltip/TooltipTrigger/index.js.map +1 -0
- package/dist/esm/Tooltip/index.d.ts +8 -0
- package/dist/esm/Tooltip/index.js +5 -0
- package/dist/esm/Tooltip/index.js.map +1 -0
- package/dist/esm/Tooltip/utils.d.ts +166 -0
- package/dist/esm/Tooltip/utils.js +135 -0
- package/dist/esm/Tooltip/utils.js.map +1 -0
- package/dist/esm/VerticalSplit/index.d.ts +238 -0
- package/dist/esm/VerticalSplit/index.js +307 -0
- package/dist/esm/VerticalSplit/index.js.map +1 -0
- package/dist/esm/VerticalSplit/styles.module.css.json +1 -0
- package/dist/esm/icons.d.ts +3 -0
- package/dist/esm/icons.js +15 -0
- package/dist/esm/icons.js.map +1 -1
- package/dist/esm/index.d.ts +7 -3
- package/dist/esm/index.js +7 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/types/HotKey/index.d.ts +70 -0
- package/dist/types/SplitView/SplitViewPane/index.d.ts +7 -0
- package/dist/types/SplitView/SplitViewSash/index.d.ts +17 -0
- package/dist/types/SplitView/index.d.ts +16 -0
- package/dist/types/SplitView/types.d.ts +8 -0
- package/dist/types/Tooltip/Tooltip/index.d.ts +74 -0
- package/dist/types/Tooltip/TooltipContent/index.d.ts +68 -0
- package/dist/types/Tooltip/TooltipDelayGroup/index.d.ts +118 -0
- package/dist/types/Tooltip/TooltipTrigger/index.d.ts +45 -0
- package/dist/types/Tooltip/index.d.ts +8 -0
- package/dist/types/Tooltip/utils.d.ts +166 -0
- package/dist/types/VerticalSplit/index.d.ts +238 -0
- package/dist/types/icons.d.ts +3 -0
- package/dist/types/index.d.ts +7 -3
- package/package.json +4 -3
- package/src/HotKey/index.tsx +95 -0
- package/src/HotKey/styles.module.css +22 -0
- package/src/HotKey/styles.module.css.json +1 -0
- package/src/SplitView/SplitViewPane/index.tsx +19 -0
- package/src/SplitView/SplitViewPane/styles.module.css +6 -0
- package/src/SplitView/SplitViewPane/styles.module.css.json +1 -0
- package/src/SplitView/SplitViewSash/index.tsx +99 -0
- package/src/SplitView/SplitViewSash/styles.module.css +68 -0
- package/src/SplitView/SplitViewSash/styles.module.css.json +1 -0
- package/src/SplitView/index.tsx +256 -0
- package/src/SplitView/styles.module.css +22 -0
- package/src/SplitView/styles.module.css.json +1 -0
- package/src/SplitView/types.ts +9 -0
- package/src/Tooltip/Tooltip/index.tsx +85 -0
- package/src/Tooltip/TooltipContent/index.tsx +145 -0
- package/src/Tooltip/TooltipContent/styles.module.css +10 -0
- package/src/Tooltip/TooltipContent/styles.module.css.json +1 -0
- package/src/Tooltip/TooltipDelayGroup/index.tsx +128 -0
- package/src/Tooltip/TooltipTrigger/index.tsx +71 -0
- package/src/Tooltip/index.ts +8 -0
- package/src/Tooltip/utils.ts +176 -0
- package/src/VerticalSplit/index.tsx +401 -0
- package/src/VerticalSplit/styles.module.css +103 -0
- package/src/VerticalSplit/styles.module.css.json +1 -0
- package/src/global.css +31 -25
- package/src/icons.tsx +60 -0
- package/src/index.ts +7 -3
- package/styles.css +1 -1
- package/types.json +6126 -3451
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import React, {
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import { SplitViewPane, type SplitViewPaneProps } from './SplitViewPane';
|
|
10
|
+
import { SashAction, SplitViewSash } from './SplitViewSash';
|
|
11
|
+
import s from './styles.module.css.json';
|
|
12
|
+
import { IAxis, ICacheSizes } from './types';
|
|
13
|
+
|
|
14
|
+
type SplitViewProps = {
|
|
15
|
+
children: JSX.Element[];
|
|
16
|
+
allowResize?: boolean;
|
|
17
|
+
split?: 'vertical' | 'horizontal';
|
|
18
|
+
sizes: (string | number)[];
|
|
19
|
+
onChange: (sizes: number[]) => void;
|
|
20
|
+
onDragStart?: () => void;
|
|
21
|
+
onDragEnd?: () => void;
|
|
22
|
+
performanceMode?: boolean;
|
|
23
|
+
resizerSize?: number;
|
|
24
|
+
sashAction?: SashAction;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const SplitView = ({
|
|
28
|
+
children,
|
|
29
|
+
sizes: propSizes,
|
|
30
|
+
allowResize = true,
|
|
31
|
+
split = 'vertical',
|
|
32
|
+
resizerSize = 10,
|
|
33
|
+
performanceMode = false,
|
|
34
|
+
onChange = () => null,
|
|
35
|
+
onDragStart = () => null,
|
|
36
|
+
onDragEnd = () => null,
|
|
37
|
+
sashAction,
|
|
38
|
+
}: SplitViewProps) => {
|
|
39
|
+
const axis = useRef<IAxis>({ x: 0, y: 0 });
|
|
40
|
+
const wrapper = useRef<HTMLDivElement>(null);
|
|
41
|
+
const cacheSizes = useRef<ICacheSizes>({ sizes: [], sashPosSizes: [] });
|
|
42
|
+
const [wrapperRect, setWrapperRect] = useState<DOMRect | undefined>(
|
|
43
|
+
undefined,
|
|
44
|
+
);
|
|
45
|
+
const [isDragging, setDragging] = useState<boolean>(false);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
49
|
+
if (wrapper?.current) {
|
|
50
|
+
setWrapperRect(wrapper.current.getBoundingClientRect());
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
resizeObserver.observe(wrapper.current!);
|
|
54
|
+
return () => {
|
|
55
|
+
resizeObserver.disconnect();
|
|
56
|
+
};
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
const { sizeName, splitPos, splitAxis } = useMemo<{
|
|
60
|
+
sizeName: 'width' | 'height';
|
|
61
|
+
splitPos: 'left' | 'top';
|
|
62
|
+
splitAxis: 'x' | 'y';
|
|
63
|
+
}>(
|
|
64
|
+
() => ({
|
|
65
|
+
sizeName: split === 'vertical' ? 'width' : 'height',
|
|
66
|
+
splitPos: split === 'vertical' ? 'left' : 'top',
|
|
67
|
+
splitAxis: split === 'vertical' ? 'x' : 'y',
|
|
68
|
+
}),
|
|
69
|
+
[split],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const wrapSize: number = wrapperRect ? wrapperRect[sizeName] : 0;
|
|
73
|
+
|
|
74
|
+
// Get limit sizes via children
|
|
75
|
+
const paneLimitSizes = useMemo(
|
|
76
|
+
() =>
|
|
77
|
+
children.map((childNode) => {
|
|
78
|
+
const limits = [0, Infinity];
|
|
79
|
+
if (childNode.type === SplitViewPane) {
|
|
80
|
+
const { minSize, maxSize } = childNode.props as SplitViewPaneProps;
|
|
81
|
+
limits[0] = assertSize(minSize, wrapSize, 0);
|
|
82
|
+
limits[1] = assertSize(maxSize, wrapSize);
|
|
83
|
+
}
|
|
84
|
+
return limits as [number, number];
|
|
85
|
+
}),
|
|
86
|
+
[children, wrapSize],
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const sizes = useMemo(() => {
|
|
90
|
+
let count = 0;
|
|
91
|
+
let curSum = 0;
|
|
92
|
+
|
|
93
|
+
const res = children.map((_child, index) => {
|
|
94
|
+
const size = clampSize(
|
|
95
|
+
assertSize(propSizes[index], wrapSize),
|
|
96
|
+
paneLimitSizes[index],
|
|
97
|
+
);
|
|
98
|
+
if (size === Infinity) {
|
|
99
|
+
count = count + 1;
|
|
100
|
+
} else {
|
|
101
|
+
curSum = curSum + size;
|
|
102
|
+
}
|
|
103
|
+
return size;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// resize or illegal size input,recalculate pane sizes
|
|
107
|
+
if (curSum > wrapSize || (!count && curSum < wrapSize)) {
|
|
108
|
+
const cacheNum = (curSum - wrapSize) / curSum;
|
|
109
|
+
return res.map((size) => {
|
|
110
|
+
return size === Infinity ? 0 : size - size * cacheNum;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (count > 0) {
|
|
115
|
+
const average = (wrapSize - curSum) / count;
|
|
116
|
+
return res.map((size) => {
|
|
117
|
+
return size === Infinity ? average : size;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return res;
|
|
122
|
+
}, [
|
|
123
|
+
JSON.stringify(propSizes),
|
|
124
|
+
JSON.stringify(paneLimitSizes),
|
|
125
|
+
children.length,
|
|
126
|
+
wrapSize,
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
const sashPosSizes = useMemo(
|
|
130
|
+
() => sizes.reduce((a, b) => [...a, a[a.length - 1] + b], [0]),
|
|
131
|
+
[JSON.stringify(sizes)],
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const handleMouseDown = useCallback(
|
|
135
|
+
(x: number, y: number) => {
|
|
136
|
+
document?.body?.classList?.add(s['SplitView--disable-select']);
|
|
137
|
+
axis.current = { x, y };
|
|
138
|
+
cacheSizes.current = { sizes, sashPosSizes };
|
|
139
|
+
setDragging(true);
|
|
140
|
+
onDragStart();
|
|
141
|
+
},
|
|
142
|
+
[onDragStart, sizes, sashPosSizes],
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const handleMouseUp = useCallback(() => {
|
|
146
|
+
document?.body?.classList?.remove(s['SplitView--disable-select']);
|
|
147
|
+
setDragging(false);
|
|
148
|
+
onDragEnd();
|
|
149
|
+
}, [onDragEnd]);
|
|
150
|
+
|
|
151
|
+
const handleMouseMove = useCallback(
|
|
152
|
+
(x: number, y: number, i: number) => {
|
|
153
|
+
const curAxis = { x, y };
|
|
154
|
+
let distanceX = curAxis[splitAxis] - axis.current[splitAxis];
|
|
155
|
+
|
|
156
|
+
const leftBorder = -Math.min(
|
|
157
|
+
sizes[i] - paneLimitSizes[i][0],
|
|
158
|
+
paneLimitSizes[i + 1][1] - sizes[i + 1],
|
|
159
|
+
);
|
|
160
|
+
const rightBorder = Math.min(
|
|
161
|
+
sizes[i + 1] - paneLimitSizes[i + 1][0],
|
|
162
|
+
paneLimitSizes[i][1] - sizes[i],
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
if (distanceX < leftBorder) {
|
|
166
|
+
distanceX = leftBorder;
|
|
167
|
+
}
|
|
168
|
+
if (distanceX > rightBorder) {
|
|
169
|
+
distanceX = rightBorder;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const nextSizes = [...sizes];
|
|
173
|
+
nextSizes[i] += distanceX;
|
|
174
|
+
nextSizes[i + 1] -= distanceX;
|
|
175
|
+
|
|
176
|
+
onChange(nextSizes);
|
|
177
|
+
},
|
|
178
|
+
[paneLimitSizes, onChange],
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const paneFollow = !(performanceMode && isDragging);
|
|
182
|
+
const paneSizes = paneFollow ? sizes : cacheSizes.current.sizes;
|
|
183
|
+
const panePoses = paneFollow ? sashPosSizes : cacheSizes.current.sashPosSizes;
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<div
|
|
187
|
+
className={classNames(
|
|
188
|
+
s.SplitView,
|
|
189
|
+
split === 'vertical' && s['SplitView--vertical'],
|
|
190
|
+
split === 'horizontal' && s['SplitView--horizontal'],
|
|
191
|
+
isDragging && s['SplitView--dragging'],
|
|
192
|
+
)}
|
|
193
|
+
ref={wrapper}
|
|
194
|
+
>
|
|
195
|
+
{children.map((childNode, childIndex) => {
|
|
196
|
+
const isPane = childNode.type === SplitViewPane;
|
|
197
|
+
const paneProps = isPane ? childNode.props : {};
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<SplitViewPane
|
|
201
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
|
|
202
|
+
key={childIndex}
|
|
203
|
+
style={{
|
|
204
|
+
...paneProps.style,
|
|
205
|
+
[sizeName]: paneSizes[childIndex],
|
|
206
|
+
[splitPos]: panePoses[childIndex],
|
|
207
|
+
}}
|
|
208
|
+
>
|
|
209
|
+
{isPane ? paneProps.children : childNode}
|
|
210
|
+
</SplitViewPane>
|
|
211
|
+
);
|
|
212
|
+
})}
|
|
213
|
+
{sashPosSizes.slice(1, -1).map((posSize, index) => (
|
|
214
|
+
<SplitViewSash
|
|
215
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
|
|
216
|
+
key={index}
|
|
217
|
+
allowResize={allowResize}
|
|
218
|
+
split={split}
|
|
219
|
+
style={{
|
|
220
|
+
[sizeName]: resizerSize,
|
|
221
|
+
[splitPos]: posSize - resizerSize / 2,
|
|
222
|
+
}}
|
|
223
|
+
onMouseDown={handleMouseDown}
|
|
224
|
+
onMouseMove={(x, y) => handleMouseMove(x, y, index)}
|
|
225
|
+
onMouseUp={handleMouseUp}
|
|
226
|
+
action={sashAction}
|
|
227
|
+
/>
|
|
228
|
+
))}
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Convert size to absolute number or Infinity
|
|
235
|
+
* SplitPane allows sizes in string and number, but the state sizes only support number,
|
|
236
|
+
* so convert string and number to number in here
|
|
237
|
+
* 'auto' -> divide the remaining space equally
|
|
238
|
+
* 'xxxpx' -> xxx
|
|
239
|
+
* 'xxx%' -> wrapper.size * xxx/100
|
|
240
|
+
* xxx -> xxx
|
|
241
|
+
*/
|
|
242
|
+
function assertSize(
|
|
243
|
+
size: string | number | undefined,
|
|
244
|
+
sum: number,
|
|
245
|
+
defaultValue = Infinity,
|
|
246
|
+
) {
|
|
247
|
+
if (typeof size === 'undefined') return defaultValue;
|
|
248
|
+
if (typeof size === 'number') return size;
|
|
249
|
+
if (size.endsWith('%')) return sum * (+size.replace('%', '') / 100);
|
|
250
|
+
if (size.endsWith('px')) return +size.replace('px', '');
|
|
251
|
+
return defaultValue;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function clampSize(size: number, minMax: [number, number]) {
|
|
255
|
+
return Math.max(minMax[0], Math.min(minMax[1], size));
|
|
256
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.SplitView {
|
|
2
|
+
flex: 1;
|
|
3
|
+
height: 100%;
|
|
4
|
+
position: relative;
|
|
5
|
+
width: 100%;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.SplitView--dragging.SplitView--vertical {
|
|
9
|
+
cursor: col-resize;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.SplitView--dragging.SplitView--horizontal {
|
|
13
|
+
cursor: row-resize;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.SplitView--disable-select {
|
|
17
|
+
user-select: none;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.SplitView--disable-select .SplitViewPane {
|
|
21
|
+
pointer-events: none;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"SplitView":"_SplitView_1oi17_1","SplitView--dragging":"_SplitView--dragging_1oi17_8","SplitView--vertical":"_SplitView--vertical_1oi17_8","SplitView--horizontal":"_SplitView--horizontal_1oi17_12","SplitView--disable-select":"_SplitView--disable-select_1oi17_16","SplitViewPane":"_SplitViewPane_1oi17_20"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { TooltipContext, TooltipOptions, useTooltip } from '../utils';
|
|
3
|
+
|
|
4
|
+
export type TooltipProps = {
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
} & TooltipOptions;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Tooltip wrapper component that provides context for TooltipTrigger and TooltipContent.
|
|
10
|
+
*
|
|
11
|
+
* This is a compound component pattern. The Tooltip component itself doesn't render anything,
|
|
12
|
+
* but provides the necessary context for its children (TooltipTrigger and TooltipContent).
|
|
13
|
+
*
|
|
14
|
+
* @example Basic tooltip
|
|
15
|
+
*
|
|
16
|
+
* Create a simple tooltip that appears when hovering over a button:
|
|
17
|
+
*
|
|
18
|
+
* ```js
|
|
19
|
+
* <Canvas ctx={ctx}>
|
|
20
|
+
* <Tooltip>
|
|
21
|
+
* <TooltipTrigger>
|
|
22
|
+
* <Button>Hover me</Button>
|
|
23
|
+
* </TooltipTrigger>
|
|
24
|
+
* <TooltipContent>
|
|
25
|
+
* This is helpful information!
|
|
26
|
+
* </TooltipContent>
|
|
27
|
+
* </Tooltip>
|
|
28
|
+
* </Canvas>;
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @example Tooltip with custom placement
|
|
32
|
+
*
|
|
33
|
+
* Control where the tooltip appears relative to its trigger using the `placement` prop:
|
|
34
|
+
*
|
|
35
|
+
* ```js
|
|
36
|
+
* <Canvas ctx={ctx}>
|
|
37
|
+
* <Tooltip placement="right">
|
|
38
|
+
* <TooltipTrigger>
|
|
39
|
+
* <Button>Right tooltip</Button>
|
|
40
|
+
* </TooltipTrigger>
|
|
41
|
+
* <TooltipContent>
|
|
42
|
+
* Appears on the right side
|
|
43
|
+
* </TooltipContent>
|
|
44
|
+
* </Tooltip>
|
|
45
|
+
* </Canvas>;
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @example Multiple tooltips
|
|
49
|
+
*
|
|
50
|
+
* Use multiple tooltips on the same page to provide contextual help for different actions:
|
|
51
|
+
*
|
|
52
|
+
* ```js
|
|
53
|
+
* <Canvas ctx={ctx}>
|
|
54
|
+
* <div style={{ display: 'flex', gap: 'var(--spacing-m)' }}>
|
|
55
|
+
* <Tooltip>
|
|
56
|
+
* <TooltipTrigger>
|
|
57
|
+
* <Button leftIcon={<SaveIcon />} />
|
|
58
|
+
* </TooltipTrigger>
|
|
59
|
+
* <TooltipContent>
|
|
60
|
+
* Save changes (⌘S)
|
|
61
|
+
* </TooltipContent>
|
|
62
|
+
* </Tooltip>
|
|
63
|
+
*
|
|
64
|
+
* <Tooltip>
|
|
65
|
+
* <TooltipTrigger>
|
|
66
|
+
* <Button leftIcon={<DeleteIcon />} />
|
|
67
|
+
* </TooltipTrigger>
|
|
68
|
+
* <TooltipContent>
|
|
69
|
+
* Delete item
|
|
70
|
+
* </TooltipContent>
|
|
71
|
+
* </Tooltip>
|
|
72
|
+
* </div>
|
|
73
|
+
* </Canvas>;
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export function Tooltip({ children, ...options }: TooltipProps) {
|
|
77
|
+
// This can accept any props as options, e.g. `placement`,
|
|
78
|
+
// or other positioning options.
|
|
79
|
+
const tooltip = useTooltip(options);
|
|
80
|
+
return (
|
|
81
|
+
<TooltipContext.Provider value={tooltip}>
|
|
82
|
+
{children}
|
|
83
|
+
</TooltipContext.Provider>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FloatingPortal,
|
|
3
|
+
useDelayGroup,
|
|
4
|
+
useDelayGroupContext,
|
|
5
|
+
useMergeRefs,
|
|
6
|
+
useTransitionStyles,
|
|
7
|
+
} from '@floating-ui/react';
|
|
8
|
+
import * as React from 'react';
|
|
9
|
+
import { Canvas, useCtx } from '../../Canvas';
|
|
10
|
+
import {
|
|
11
|
+
getSharedPortalRoot,
|
|
12
|
+
releaseSharedPortalRoot,
|
|
13
|
+
useTooltipState,
|
|
14
|
+
} from '../utils';
|
|
15
|
+
import s from './styles.module.css.json';
|
|
16
|
+
|
|
17
|
+
export type TooltipContentProps = {
|
|
18
|
+
children?: React.ReactNode;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* TooltipContent contains the content displayed in the floating tooltip.
|
|
23
|
+
*
|
|
24
|
+
* The content is automatically wrapped in a Canvas component to inherit the DatoCMS
|
|
25
|
+
* theme and styling. The tooltip uses a portal to render outside the normal DOM
|
|
26
|
+
* hierarchy, ensuring proper positioning and z-index stacking.
|
|
27
|
+
*
|
|
28
|
+
* @example Simple text tooltip
|
|
29
|
+
*
|
|
30
|
+
* Display plain text in a tooltip to provide helpful information:
|
|
31
|
+
*
|
|
32
|
+
* ```js
|
|
33
|
+
* <Canvas ctx={ctx}>
|
|
34
|
+
* <Tooltip>
|
|
35
|
+
* <TooltipTrigger>
|
|
36
|
+
* <Button>Delete</Button>
|
|
37
|
+
* </TooltipTrigger>
|
|
38
|
+
* <TooltipContent>
|
|
39
|
+
* This action cannot be undone
|
|
40
|
+
* </TooltipContent>
|
|
41
|
+
* </Tooltip>
|
|
42
|
+
* </Canvas>;
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @example Rich content tooltip
|
|
46
|
+
*
|
|
47
|
+
* Include formatted content with custom styles for more detailed information:
|
|
48
|
+
*
|
|
49
|
+
* ```js
|
|
50
|
+
* <Canvas ctx={ctx}>
|
|
51
|
+
* <Tooltip placement="right">
|
|
52
|
+
* <TooltipTrigger>
|
|
53
|
+
* <Button leftIcon={<HelpIcon />}>Help</Button>
|
|
54
|
+
* </TooltipTrigger>
|
|
55
|
+
* <TooltipContent>
|
|
56
|
+
* <div>
|
|
57
|
+
* <strong>Need assistance?</strong>
|
|
58
|
+
* <p style={{ margin: '5px 0 0 0', fontSize: 'var(--font-size-xs)' }}>
|
|
59
|
+
* Contact support@example.com
|
|
60
|
+
* </p>
|
|
61
|
+
* </div>
|
|
62
|
+
* </TooltipContent>
|
|
63
|
+
* </Tooltip>
|
|
64
|
+
* </Canvas>;
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* @example Tooltip with keyboard shortcut
|
|
68
|
+
*
|
|
69
|
+
* Combine tooltips with the HotKey component to show keyboard shortcuts:
|
|
70
|
+
*
|
|
71
|
+
* ```js
|
|
72
|
+
* <Canvas ctx={ctx}>
|
|
73
|
+
* <Tooltip>
|
|
74
|
+
* <TooltipTrigger>
|
|
75
|
+
* <Button leftIcon={<SaveIcon />}>Save</Button>
|
|
76
|
+
* </TooltipTrigger>
|
|
77
|
+
* <TooltipContent>
|
|
78
|
+
* <HotKey hotkey="mod+s" label="Save changes" />
|
|
79
|
+
* </TooltipContent>
|
|
80
|
+
* </Tooltip>
|
|
81
|
+
* </Canvas>;
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export const TooltipContent = React.forwardRef<
|
|
85
|
+
HTMLDivElement,
|
|
86
|
+
TooltipContentProps
|
|
87
|
+
>(function TooltipContent({ children }, propRef) {
|
|
88
|
+
const ctx = useCtx();
|
|
89
|
+
const state = useTooltipState();
|
|
90
|
+
const { isInstantPhase, currentId } = useDelayGroupContext();
|
|
91
|
+
const ref = useMergeRefs([state.refs.setFloating, propRef]);
|
|
92
|
+
|
|
93
|
+
// Use the shared portal root
|
|
94
|
+
const portalRootRef = React.useRef<HTMLDivElement | null>(null);
|
|
95
|
+
|
|
96
|
+
React.useEffect(() => {
|
|
97
|
+
// Get the shared portal root and increment ref count
|
|
98
|
+
portalRootRef.current = getSharedPortalRoot();
|
|
99
|
+
|
|
100
|
+
// Cleanup function to release the shared portal root
|
|
101
|
+
return () => {
|
|
102
|
+
releaseSharedPortalRoot();
|
|
103
|
+
};
|
|
104
|
+
}, []);
|
|
105
|
+
|
|
106
|
+
useDelayGroup(state.context, { id: state.context.floatingId });
|
|
107
|
+
|
|
108
|
+
const instantDuration = 0;
|
|
109
|
+
const duration = 250;
|
|
110
|
+
|
|
111
|
+
const { isMounted, styles } = useTransitionStyles(state.context, {
|
|
112
|
+
duration: isInstantPhase
|
|
113
|
+
? {
|
|
114
|
+
open: instantDuration,
|
|
115
|
+
// `id` is this component's `id`
|
|
116
|
+
// `currentId` is the current group's `id`
|
|
117
|
+
close:
|
|
118
|
+
currentId === state.context.floatingId ? duration : instantDuration,
|
|
119
|
+
}
|
|
120
|
+
: duration,
|
|
121
|
+
initial: {
|
|
122
|
+
opacity: 0,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (!isMounted) return null;
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<FloatingPortal root={portalRootRef}>
|
|
130
|
+
<Canvas ctx={ctx} noAutoResizer>
|
|
131
|
+
<div
|
|
132
|
+
ref={ref}
|
|
133
|
+
style={{
|
|
134
|
+
...state.floatingStyles,
|
|
135
|
+
...styles,
|
|
136
|
+
}}
|
|
137
|
+
{...state.getFloatingProps()}
|
|
138
|
+
className={s.tooltip}
|
|
139
|
+
>
|
|
140
|
+
{children}
|
|
141
|
+
</div>
|
|
142
|
+
</Canvas>
|
|
143
|
+
</FloatingPortal>
|
|
144
|
+
);
|
|
145
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"tooltip":"_tooltip_3z5rn_1"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { FloatingDelayGroup } from '@floating-ui/react';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
export type TooltipDelayGroupProps = {
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
/** The delay in milliseconds before a tooltip opens on hover (default: 1000) */
|
|
7
|
+
delay?: number | { open?: number; close?: number };
|
|
8
|
+
/** How long to wait in milliseconds before closing the group (default: 0) */
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* TooltipDelayGroup synchronizes hover delays across multiple tooltips.
|
|
14
|
+
*
|
|
15
|
+
* When tooltips are wrapped in a delay group, hovering over the first tooltip
|
|
16
|
+
* will use the configured delay, but subsequent tooltips in the group will
|
|
17
|
+
* open instantly. This creates a smoother UX when users explore multiple
|
|
18
|
+
* interactive elements with tooltips.
|
|
19
|
+
*
|
|
20
|
+
* @example Basic delay group
|
|
21
|
+
*
|
|
22
|
+
* Group multiple tooltips together so they open instantly after the first one:
|
|
23
|
+
*
|
|
24
|
+
* ```js
|
|
25
|
+
* <Canvas ctx={ctx}>
|
|
26
|
+
* <TooltipDelayGroup delay={500}>
|
|
27
|
+
* <div style={{ display: 'flex', gap: 'var(--spacing-m)' }}>
|
|
28
|
+
* <Tooltip>
|
|
29
|
+
* <TooltipTrigger>
|
|
30
|
+
* <Button leftIcon={<SaveIcon />} />
|
|
31
|
+
* </TooltipTrigger>
|
|
32
|
+
* <TooltipContent>Save changes</TooltipContent>
|
|
33
|
+
* </Tooltip>
|
|
34
|
+
*
|
|
35
|
+
* <Tooltip>
|
|
36
|
+
* <TooltipTrigger>
|
|
37
|
+
* <Button leftIcon={<UndoIcon />} />
|
|
38
|
+
* </TooltipTrigger>
|
|
39
|
+
* <TooltipContent>Undo</TooltipContent>
|
|
40
|
+
* </Tooltip>
|
|
41
|
+
*
|
|
42
|
+
* <Tooltip>
|
|
43
|
+
* <TooltipTrigger>
|
|
44
|
+
* <Button leftIcon={<RedoIcon />} />
|
|
45
|
+
* </TooltipTrigger>
|
|
46
|
+
* <TooltipContent>Redo</TooltipContent>
|
|
47
|
+
* </Tooltip>
|
|
48
|
+
* </div>
|
|
49
|
+
* </TooltipDelayGroup>
|
|
50
|
+
* </Canvas>;
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @example Custom delay configuration
|
|
54
|
+
*
|
|
55
|
+
* Configure different delays for opening and closing tooltips in the group:
|
|
56
|
+
*
|
|
57
|
+
* ```js
|
|
58
|
+
* <Canvas ctx={ctx}>
|
|
59
|
+
* <TooltipDelayGroup delay={{ open: 800, close: 200 }} timeoutMs={500}>
|
|
60
|
+
* <div style={{ display: 'flex', gap: 'var(--spacing-s)' }}>
|
|
61
|
+
* <Tooltip>
|
|
62
|
+
* <TooltipTrigger>
|
|
63
|
+
* <Button>Action 1</Button>
|
|
64
|
+
* </TooltipTrigger>
|
|
65
|
+
* <TooltipContent>First action</TooltipContent>
|
|
66
|
+
* </Tooltip>
|
|
67
|
+
*
|
|
68
|
+
* <Tooltip>
|
|
69
|
+
* <TooltipTrigger>
|
|
70
|
+
* <Button>Action 2</Button>
|
|
71
|
+
* </TooltipTrigger>
|
|
72
|
+
* <TooltipContent>Second action</TooltipContent>
|
|
73
|
+
* </Tooltip>
|
|
74
|
+
* </div>
|
|
75
|
+
* </TooltipDelayGroup>
|
|
76
|
+
* </Canvas>;
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @example Toolbar with grouped tooltips
|
|
80
|
+
*
|
|
81
|
+
* Create a toolbar where hovering between tools feels instant and responsive:
|
|
82
|
+
*
|
|
83
|
+
* ```js
|
|
84
|
+
* <Canvas ctx={ctx}>
|
|
85
|
+
* <TooltipDelayGroup delay={600}>
|
|
86
|
+
* <div style={{
|
|
87
|
+
* display: 'flex',
|
|
88
|
+
* gap: 'var(--spacing-xs)',
|
|
89
|
+
* padding: 'var(--spacing-s)',
|
|
90
|
+
* borderRadius: 'var(--border-radius-m)',
|
|
91
|
+
* backgroundColor: 'var(--light-bg-color)'
|
|
92
|
+
* }}>
|
|
93
|
+
* <Tooltip>
|
|
94
|
+
* <TooltipTrigger>
|
|
95
|
+
* <Button buttonSize="s" leftIcon={<BoldIcon />} />
|
|
96
|
+
* </TooltipTrigger>
|
|
97
|
+
* <TooltipContent>Bold</TooltipContent>
|
|
98
|
+
* </Tooltip>
|
|
99
|
+
*
|
|
100
|
+
* <Tooltip>
|
|
101
|
+
* <TooltipTrigger>
|
|
102
|
+
* <Button buttonSize="s" leftIcon={<ItalicIcon />} />
|
|
103
|
+
* </TooltipTrigger>
|
|
104
|
+
* <TooltipContent>Italic</TooltipContent>
|
|
105
|
+
* </Tooltip>
|
|
106
|
+
*
|
|
107
|
+
* <Tooltip>
|
|
108
|
+
* <TooltipTrigger>
|
|
109
|
+
* <Button buttonSize="s" leftIcon={<UnderlineIcon />} />
|
|
110
|
+
* </TooltipTrigger>
|
|
111
|
+
* <TooltipContent>Underline</TooltipContent>
|
|
112
|
+
* </Tooltip>
|
|
113
|
+
* </div>
|
|
114
|
+
* </TooltipDelayGroup>
|
|
115
|
+
* </Canvas>;
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
export function TooltipDelayGroup({
|
|
119
|
+
children,
|
|
120
|
+
delay = 1000,
|
|
121
|
+
timeoutMs = 0,
|
|
122
|
+
}: TooltipDelayGroupProps) {
|
|
123
|
+
return (
|
|
124
|
+
<FloatingDelayGroup delay={delay} timeoutMs={timeoutMs}>
|
|
125
|
+
{children}
|
|
126
|
+
</FloatingDelayGroup>
|
|
127
|
+
);
|
|
128
|
+
}
|