@redocly/theme 0.63.0-next.3 → 0.63.0-next.4

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.
Files changed (36) hide show
  1. package/lib/components/Buttons/CopyButton.d.ts +1 -1
  2. package/lib/components/Buttons/CopyButton.js +1 -1
  3. package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.js +2 -2
  4. package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityVersionItem.js +6 -13
  5. package/lib/components/CodeBlock/CodeBlockControls.js +3 -3
  6. package/lib/components/PageActions/PageActions.js +1 -1
  7. package/lib/components/SidebarActions/styled.d.ts +1 -1
  8. package/lib/components/Tooltip/AnchorTooltip.d.ts +7 -0
  9. package/lib/components/Tooltip/AnchorTooltip.js +230 -0
  10. package/lib/components/Tooltip/JsTooltip.d.ts +3 -0
  11. package/lib/components/Tooltip/JsTooltip.js +274 -0
  12. package/lib/components/Tooltip/Tooltip.d.ts +2 -13
  13. package/lib/components/Tooltip/Tooltip.js +14 -190
  14. package/lib/core/hooks/use-page-actions.js +9 -5
  15. package/lib/core/hooks/use-theme-hooks.js +1 -0
  16. package/lib/core/types/hooks.d.ts +3 -0
  17. package/lib/core/types/index.d.ts +1 -0
  18. package/lib/core/types/tooltip.d.ts +13 -0
  19. package/lib/core/types/tooltip.js +3 -0
  20. package/lib/core/utils/transform-revisions-to-version-history.js +13 -20
  21. package/package.json +3 -3
  22. package/src/components/Buttons/CopyButton.tsx +2 -1
  23. package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.tsx +2 -2
  24. package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityVersionItem.tsx +5 -21
  25. package/src/components/CodeBlock/CodeBlockControls.tsx +3 -0
  26. package/src/components/PageActions/PageActions.tsx +6 -1
  27. package/src/components/Tooltip/AnchorTooltip.tsx +255 -0
  28. package/src/components/Tooltip/JsTooltip.tsx +292 -0
  29. package/src/components/Tooltip/Tooltip.tsx +18 -257
  30. package/src/core/hooks/__mocks__/use-theme-hooks.ts +3 -0
  31. package/src/core/hooks/use-page-actions.ts +5 -0
  32. package/src/core/hooks/use-theme-hooks.ts +1 -0
  33. package/src/core/types/hooks.ts +1 -0
  34. package/src/core/types/index.ts +1 -0
  35. package/src/core/types/tooltip.ts +14 -0
  36. package/src/core/utils/transform-revisions-to-version-history.ts +13 -21
@@ -0,0 +1,255 @@
1
+ import React, { memo, useEffect, useRef, useId } from 'react';
2
+ import styled, { css } from 'styled-components';
3
+
4
+ import type { JSX, PropsWithChildren } from 'react';
5
+ import type { TooltipProps } from '@redocly/theme/core/types';
6
+
7
+ import { useControl, useOutsideClick } from '@redocly/theme/core/hooks';
8
+ import { Portal } from '@redocly/theme/components/Portal/Portal';
9
+
10
+ type Props = Exclude<TooltipProps, 'arrowPosition'> & {
11
+ arrowPosition?: 'left' | 'right' | 'center';
12
+ };
13
+
14
+ function TooltipComponent({
15
+ children,
16
+ isOpen,
17
+ tip,
18
+ withArrow = true,
19
+ placement = 'top',
20
+ className = 'default',
21
+ width,
22
+ dataTestId,
23
+ disabled = false,
24
+ arrowPosition = 'center',
25
+ }: PropsWithChildren<Props>): JSX.Element {
26
+ const tooltipWrapperRef = useRef<HTMLDivElement | null>(null);
27
+ const tooltipBodyRef = useRef<HTMLDivElement | null>(null);
28
+ const { isOpened, handleOpen, handleClose } = useControl(isOpen);
29
+ const anchorName = `--tooltip${useId().replace(/:/g, '')}`;
30
+
31
+ useOutsideClick(isOpened ? [tooltipWrapperRef, tooltipBodyRef] : tooltipWrapperRef, handleClose);
32
+
33
+ const isControlled = isOpen !== undefined;
34
+
35
+ useEffect(() => {
36
+ if (!isControlled) return;
37
+
38
+ if (isOpen && !disabled) {
39
+ handleOpen();
40
+ } else {
41
+ handleClose();
42
+ }
43
+ }, [isOpen, disabled, isControlled, handleOpen, handleClose]);
44
+
45
+ const controllers =
46
+ !isControlled && !disabled
47
+ ? {
48
+ onMouseEnter: handleOpen,
49
+ onMouseLeave: handleClose,
50
+ onClick: handleClose,
51
+ onFocus: handleOpen,
52
+ onBlur: handleClose,
53
+ }
54
+ : {};
55
+
56
+ return (
57
+ <TooltipWrapper
58
+ ref={tooltipWrapperRef}
59
+ {...controllers}
60
+ className={`tooltip-${className}`}
61
+ data-component-name="Tooltip/Tooltip"
62
+ anchorName={anchorName}
63
+ >
64
+ {children}
65
+ {isOpened && !disabled && (
66
+ <Portal>
67
+ <TooltipBody
68
+ ref={tooltipBodyRef}
69
+ data-testid={dataTestId || (typeof tip === 'string' ? tip : '')}
70
+ placement={placement}
71
+ width={width}
72
+ withArrow={withArrow}
73
+ arrowPosition={arrowPosition}
74
+ anchorName={anchorName}
75
+ >
76
+ {tip}
77
+ </TooltipBody>
78
+ </Portal>
79
+ )}
80
+ </TooltipWrapper>
81
+ );
82
+ }
83
+
84
+ export const Tooltip = memo<PropsWithChildren<Props>>(TooltipComponent);
85
+
86
+ const PLACEMENTS = {
87
+ top: css<Pick<Props, 'withArrow' | 'arrowPosition'>>`
88
+ bottom: anchor(top);
89
+ ${({ withArrow, arrowPosition }) =>
90
+ withArrow && arrowPosition === 'left'
91
+ ? css`
92
+ transform: translate(-32px, -6px);
93
+ left: anchor(center);
94
+ `
95
+ : arrowPosition === 'right'
96
+ ? css`
97
+ transform: translate(32px, -6px);
98
+ right: anchor(center);
99
+ `
100
+ : css`
101
+ transform: translate(-50%, -6px);
102
+ left: anchor(center);
103
+ `}
104
+
105
+ ${({ withArrow, arrowPosition }) =>
106
+ withArrow &&
107
+ css`
108
+ &::after {
109
+ border-left: 14px solid transparent;
110
+ border-right: 14px solid transparent;
111
+ border-top-width: 8px;
112
+ border-top-style: solid;
113
+ border-radius: 2px;
114
+ bottom: 0;
115
+ ${arrowPosition === 'left' && 'left: 16px; transform: translateY(100%);'}
116
+ ${arrowPosition === 'center' && 'left: 50%; transform: translate(-50%, 100%);'}
117
+ ${arrowPosition === 'right' && 'right: 16px; transform: translateY(100%);'}
118
+ }
119
+ `}
120
+ `,
121
+ bottom: css<Pick<Props, 'withArrow' | 'arrowPosition'>>`
122
+ top: anchor(bottom);
123
+ ${({ withArrow, arrowPosition }) =>
124
+ withArrow && arrowPosition === 'left'
125
+ ? css`
126
+ transform: translate(-32px, 6px);
127
+ left: anchor(center);
128
+ `
129
+ : arrowPosition === 'right'
130
+ ? css`
131
+ transform: translate(32px, 6px);
132
+ right: anchor(center);
133
+ `
134
+ : css`
135
+ transform: translate(-50%, 6px);
136
+ left: anchor(center);
137
+ `}
138
+
139
+ ${({ withArrow, arrowPosition }) =>
140
+ withArrow &&
141
+ css`
142
+ &::after {
143
+ border-left: 14px solid transparent;
144
+ border-right: 14px solid transparent;
145
+ border-bottom-width: 8px;
146
+ border-bottom-style: solid;
147
+ border-radius: 0 0 2px 2px;
148
+ top: 0;
149
+ ${arrowPosition === 'left' && 'left: 16px; transform: translateY(-100%);'}
150
+ ${arrowPosition === 'center' && 'left: 50%; transform: translate(-50%, -100%);'}
151
+ ${arrowPosition === 'right' && 'right: 16px; transform: translateY(-100%);'}
152
+ }
153
+ `}
154
+ `,
155
+ left: css<Pick<Props, 'withArrow' | 'arrowPosition'>>`
156
+ transform: translate(-100%, -50%);
157
+ margin-left: -7px;
158
+ top: anchor(center);
159
+ left: anchor(left);
160
+
161
+ ${({ withArrow }) =>
162
+ withArrow &&
163
+ css`
164
+ &::after {
165
+ border-top: 14px solid transparent;
166
+ border-bottom: 14px solid transparent;
167
+ border-left-width: 8px;
168
+ border-left-style: solid;
169
+ border-radius: 2px 0 0 2px;
170
+ right: -9px;
171
+ top: 50%;
172
+ transform: translateY(-50%);
173
+ }
174
+ `}
175
+ `,
176
+ right: css<Pick<Props, 'withArrow' | 'arrowPosition'>>`
177
+ transform: translate(0, -50%);
178
+ margin-left: 7px;
179
+ top: anchor(center);
180
+ left: anchor(right);
181
+
182
+ ${({ withArrow }) =>
183
+ withArrow &&
184
+ css`
185
+ &::after {
186
+ border-top: 14px solid transparent;
187
+ border-bottom: 14px solid transparent;
188
+ border-right-width: 8px;
189
+ border-right-style: solid;
190
+ border-radius: 0 2px 2px 0;
191
+ left: -9px;
192
+ top: 50%;
193
+ transform: translateY(-50%);
194
+ }
195
+ `}
196
+ `,
197
+ };
198
+
199
+ const TooltipWrapper = styled.div.attrs<{ anchorName: string }>(({ anchorName }) => ({
200
+ style: {
201
+ anchorName: anchorName,
202
+ } as React.CSSProperties,
203
+ }))<{ anchorName: string }>`
204
+ display: flex;
205
+ `;
206
+
207
+ const TooltipBody = styled.span.attrs<{ anchorName: string }>(({ anchorName }) => ({
208
+ style: {
209
+ positionAnchor: anchorName,
210
+ } as React.CSSProperties,
211
+ }))<
212
+ Pick<Required<Props>, 'placement' | 'withArrow' | 'arrowPosition'> & {
213
+ width?: string;
214
+ anchorName: string;
215
+ }
216
+ >`
217
+ position: fixed;
218
+ min-width: 64px;
219
+ padding: var(--tooltip-padding);
220
+ max-width: var(--tooltip-max-width);
221
+ white-space: normal;
222
+ word-break: normal;
223
+ overflow-wrap: break-word;
224
+ text-align: left;
225
+
226
+ border-radius: var(--border-radius-md);
227
+ transition: opacity 0.3s ease-out;
228
+
229
+ font-size: var(--font-size-base);
230
+ line-height: var(--line-height-base);
231
+
232
+ z-index: var(--z-index-overlay);
233
+
234
+ &::after {
235
+ position: absolute;
236
+
237
+ content: ' ';
238
+ display: inline-block;
239
+ width: 0;
240
+ height: 0;
241
+ border-color: var(--tooltip-arrow-color, var(--tooltip-bg-color));
242
+ }
243
+
244
+ background: var(--tooltip-bg-color);
245
+ color: var(--tooltip-text-color);
246
+ border: var(--tooltip-border-width, 0) var(--tooltip-border-style, solid)
247
+ var(--tooltip-border-color, transparent);
248
+ box-shadow: var(--bg-raised-shadow);
249
+
250
+ width: ${({ width }) => width || 'max-content'};
251
+
252
+ ${({ placement }) => css`
253
+ ${PLACEMENTS[placement]};
254
+ `}
255
+ `;
@@ -0,0 +1,292 @@
1
+ import React, { useEffect, memo, useRef, useState, useCallback, useLayoutEffect } from 'react';
2
+ import styled, { css } from 'styled-components';
3
+
4
+ import type { JSX, PropsWithChildren } from 'react';
5
+ import type { TooltipProps } from '@redocly/theme/core/types';
6
+
7
+ import { useControl, useOutsideClick } from '@redocly/theme/core/hooks';
8
+ import { Portal } from '@redocly/theme/components/Portal/Portal';
9
+
10
+ function TooltipComponent({
11
+ children,
12
+ isOpen,
13
+ tip,
14
+ withArrow = true,
15
+ placement = 'top',
16
+ className = 'default',
17
+ width,
18
+ dataTestId,
19
+ disabled = false,
20
+ arrowPosition = 'center',
21
+ }: PropsWithChildren<TooltipProps>): JSX.Element {
22
+ const ref = useRef<HTMLDivElement | null>(null);
23
+ const { isOpened, handleOpen, handleClose } = useControl(isOpen);
24
+ const [tooltipPosition, setTooltipPosition] = useState({ top: 0, left: 0 });
25
+
26
+ useOutsideClick(ref, handleClose);
27
+
28
+ const isControlled = isOpen !== undefined;
29
+
30
+ const updateTooltipPosition = useCallback(() => {
31
+ if (isOpened && ref.current) {
32
+ const rect = ref.current.getBoundingClientRect();
33
+
34
+ let top = 0;
35
+ let left = 0;
36
+
37
+ switch (placement) {
38
+ case 'top':
39
+ top = rect.top;
40
+ if (arrowPosition === 'left') {
41
+ left = rect.left - 24;
42
+ } else if (arrowPosition === 'right') {
43
+ left = rect.right + 24;
44
+ } else {
45
+ left = rect.left + rect.width / 2;
46
+ }
47
+ break;
48
+ case 'bottom':
49
+ top = rect.bottom;
50
+ if (arrowPosition === 'left') {
51
+ left = rect.left - 24;
52
+ } else if (arrowPosition === 'right') {
53
+ left = rect.right + 24;
54
+ } else {
55
+ left = rect.left + rect.width / 2;
56
+ }
57
+ break;
58
+ case 'left':
59
+ top = rect.top + rect.height / 2;
60
+ left = rect.left;
61
+ break;
62
+ case 'right':
63
+ top = rect.top + rect.height / 2;
64
+ left = rect.right;
65
+ break;
66
+ }
67
+
68
+ setTooltipPosition({ top, left });
69
+ }
70
+ }, [isOpened, placement, arrowPosition]);
71
+
72
+ useLayoutEffect(() => {
73
+ if (isOpened && ref.current) {
74
+ updateTooltipPosition();
75
+
76
+ const handleScroll = () => updateTooltipPosition();
77
+ const handleResize = () => updateTooltipPosition();
78
+
79
+ window.addEventListener('scroll', handleScroll, true);
80
+ window.addEventListener('resize', handleResize);
81
+
82
+ return () => {
83
+ window.removeEventListener('scroll', handleScroll, true);
84
+ window.removeEventListener('resize', handleResize);
85
+ };
86
+ }
87
+ }, [isOpened, placement, updateTooltipPosition]);
88
+
89
+ useEffect(() => {
90
+ if (isOpen && !disabled) {
91
+ handleOpen();
92
+ } else {
93
+ handleClose();
94
+ }
95
+ }, [isOpen, handleOpen, handleClose, disabled]);
96
+
97
+ const controllers = !isControlled &&
98
+ !disabled && {
99
+ onMouseEnter: handleOpen,
100
+ onMouseLeave: handleClose,
101
+ onClick: handleClose,
102
+ onFocus: handleOpen,
103
+ onBlur: handleClose,
104
+ };
105
+
106
+ return (
107
+ <TooltipWrapper
108
+ ref={ref}
109
+ {...controllers}
110
+ className={`tooltip-${className}`}
111
+ data-component-name="Tooltip/Tooltip"
112
+ >
113
+ {children}
114
+ {isOpened && !disabled && (
115
+ <Portal>
116
+ <TooltipBody
117
+ data-testid={dataTestId || (typeof tip === 'string' ? tip : '')}
118
+ placement={placement}
119
+ width={width}
120
+ withArrow={withArrow}
121
+ arrowPosition={arrowPosition}
122
+ style={{
123
+ position: 'fixed',
124
+ top: tooltipPosition.top,
125
+ left: tooltipPosition.left,
126
+ }}
127
+ >
128
+ {tip}
129
+ </TooltipBody>
130
+ </Portal>
131
+ )}
132
+ </TooltipWrapper>
133
+ );
134
+ }
135
+
136
+ export const Tooltip = memo<PropsWithChildren<TooltipProps>>(TooltipComponent);
137
+
138
+ const PLACEMENTS = {
139
+ top: css<Pick<TooltipProps, 'withArrow' | 'arrowPosition'>>`
140
+ ${({ withArrow, arrowPosition }) =>
141
+ withArrow && arrowPosition === 'left'
142
+ ? css`
143
+ transform: translate(0, -100%);
144
+ margin-top: -10px;
145
+ `
146
+ : arrowPosition === 'right'
147
+ ? css`
148
+ transform: translate(-100%, -100%);
149
+ margin-top: -10px;
150
+ `
151
+ : css`
152
+ transform: translate(-50%, -100%);
153
+ margin-top: -10px;
154
+ `}
155
+
156
+ ${({ withArrow, arrowPosition }) =>
157
+ withArrow &&
158
+ css`
159
+ &::after {
160
+ border-left: 14px solid transparent;
161
+ border-right: 14px solid transparent;
162
+ border-top-width: 8px;
163
+ border-top-style: solid;
164
+ border-radius: 2px;
165
+ bottom: 0;
166
+ ${arrowPosition === 'left' && 'left: 16px; transform: translateY(99%);'}
167
+ ${arrowPosition === 'center' && 'left: 50%; transform: translate(-50%, 99%);'}
168
+ ${arrowPosition === 'right' && 'right: 16px; transform: translateY(99%);'}
169
+ }
170
+ `}
171
+ `,
172
+ bottom: css<Pick<TooltipProps, 'withArrow' | 'arrowPosition'>>`
173
+ ${({ withArrow, arrowPosition }) =>
174
+ withArrow && arrowPosition === 'left'
175
+ ? css`
176
+ transform: translate(0, 10px);
177
+ margin-top: 0;
178
+ `
179
+ : arrowPosition === 'right'
180
+ ? css`
181
+ transform: translate(-100%, 10px);
182
+ margin-top: 0;
183
+ `
184
+ : css`
185
+ transform: translate(-50%, 10px);
186
+ margin-top: 0;
187
+ `}
188
+
189
+ ${({ withArrow, arrowPosition }) =>
190
+ withArrow &&
191
+ css`
192
+ &::after {
193
+ border-left: 14px solid transparent;
194
+ border-right: 14px solid transparent;
195
+ border-bottom-width: 8px;
196
+ border-bottom-style: solid;
197
+ border-radius: 0 0 2px 2px;
198
+ top: 0;
199
+ ${arrowPosition === 'left' && 'left: 16px; transform: translateY(-99%);'}
200
+ ${arrowPosition === 'center' && 'left: 50%; transform: translate(-50%, -99%);'}
201
+ ${arrowPosition === 'right' && 'right: 16px; transform: translateY(-99%);'}
202
+ }
203
+ `}
204
+ `,
205
+ left: css<Pick<TooltipProps, 'withArrow' | 'arrowPosition'>>`
206
+ transform: translate(-100%, -50%);
207
+ margin-left: -10px;
208
+
209
+ ${({ withArrow, arrowPosition }) =>
210
+ withArrow &&
211
+ css`
212
+ &::after {
213
+ border-top: 14px solid transparent;
214
+ border-bottom: 14px solid transparent;
215
+ border-left-width: 8px;
216
+ border-left-style: solid;
217
+ border-radius: 2px 0 0 2px;
218
+ top: 50%;
219
+ right: 0;
220
+ ${arrowPosition === 'top' && 'top: 16px; transform: translateX(99%);'}
221
+ ${arrowPosition === 'center' && 'top: 50%; transform: translate(99%, -50%);'}
222
+ ${arrowPosition === 'bottom' && 'bottom: 16px; transform: translateX(99%);'}
223
+ }
224
+ `}
225
+ `,
226
+ right: css<Pick<TooltipProps, 'withArrow' | 'arrowPosition'>>`
227
+ transform: translate(0, -50%);
228
+ margin-left: 10px;
229
+
230
+ ${({ withArrow, arrowPosition }) =>
231
+ withArrow &&
232
+ css`
233
+ &::after {
234
+ border-top: 14px solid transparent;
235
+ border-bottom: 14px solid transparent;
236
+ border-right-width: 8px;
237
+ border-right-style: solid;
238
+ border-radius: 0 2px 2px 0;
239
+ top: 50%;
240
+ left: 0;
241
+ ${arrowPosition === 'top' && 'top: 16px; transform: translateX(-99%);'}
242
+ ${arrowPosition === 'center' && 'top: 50%; transform: translate(-99%, -50%);'}
243
+ ${arrowPosition === 'bottom' && 'bottom: 16px; transform: translateX(-99%);'}
244
+ }
245
+ `}
246
+ `,
247
+ };
248
+
249
+ const TooltipWrapper = styled.div`
250
+ position: relative;
251
+ display: flex;
252
+ `;
253
+ const TooltipBody = styled.span<
254
+ Pick<Required<TooltipProps>, 'placement' | 'withArrow' | 'arrowPosition'> & { width?: string }
255
+ >`
256
+ display: inline-block;
257
+
258
+ padding: var(--tooltip-padding);
259
+ max-width: ${({ width }) => width || 'var(--tooltip-max-width)'};
260
+ white-space: normal;
261
+ word-break: normal;
262
+ overflow-wrap: break-word;
263
+
264
+ border-radius: var(--border-radius-md);
265
+ transition: opacity 0.3s ease-out;
266
+
267
+ font-size: var(--font-size-base);
268
+ line-height: var(--line-height-base);
269
+
270
+ z-index: var(--z-index-overlay);
271
+
272
+ &::after {
273
+ position: absolute;
274
+
275
+ content: ' ';
276
+ display: inline-block;
277
+ width: 0;
278
+ height: 0;
279
+ border-color: var(--tooltip-arrow-color, var(--tooltip-bg-color));
280
+ }
281
+
282
+ background: var(--tooltip-bg-color);
283
+ color: var(--tooltip-text-color);
284
+ border: var(--tooltip-border-width, 0) var(--tooltip-border-style, solid)
285
+ var(--tooltip-border-color, transparent);
286
+ box-shadow: var(--bg-raised-shadow);
287
+
288
+ width: ${({ width }) => width || 'auto'};
289
+ ${({ placement }) => css`
290
+ ${PLACEMENTS[placement]};
291
+ `}
292
+ `;