|
1 |
| -import { useState, useRef, useEffect } from 'react' |
2 |
| -import { createPortal } from 'react-dom' |
3 | 1 | import { useSegmentTree, type SegmentTrieNode } from '../../segment-explorer'
|
4 | 2 | import { css } from '../../utils/css'
|
5 | 3 | import { cx } from '../../utils/cx'
|
| 4 | +import { |
| 5 | + Tooltip, |
| 6 | + styles as tooltipStyles, |
| 7 | +} from '../../../userspace/components/tooltip' |
6 | 8 |
|
7 | 9 | const BUILTIN_PREFIX = '__next_builtin__'
|
8 | 10 |
|
@@ -138,12 +140,12 @@ function PageSegmentTreeLayerPresentation({
|
138 | 140 | >
|
139 | 141 | {fileName}
|
140 | 142 | {isBuiltin && (
|
141 |
| - <TooltipSpan |
| 143 | + <Tooltip |
142 | 144 | direction="right"
|
143 | 145 | title={`The default Next.js not found is being shown. You can customize this page by adding your own ${fileName} file to the app/ directory.`}
|
144 | 146 | >
|
145 | 147 | <InfoIcon />
|
146 |
| - </TooltipSpan> |
| 148 | + </Tooltip> |
147 | 149 | )}
|
148 | 150 | </span>
|
149 | 151 | )
|
@@ -179,102 +181,6 @@ function PageSegmentTreeLayerPresentation({
|
179 | 181 | )
|
180 | 182 | }
|
181 | 183 |
|
182 |
| -const tooltipStyles = ` |
183 |
| - .tooltip-wrapper { |
184 |
| - position: relative; |
185 |
| - display: inline-block; |
186 |
| - } |
187 |
| -
|
188 |
| - .tooltip { |
189 |
| - position: absolute; |
190 |
| - background: var(--color-gray-1000); |
191 |
| - color: var(--color-gray-100); |
192 |
| - padding: 6px 12px; |
193 |
| - border-radius: 8px; |
194 |
| - font-size: 14px; |
195 |
| - line-height: 1.4; |
196 |
| - white-space: nowrap; |
197 |
| - min-width: 200px; |
198 |
| - white-space: normal; |
199 |
| - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); |
200 |
| - pointer-events: none; |
201 |
| - } |
202 |
| -
|
203 |
| - .tooltip-arrow { |
204 |
| - position: absolute; |
205 |
| - width: 0; |
206 |
| - height: 0; |
207 |
| - } |
208 |
| -
|
209 |
| - /* Top direction */ |
210 |
| - .tooltip--top { |
211 |
| - bottom: 100%; |
212 |
| - left: 50%; |
213 |
| - transform: translateX(-50%); |
214 |
| - margin-bottom: 8px; |
215 |
| - } |
216 |
| -
|
217 |
| - .tooltip-arrow--top { |
218 |
| - top: 100%; |
219 |
| - left: 50%; |
220 |
| - transform: translateX(-50%); |
221 |
| - border-left: 6px solid transparent; |
222 |
| - border-right: 6px solid transparent; |
223 |
| - border-top: 6px solid var(--color-gray-1000); |
224 |
| - } |
225 |
| -
|
226 |
| - /* Bottom direction */ |
227 |
| - .tooltip--bottom { |
228 |
| - top: 100%; |
229 |
| - left: 50%; |
230 |
| - transform: translateX(-50%); |
231 |
| - margin-top: 8px; |
232 |
| - } |
233 |
| -
|
234 |
| - .tooltip-arrow--bottom { |
235 |
| - bottom: 100%; |
236 |
| - left: 50%; |
237 |
| - transform: translateX(-50%); |
238 |
| - border-left: 6px solid transparent; |
239 |
| - border-right: 6px solid transparent; |
240 |
| - border-bottom: 6px solid var(--color-gray-1000); |
241 |
| - } |
242 |
| -
|
243 |
| - /* Left direction */ |
244 |
| - .tooltip--left { |
245 |
| - right: 100%; |
246 |
| - top: 50%; |
247 |
| - transform: translateY(-50%); |
248 |
| - margin-right: 8px; |
249 |
| - } |
250 |
| -
|
251 |
| - .tooltip-arrow--left { |
252 |
| - left: 100%; |
253 |
| - top: 50%; |
254 |
| - transform: translateY(-50%); |
255 |
| - border-top: 6px solid transparent; |
256 |
| - border-bottom: 6px solid transparent; |
257 |
| - border-left: 6px solid var(--color-gray-1000); |
258 |
| - } |
259 |
| -
|
260 |
| - /* Right direction */ |
261 |
| - .tooltip--right { |
262 |
| - left: 100%; |
263 |
| - top: 50%; |
264 |
| - transform: translateY(-50%); |
265 |
| - margin-left: 8px; |
266 |
| - } |
267 |
| -
|
268 |
| - .tooltip-arrow--right { |
269 |
| - right: 100%; |
270 |
| - top: 50%; |
271 |
| - transform: translateY(-50%); |
272 |
| - border-top: 6px solid transparent; |
273 |
| - border-bottom: 6px solid transparent; |
274 |
| - border-right: 6px solid var(--color-gray-1000); |
275 |
| - } |
276 |
| -` |
277 |
| - |
278 | 184 | export const DEV_TOOLS_INFO_RENDER_FILES_STYLES = css`
|
279 | 185 | .segment-explorer-content {
|
280 | 186 | font-size: var(--size-14);
|
@@ -414,89 +320,3 @@ function InfoIcon(props: React.SVGProps<SVGSVGElement>) {
|
414 | 320 | </svg>
|
415 | 321 | )
|
416 | 322 | }
|
417 |
| - |
418 |
| -type TooltipDirection = 'top' | 'bottom' | 'left' | 'right' |
419 |
| - |
420 |
| -function TooltipSpan({ |
421 |
| - children, |
422 |
| - title, |
423 |
| - direction = 'top', |
424 |
| -}: { |
425 |
| - children: React.ReactNode |
426 |
| - title: string |
427 |
| - direction: TooltipDirection |
428 |
| -}) { |
429 |
| - const [isVisible, setIsVisible] = useState(false) |
430 |
| - const [position, setPosition] = useState({ top: 0, left: 0 }) |
431 |
| - const wrapperRef = useRef<HTMLSpanElement>(null) |
432 |
| - |
433 |
| - useEffect(() => { |
434 |
| - if (isVisible && wrapperRef.current) { |
435 |
| - const rect = wrapperRef.current.getBoundingClientRect() |
436 |
| - const scrollTop = window.scrollY || document.documentElement.scrollTop |
437 |
| - const scrollLeft = window.scrollX || document.documentElement.scrollLeft |
438 |
| - |
439 |
| - setPosition({ |
440 |
| - top: rect.top + scrollTop, |
441 |
| - left: rect.left + scrollLeft, |
442 |
| - }) |
443 |
| - } |
444 |
| - }, [isVisible]) |
445 |
| - |
446 |
| - const handleMouseEnter = () => { |
447 |
| - setIsVisible(true) |
448 |
| - } |
449 |
| - |
450 |
| - const handleMouseLeave = () => { |
451 |
| - setIsVisible(false) |
452 |
| - } |
453 |
| - |
454 |
| - const tooltip = isVisible ? ( |
455 |
| - <div |
456 |
| - className="custom-tooltip-portal" |
457 |
| - style={{ |
458 |
| - position: 'absolute', |
459 |
| - top: position.top, |
460 |
| - left: position.left, |
461 |
| - width: wrapperRef.current?.offsetWidth || 0, |
462 |
| - height: wrapperRef.current?.offsetHeight || 0, |
463 |
| - pointerEvents: 'none', |
464 |
| - zIndex: 99999, |
465 |
| - }} |
466 |
| - > |
467 |
| - <div className={cx('custom-tooltip', `custom-tooltip--${direction}`)}> |
468 |
| - {title} |
469 |
| - <div |
470 |
| - className={cx( |
471 |
| - 'custom-tooltip-arrow', |
472 |
| - `custom-tooltip-arrow--${direction}` |
473 |
| - )} |
474 |
| - /> |
475 |
| - </div> |
476 |
| - </div> |
477 |
| - ) : null |
478 |
| - |
479 |
| - const [shadowRootRef] = useState<ShadowRoot | null>(() => { |
480 |
| - const portal = document.querySelector('nextjs-portal') |
481 |
| - if (!portal) return null |
482 |
| - return portal.shadowRoot as ShadowRoot |
483 |
| - }) |
484 |
| - |
485 |
| - if (!shadowRootRef) return null |
486 |
| - |
487 |
| - return ( |
488 |
| - <> |
489 |
| - <span |
490 |
| - ref={wrapperRef} |
491 |
| - className="tooltip-wrapper" |
492 |
| - onMouseEnter={handleMouseEnter} |
493 |
| - onMouseLeave={handleMouseLeave} |
494 |
| - > |
495 |
| - {children} |
496 |
| - </span> |
497 |
| - {typeof document !== 'undefined' && |
498 |
| - tooltip && |
499 |
| - createPortal(tooltip, shadowRootRef)} |
500 |
| - </> |
501 |
| - ) |
502 |
| -} |
0 commit comments