@tanstack/router-devtools 1.111.11 → 1.112.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 (48) hide show
  1. package/dist/cjs/AgeTicker.cjs +58 -0
  2. package/dist/cjs/AgeTicker.cjs.map +1 -0
  3. package/dist/cjs/AgeTicker.d.cts +5 -0
  4. package/dist/cjs/BaseTanStackRouterDevtoolsPanel.cjs +421 -0
  5. package/dist/cjs/BaseTanStackRouterDevtoolsPanel.cjs.map +1 -0
  6. package/dist/cjs/BaseTanStackRouterDevtoolsPanel.d.cts +3 -0
  7. package/dist/cjs/TanStackRouterDevtools.cjs +177 -0
  8. package/dist/cjs/TanStackRouterDevtools.cjs.map +1 -0
  9. package/dist/cjs/{devtools.d.cts → TanStackRouterDevtools.d.cts} +0 -31
  10. package/dist/cjs/TanStackRouterDevtoolsPanel.cjs +21 -0
  11. package/dist/cjs/TanStackRouterDevtoolsPanel.cjs.map +1 -0
  12. package/dist/cjs/TanStackRouterDevtoolsPanel.d.cts +33 -0
  13. package/dist/cjs/index.cjs +4 -3
  14. package/dist/cjs/index.cjs.map +1 -1
  15. package/dist/cjs/index.d.cts +2 -1
  16. package/dist/cjs/useStyles.cjs +570 -0
  17. package/dist/cjs/useStyles.cjs.map +1 -0
  18. package/dist/cjs/useStyles.d.cts +52 -0
  19. package/dist/esm/AgeTicker.d.ts +5 -0
  20. package/dist/esm/AgeTicker.js +58 -0
  21. package/dist/esm/AgeTicker.js.map +1 -0
  22. package/dist/esm/BaseTanStackRouterDevtoolsPanel.d.ts +3 -0
  23. package/dist/esm/BaseTanStackRouterDevtoolsPanel.js +421 -0
  24. package/dist/esm/BaseTanStackRouterDevtoolsPanel.js.map +1 -0
  25. package/dist/esm/{devtools.d.ts → TanStackRouterDevtools.d.ts} +0 -31
  26. package/dist/esm/TanStackRouterDevtools.js +177 -0
  27. package/dist/esm/TanStackRouterDevtools.js.map +1 -0
  28. package/dist/esm/TanStackRouterDevtoolsPanel.d.ts +33 -0
  29. package/dist/esm/TanStackRouterDevtoolsPanel.js +21 -0
  30. package/dist/esm/TanStackRouterDevtoolsPanel.js.map +1 -0
  31. package/dist/esm/index.d.ts +2 -1
  32. package/dist/esm/index.js +2 -1
  33. package/dist/esm/index.js.map +1 -1
  34. package/dist/esm/useStyles.d.ts +52 -0
  35. package/dist/esm/useStyles.js +553 -0
  36. package/dist/esm/useStyles.js.map +1 -0
  37. package/package.json +2 -2
  38. package/src/AgeTicker.tsx +73 -0
  39. package/src/BaseTanStackRouterDevtoolsPanel.tsx +488 -0
  40. package/src/TanStackRouterDevtools.tsx +250 -0
  41. package/src/TanStackRouterDevtoolsPanel.tsx +54 -0
  42. package/src/index.tsx +2 -1
  43. package/src/useStyles.tsx +589 -0
  44. package/dist/cjs/devtools.cjs +0 -1212
  45. package/dist/cjs/devtools.cjs.map +0 -1
  46. package/dist/esm/devtools.js +0 -1195
  47. package/dist/esm/devtools.js.map +0 -1
  48. package/src/devtools.tsx +0 -1443
package/src/devtools.tsx DELETED
@@ -1,1443 +0,0 @@
1
- import React from 'react'
2
- import {
3
- invariant,
4
- rootRouteId,
5
- trimPath,
6
- useRouter,
7
- useRouterState,
8
- } from '@tanstack/react-router'
9
-
10
- import * as goober from 'goober'
11
- import { clsx as cx } from 'clsx'
12
- import useLocalStorage from './useLocalStorage'
13
- import {
14
- getRouteStatusColor,
15
- getStatusColor,
16
- multiSortBy,
17
- useIsMounted,
18
- useSafeState,
19
- } from './utils'
20
- import Explorer from './Explorer'
21
- import { tokens } from './tokens'
22
- import { TanStackLogo } from './logo'
23
- import {
24
- DevtoolsOnCloseContext,
25
- ShadowDomTargetContext,
26
- useDevtoolsOnClose,
27
- } from './context'
28
- import type {
29
- AnyRootRoute,
30
- AnyRoute,
31
- AnyRouteMatch,
32
- AnyRouter,
33
- Route,
34
- } from '@tanstack/react-router'
35
-
36
- interface DevtoolsOptions {
37
- /**
38
- * Set this true if you want the dev tools to default to being open
39
- */
40
- initialIsOpen?: boolean
41
- /**
42
- * Use this to add props to the panel. For example, you can add className, style (merge and override default style), etc.
43
- */
44
- panelProps?: React.DetailedHTMLProps<
45
- React.HTMLAttributes<HTMLDivElement>,
46
- HTMLDivElement
47
- >
48
- /**
49
- * Use this to add props to the close button. For example, you can add className, style (merge and override default style), onClick (extend default handler), etc.
50
- */
51
- closeButtonProps?: React.DetailedHTMLProps<
52
- React.ButtonHTMLAttributes<HTMLButtonElement>,
53
- HTMLButtonElement
54
- >
55
- /**
56
- * Use this to add props to the toggle button. For example, you can add className, style (merge and override default style), onClick (extend default handler), etc.
57
- */
58
- toggleButtonProps?: React.DetailedHTMLProps<
59
- React.ButtonHTMLAttributes<HTMLButtonElement>,
60
- HTMLButtonElement
61
- >
62
- /**
63
- * The position of the TanStack Router logo to open and close the devtools panel.
64
- * Defaults to 'bottom-left'.
65
- */
66
- position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
67
- /**
68
- * Use this to render the devtools inside a different type of container element for a11y purposes.
69
- * Any string which corresponds to a valid intrinsic JSX element is allowed.
70
- * Defaults to 'footer'.
71
- */
72
- containerElement?: string | any
73
- /**
74
- * A boolean variable indicating if the "lite" version of the library is being used
75
- */
76
- router?: AnyRouter
77
- /**
78
- * Use this to attach the devtool's styles to specific element in the DOM.
79
- */
80
- shadowDOMTarget?: ShadowRoot
81
- }
82
-
83
- interface DevtoolsPanelOptions {
84
- /**
85
- * The standard React style object used to style a component with inline styles
86
- */
87
- style?: React.CSSProperties
88
- /**
89
- * The standard React className property used to style a component with classes
90
- */
91
- className?: string
92
- /**
93
- * A boolean variable indicating whether the panel is open or closed
94
- */
95
- isOpen?: boolean
96
- /**
97
- * A function that toggles the open and close state of the panel
98
- */
99
- setIsOpen: (isOpen: boolean) => void
100
- /**
101
- * Handles the opening and closing the devtools panel
102
- */
103
- handleDragStart?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void
104
- /**
105
- * A boolean variable indicating if the "lite" version of the library is being used
106
- */
107
- router?: AnyRouter
108
- /**
109
- * Use this to attach the devtool's styles to specific element in the DOM.
110
- */
111
- shadowDOMTarget?: ShadowRoot
112
- }
113
-
114
- function Logo(props: React.HTMLAttributes<HTMLButtonElement>) {
115
- const { className, ...rest } = props
116
- const styles = useStyles()
117
- return (
118
- <button {...rest} className={cx(styles.logo, className)}>
119
- <div className={styles.tanstackLogo}>TANSTACK</div>
120
- <div className={styles.routerLogo}>React Router v1</div>
121
- </button>
122
- )
123
- }
124
-
125
- export function TanStackRouterDevtools(
126
- props: DevtoolsOptions,
127
- ): React.ReactElement | null {
128
- const { shadowDOMTarget } = props
129
-
130
- return (
131
- <ShadowDomTargetContext.Provider value={shadowDOMTarget}>
132
- <FloatingTanStackRouterDevtools {...props} />
133
- </ShadowDomTargetContext.Provider>
134
- )
135
- }
136
-
137
- function FloatingTanStackRouterDevtools({
138
- initialIsOpen,
139
- panelProps = {},
140
- closeButtonProps = {},
141
- toggleButtonProps = {},
142
- position = 'bottom-left',
143
- containerElement: Container = 'footer',
144
- router,
145
- shadowDOMTarget,
146
- }: DevtoolsOptions): React.ReactElement | null {
147
- const [rootEl, setRootEl] = React.useState<HTMLDivElement>()
148
- const panelRef = React.useRef<HTMLDivElement>(null)
149
- const [isOpen, setIsOpen] = useLocalStorage(
150
- 'tanstackRouterDevtoolsOpen',
151
- initialIsOpen,
152
- )
153
- const [devtoolsHeight, setDevtoolsHeight] = useLocalStorage<number | null>(
154
- 'tanstackRouterDevtoolsHeight',
155
- null,
156
- )
157
- const [isResolvedOpen, setIsResolvedOpen] = useSafeState(false)
158
- const [isResizing, setIsResizing] = useSafeState(false)
159
- const isMounted = useIsMounted()
160
- const styles = useStyles()
161
-
162
- const handleDragStart = (
163
- panelElement: HTMLDivElement | null,
164
- startEvent: React.MouseEvent<HTMLDivElement, MouseEvent>,
165
- ) => {
166
- if (startEvent.button !== 0) return // Only allow left click for drag
167
-
168
- setIsResizing(true)
169
-
170
- const dragInfo = {
171
- originalHeight: panelElement?.getBoundingClientRect().height ?? 0,
172
- pageY: startEvent.pageY,
173
- }
174
-
175
- const run = (moveEvent: MouseEvent) => {
176
- const delta = dragInfo.pageY - moveEvent.pageY
177
- const newHeight = dragInfo.originalHeight + delta
178
-
179
- setDevtoolsHeight(newHeight)
180
-
181
- if (newHeight < 70) {
182
- setIsOpen(false)
183
- } else {
184
- setIsOpen(true)
185
- }
186
- }
187
-
188
- const unsub = () => {
189
- setIsResizing(false)
190
- document.removeEventListener('mousemove', run)
191
- document.removeEventListener('mouseUp', unsub)
192
- }
193
-
194
- document.addEventListener('mousemove', run)
195
- document.addEventListener('mouseup', unsub)
196
- }
197
-
198
- const isButtonClosed = isOpen ?? false
199
-
200
- React.useEffect(() => {
201
- setIsResolvedOpen(isOpen ?? false)
202
- }, [isOpen, isResolvedOpen, setIsResolvedOpen])
203
-
204
- React.useEffect(() => {
205
- if (isResolvedOpen) {
206
- const previousValue = rootEl?.parentElement?.style.paddingBottom
207
-
208
- const run = () => {
209
- const containerHeight = panelRef.current?.getBoundingClientRect().height
210
- if (rootEl?.parentElement) {
211
- rootEl.parentElement.style.paddingBottom = `${containerHeight}px`
212
- }
213
- }
214
-
215
- run()
216
-
217
- if (typeof window !== 'undefined') {
218
- window.addEventListener('resize', run)
219
-
220
- return () => {
221
- window.removeEventListener('resize', run)
222
- if (rootEl?.parentElement && typeof previousValue === 'string') {
223
- rootEl.parentElement.style.paddingBottom = previousValue
224
- }
225
- }
226
- }
227
- }
228
- return
229
- }, [isResolvedOpen, rootEl?.parentElement])
230
-
231
- React.useEffect(() => {
232
- if (rootEl) {
233
- const el = rootEl
234
- const fontSize = getComputedStyle(el).fontSize
235
- el.style.setProperty('--tsrd-font-size', fontSize)
236
- }
237
- }, [rootEl])
238
-
239
- const { style: panelStyle = {}, ...otherPanelProps } = panelProps
240
-
241
- const {
242
- style: closeButtonStyle = {},
243
- onClick: onCloseClick,
244
- ...otherCloseButtonProps
245
- } = closeButtonProps
246
-
247
- const {
248
- onClick: onToggleClick,
249
- className: toggleButtonClassName,
250
- ...otherToggleButtonProps
251
- } = toggleButtonProps
252
-
253
- // Do not render on the server
254
- if (!isMounted) return null
255
-
256
- const resolvedHeight = devtoolsHeight ?? 500
257
-
258
- return (
259
- <Container ref={setRootEl} className="TanStackRouterDevtools">
260
- <DevtoolsOnCloseContext.Provider
261
- value={{
262
- onCloseClick: onCloseClick ?? (() => {}),
263
- }}
264
- >
265
- <BaseTanStackRouterDevtoolsPanel
266
- ref={panelRef as any}
267
- {...otherPanelProps}
268
- router={router}
269
- className={cx(
270
- styles.devtoolsPanelContainer,
271
- styles.devtoolsPanelContainerVisibility(!!isOpen),
272
- styles.devtoolsPanelContainerResizing(isResizing),
273
- styles.devtoolsPanelContainerAnimation(
274
- isResolvedOpen,
275
- resolvedHeight + 16,
276
- ),
277
- )}
278
- style={{
279
- height: resolvedHeight,
280
- ...panelStyle,
281
- }}
282
- isOpen={isResolvedOpen}
283
- setIsOpen={setIsOpen}
284
- handleDragStart={(e) => handleDragStart(panelRef.current, e)}
285
- shadowDOMTarget={shadowDOMTarget}
286
- />
287
- </DevtoolsOnCloseContext.Provider>
288
-
289
- <button
290
- type="button"
291
- {...otherToggleButtonProps}
292
- aria-label="Open TanStack Router Devtools"
293
- onClick={(e) => {
294
- setIsOpen(true)
295
- onToggleClick && onToggleClick(e)
296
- }}
297
- className={cx(
298
- styles.mainCloseBtn,
299
- styles.mainCloseBtnPosition(position),
300
- styles.mainCloseBtnAnimation(!isButtonClosed),
301
- toggleButtonClassName,
302
- )}
303
- >
304
- <div className={styles.mainCloseBtnIconContainer}>
305
- <div className={styles.mainCloseBtnIconOuter}>
306
- <TanStackLogo />
307
- </div>
308
- <div className={styles.mainCloseBtnIconInner}>
309
- <TanStackLogo />
310
- </div>
311
- </div>
312
- <div className={styles.mainCloseBtnDivider}>-</div>
313
- <div className={styles.routerLogoCloseButton}>TanStack Router</div>
314
- </button>
315
- </Container>
316
- )
317
- }
318
-
319
- export const TanStackRouterDevtoolsPanel = React.forwardRef<
320
- HTMLDivElement,
321
- DevtoolsPanelOptions
322
- >(function TanStackRouterDevtoolsPanel(props, ref) {
323
- const { shadowDOMTarget } = props
324
-
325
- return (
326
- <ShadowDomTargetContext.Provider value={shadowDOMTarget}>
327
- <DevtoolsOnCloseContext.Provider
328
- value={{
329
- onCloseClick: () => {},
330
- }}
331
- >
332
- <BaseTanStackRouterDevtoolsPanel ref={ref} {...props} />
333
- </DevtoolsOnCloseContext.Provider>
334
- </ShadowDomTargetContext.Provider>
335
- )
336
- })
337
-
338
- function RouteComp({
339
- router,
340
- route,
341
- isRoot,
342
- activeId,
343
- setActiveId,
344
- }: {
345
- router: AnyRouter
346
- route: AnyRootRoute | AnyRoute
347
- isRoot?: boolean
348
- activeId: string | undefined
349
- setActiveId: (id: string) => void
350
- }) {
351
- const routerState = useRouterState({
352
- router,
353
- } as any)
354
- const styles = useStyles()
355
- const matches = routerState.pendingMatches || routerState.matches
356
- const match = routerState.matches.find((d) => d.routeId === route.id)
357
-
358
- const param = React.useMemo(() => {
359
- try {
360
- if (match?.params) {
361
- const p = match.params
362
- const r: string = route.path || trimPath(route.id)
363
- if (r.startsWith('$')) {
364
- const trimmed = r.slice(1)
365
- if (p[trimmed]) {
366
- return `(${p[trimmed]})`
367
- }
368
- }
369
- }
370
- return ''
371
- } catch (error) {
372
- return ''
373
- }
374
- }, [match, route])
375
-
376
- return (
377
- <div>
378
- <div
379
- role="button"
380
- aria-label={`Open match details for ${route.id}`}
381
- onClick={() => {
382
- if (match) {
383
- setActiveId(activeId === route.id ? '' : route.id)
384
- }
385
- }}
386
- className={cx(
387
- styles.routesRowContainer(route.id === activeId, !!match),
388
- )}
389
- >
390
- <div
391
- className={cx(
392
- styles.matchIndicator(getRouteStatusColor(matches, route)),
393
- )}
394
- />
395
- <div className={cx(styles.routesRow(!!match))}>
396
- <div>
397
- <code className={styles.code}>
398
- {isRoot ? rootRouteId : route.path || trimPath(route.id)}{' '}
399
- </code>
400
- <code className={styles.routeParamInfo}>{param}</code>
401
- </div>
402
- <AgeTicker match={match} router={router} />
403
- </div>
404
- </div>
405
- {route.children?.length ? (
406
- <div className={styles.nestedRouteRow(!!isRoot)}>
407
- {[...(route.children as Array<Route>)]
408
- .sort((a, b) => {
409
- return a.rank - b.rank
410
- })
411
- .map((r) => (
412
- <RouteComp
413
- key={r.id}
414
- router={router}
415
- route={r}
416
- activeId={activeId}
417
- setActiveId={setActiveId}
418
- />
419
- ))}
420
- </div>
421
- ) : null}
422
- </div>
423
- )
424
- }
425
-
426
- const BaseTanStackRouterDevtoolsPanel = React.forwardRef<
427
- HTMLDivElement,
428
- DevtoolsPanelOptions
429
- >(function BaseTanStackRouterDevtoolsPanel(props, ref): React.ReactElement {
430
- const {
431
- isOpen = true,
432
- setIsOpen,
433
- handleDragStart,
434
- router: userRouter,
435
- shadowDOMTarget,
436
- ...panelProps
437
- } = props
438
-
439
- const { onCloseClick } = useDevtoolsOnClose()
440
- const styles = useStyles()
441
- const { className, ...otherPanelProps } = panelProps
442
-
443
- const contextRouter = useRouter({ warn: false })
444
- const router = userRouter ?? contextRouter
445
- const routerState = useRouterState({
446
- router,
447
- } as any)
448
-
449
- invariant(
450
- router,
451
- 'No router was found for the TanStack Router Devtools. Please place the devtools in the <RouterProvider> component tree or pass the router instance to the devtools manually.',
452
- )
453
-
454
- // useStore(router.__store)
455
-
456
- const [showMatches, setShowMatches] = useLocalStorage(
457
- 'tanstackRouterDevtoolsShowMatches',
458
- true,
459
- )
460
-
461
- const [activeId, setActiveId] = useLocalStorage(
462
- 'tanstackRouterDevtoolsActiveRouteId',
463
- '',
464
- )
465
-
466
- const activeMatch = React.useMemo(() => {
467
- const matches = [
468
- ...(routerState.pendingMatches ?? []),
469
- ...routerState.matches,
470
- ...routerState.cachedMatches,
471
- ]
472
- return matches.find((d) => d.routeId === activeId || d.id === activeId)
473
- }, [
474
- activeId,
475
- routerState.cachedMatches,
476
- routerState.matches,
477
- routerState.pendingMatches,
478
- ])
479
-
480
- const hasSearch = Object.keys(routerState.location.search).length
481
-
482
- const explorerState = {
483
- ...router,
484
- state: router.state,
485
- }
486
-
487
- return (
488
- <div
489
- ref={ref}
490
- className={cx(
491
- styles.devtoolsPanel,
492
- 'TanStackRouterDevtoolsPanel',
493
- className,
494
- )}
495
- {...otherPanelProps}
496
- >
497
- {handleDragStart ? (
498
- <div className={styles.dragHandle} onMouseDown={handleDragStart}></div>
499
- ) : null}
500
- <button
501
- className={styles.panelCloseBtn}
502
- onClick={(e) => {
503
- setIsOpen(false)
504
- onCloseClick(e)
505
- }}
506
- >
507
- <svg
508
- xmlns="http://www.w3.org/2000/svg"
509
- width="10"
510
- height="6"
511
- fill="none"
512
- viewBox="0 0 10 6"
513
- className={styles.panelCloseBtnIcon}
514
- >
515
- <path
516
- stroke="currentColor"
517
- strokeLinecap="round"
518
- strokeLinejoin="round"
519
- strokeWidth="1.667"
520
- d="M1 1l4 4 4-4"
521
- ></path>
522
- </svg>
523
- </button>
524
- <div className={styles.firstContainer}>
525
- <div className={styles.row}>
526
- <Logo
527
- aria-hidden
528
- onClick={(e) => {
529
- setIsOpen(false)
530
- onCloseClick(e)
531
- }}
532
- />
533
- </div>
534
- <div className={styles.routerExplorerContainer}>
535
- <div className={styles.routerExplorer}>
536
- <Explorer
537
- label="Router"
538
- value={Object.fromEntries(
539
- multiSortBy(
540
- Object.keys(explorerState),
541
- (
542
- [
543
- 'state',
544
- 'routesById',
545
- 'routesByPath',
546
- 'flatRoutes',
547
- 'options',
548
- 'manifest',
549
- ] as const
550
- ).map((d) => (dd) => dd !== d),
551
- )
552
- .map((key) => [key, (explorerState as any)[key]])
553
- .filter(
554
- (d) =>
555
- typeof d[1] !== 'function' &&
556
- ![
557
- '__store',
558
- 'basepath',
559
- 'injectedHtml',
560
- 'subscribers',
561
- 'latestLoadPromise',
562
- 'navigateTimeout',
563
- 'resetNextScroll',
564
- 'tempLocationKey',
565
- 'latestLocation',
566
- 'routeTree',
567
- 'history',
568
- ].includes(d[0]),
569
- ),
570
- )}
571
- defaultExpanded={{
572
- state: {} as any,
573
- context: {} as any,
574
- options: {} as any,
575
- }}
576
- filterSubEntries={(subEntries) => {
577
- return subEntries.filter((d) => typeof d.value !== 'function')
578
- }}
579
- />
580
- </div>
581
- </div>
582
- </div>
583
- <div className={styles.secondContainer}>
584
- <div className={styles.matchesContainer}>
585
- <div className={styles.detailsHeader}>
586
- <span>Pathname</span>
587
- {routerState.location.maskedLocation ? (
588
- <div className={styles.maskedBadgeContainer}>
589
- <span className={styles.maskedBadge}>masked</span>
590
- </div>
591
- ) : null}
592
- </div>
593
- <div className={styles.detailsContent}>
594
- <code>{routerState.location.pathname}</code>
595
- {routerState.location.maskedLocation ? (
596
- <code className={styles.maskedLocation}>
597
- {routerState.location.maskedLocation.pathname}
598
- </code>
599
- ) : null}
600
- </div>
601
- <div className={styles.detailsHeader}>
602
- <div className={styles.routeMatchesToggle}>
603
- <button
604
- type="button"
605
- onClick={() => {
606
- setShowMatches(false)
607
- }}
608
- disabled={!showMatches}
609
- className={cx(styles.routeMatchesToggleBtn(!showMatches, true))}
610
- >
611
- Routes
612
- </button>
613
- <button
614
- type="button"
615
- onClick={() => {
616
- setShowMatches(true)
617
- }}
618
- disabled={showMatches}
619
- className={cx(
620
- styles.routeMatchesToggleBtn(!!showMatches, false),
621
- )}
622
- >
623
- Matches
624
- </button>
625
- </div>
626
- <div className={styles.detailsHeaderInfo}>
627
- <div>age / staleTime / gcTime</div>
628
- </div>
629
- </div>
630
- <div className={cx(styles.routesContainer)}>
631
- {!showMatches ? (
632
- <RouteComp
633
- router={router}
634
- route={router.routeTree}
635
- isRoot
636
- activeId={activeId}
637
- setActiveId={setActiveId}
638
- />
639
- ) : (
640
- <div>
641
- {(routerState.pendingMatches?.length
642
- ? routerState.pendingMatches
643
- : routerState.matches
644
- ).map((match, i) => {
645
- return (
646
- <div
647
- key={match.id || i}
648
- role="button"
649
- aria-label={`Open match details for ${match.id}`}
650
- onClick={() =>
651
- setActiveId(activeId === match.id ? '' : match.id)
652
- }
653
- className={cx(styles.matchRow(match === activeMatch))}
654
- >
655
- <div
656
- className={cx(
657
- styles.matchIndicator(getStatusColor(match)),
658
- )}
659
- />
660
-
661
- <code
662
- className={styles.matchID}
663
- >{`${match.routeId === rootRouteId ? rootRouteId : match.pathname}`}</code>
664
- <AgeTicker match={match} router={router} />
665
- </div>
666
- )
667
- })}
668
- </div>
669
- )}
670
- </div>
671
- </div>
672
- {routerState.cachedMatches.length ? (
673
- <div className={styles.cachedMatchesContainer}>
674
- <div className={styles.detailsHeader}>
675
- <div>Cached Matches</div>
676
- <div className={styles.detailsHeaderInfo}>
677
- age / staleTime / gcTime
678
- </div>
679
- </div>
680
- <div>
681
- {routerState.cachedMatches.map((match) => {
682
- return (
683
- <div
684
- key={match.id}
685
- role="button"
686
- aria-label={`Open match details for ${match.id}`}
687
- onClick={() =>
688
- setActiveId(activeId === match.id ? '' : match.id)
689
- }
690
- className={cx(styles.matchRow(match === activeMatch))}
691
- >
692
- <div
693
- className={cx(
694
- styles.matchIndicator(getStatusColor(match)),
695
- )}
696
- />
697
-
698
- <code className={styles.matchID}>{`${match.id}`}</code>
699
-
700
- <AgeTicker match={match} router={router} />
701
- </div>
702
- )
703
- })}
704
- </div>
705
- </div>
706
- ) : null}
707
- </div>
708
- {activeMatch ? (
709
- <div className={styles.thirdContainer}>
710
- <div className={styles.detailsHeader}>Match Details</div>
711
- <div>
712
- <div className={styles.matchDetails}>
713
- <div
714
- className={styles.matchStatus(
715
- activeMatch.status,
716
- activeMatch.isFetching,
717
- )}
718
- >
719
- <div>
720
- {activeMatch.status === 'success' && activeMatch.isFetching
721
- ? 'fetching'
722
- : activeMatch.status}
723
- </div>
724
- </div>
725
- <div className={styles.matchDetailsInfoLabel}>
726
- <div>ID:</div>
727
- <div className={styles.matchDetailsInfo}>
728
- <code>{activeMatch.id}</code>
729
- </div>
730
- </div>
731
- <div className={styles.matchDetailsInfoLabel}>
732
- <div>State:</div>
733
- <div className={styles.matchDetailsInfo}>
734
- {routerState.pendingMatches?.find(
735
- (d) => d.id === activeMatch.id,
736
- )
737
- ? 'Pending'
738
- : routerState.matches.find((d) => d.id === activeMatch.id)
739
- ? 'Active'
740
- : 'Cached'}
741
- </div>
742
- </div>
743
- <div className={styles.matchDetailsInfoLabel}>
744
- <div>Last Updated:</div>
745
- <div className={styles.matchDetailsInfo}>
746
- {activeMatch.updatedAt
747
- ? new Date(activeMatch.updatedAt).toLocaleTimeString()
748
- : 'N/A'}
749
- </div>
750
- </div>
751
- </div>
752
- </div>
753
- {activeMatch.loaderData ? (
754
- <>
755
- <div className={styles.detailsHeader}>Loader Data</div>
756
- <div className={styles.detailsContent}>
757
- <Explorer
758
- label="loaderData"
759
- value={activeMatch.loaderData}
760
- defaultExpanded={{}}
761
- />
762
- </div>
763
- </>
764
- ) : null}
765
- <div className={styles.detailsHeader}>Explorer</div>
766
- <div className={styles.detailsContent}>
767
- <Explorer label="Match" value={activeMatch} defaultExpanded={{}} />
768
- </div>
769
- </div>
770
- ) : null}
771
- {hasSearch ? (
772
- <div className={styles.fourthContainer}>
773
- <div className={styles.detailsHeader}>Search Params</div>
774
- <div className={styles.detailsContent}>
775
- <Explorer
776
- value={routerState.location.search}
777
- defaultExpanded={Object.keys(routerState.location.search).reduce(
778
- (obj: any, next) => {
779
- obj[next] = {}
780
- return obj
781
- },
782
- {},
783
- )}
784
- />
785
- </div>
786
- </div>
787
- ) : null}
788
- </div>
789
- )
790
- })
791
-
792
- function AgeTicker({
793
- match,
794
- router,
795
- }: {
796
- match?: AnyRouteMatch
797
- router: AnyRouter
798
- }) {
799
- const styles = useStyles()
800
- const rerender = React.useReducer(
801
- () => ({}),
802
- () => ({}),
803
- )[1]
804
-
805
- React.useEffect(() => {
806
- const interval = setInterval(() => {
807
- rerender()
808
- }, 1000)
809
-
810
- return () => {
811
- clearInterval(interval)
812
- }
813
- }, [rerender])
814
-
815
- if (!match) {
816
- return null
817
- }
818
-
819
- const route = router.looseRoutesById[match.routeId]!
820
-
821
- if (!route.options.loader) {
822
- return null
823
- }
824
-
825
- const age = Date.now() - match.updatedAt
826
- const staleTime =
827
- route.options.staleTime ?? router.options.defaultStaleTime ?? 0
828
- const gcTime =
829
- route.options.gcTime ?? router.options.defaultGcTime ?? 30 * 60 * 1000
830
-
831
- return (
832
- <div className={cx(styles.ageTicker(age > staleTime))}>
833
- <div>{formatTime(age)}</div>
834
- <div>/</div>
835
- <div>{formatTime(staleTime)}</div>
836
- <div>/</div>
837
- <div>{formatTime(gcTime)}</div>
838
- </div>
839
- )
840
- }
841
-
842
- function formatTime(ms: number) {
843
- const units = ['s', 'min', 'h', 'd']
844
- const values = [ms / 1000, ms / 60000, ms / 3600000, ms / 86400000]
845
-
846
- let chosenUnitIndex = 0
847
- for (let i = 1; i < values.length; i++) {
848
- if (values[i]! < 1) break
849
- chosenUnitIndex = i
850
- }
851
-
852
- const formatter = new Intl.NumberFormat(navigator.language, {
853
- compactDisplay: 'short',
854
- notation: 'compact',
855
- maximumFractionDigits: 0,
856
- })
857
-
858
- return formatter.format(values[chosenUnitIndex]!) + units[chosenUnitIndex]
859
- }
860
-
861
- const stylesFactory = (shadowDOMTarget?: ShadowRoot) => {
862
- const { colors, font, size, alpha, shadow, border } = tokens
863
- const { fontFamily, lineHeight, size: fontSize } = font
864
- const css = shadowDOMTarget
865
- ? goober.css.bind({ target: shadowDOMTarget })
866
- : goober.css
867
-
868
- return {
869
- devtoolsPanelContainer: css`
870
- direction: ltr;
871
- position: fixed;
872
- bottom: 0;
873
- right: 0;
874
- z-index: 99999;
875
- width: 100%;
876
- max-height: 90%;
877
- border-top: 1px solid ${colors.gray[700]};
878
- transform-origin: top;
879
- `,
880
- devtoolsPanelContainerVisibility: (isOpen: boolean) => {
881
- return css`
882
- visibility: ${isOpen ? 'visible' : 'hidden'};
883
- `
884
- },
885
- devtoolsPanelContainerResizing: (isResizing: boolean) => {
886
- if (isResizing) {
887
- return css`
888
- transition: none;
889
- `
890
- }
891
-
892
- return css`
893
- transition: all 0.4s ease;
894
- `
895
- },
896
- devtoolsPanelContainerAnimation: (isOpen: boolean, height: number) => {
897
- if (isOpen) {
898
- return css`
899
- pointer-events: auto;
900
- transform: translateY(0);
901
- `
902
- }
903
- return css`
904
- pointer-events: none;
905
- transform: translateY(${height}px);
906
- `
907
- },
908
- logo: css`
909
- cursor: pointer;
910
- display: flex;
911
- flex-direction: column;
912
- background-color: transparent;
913
- border: none;
914
- font-family: ${fontFamily.sans};
915
- gap: ${tokens.size[0.5]};
916
- padding: 0px;
917
- &:hover {
918
- opacity: 0.7;
919
- }
920
- &:focus-visible {
921
- outline-offset: 4px;
922
- border-radius: ${border.radius.xs};
923
- outline: 2px solid ${colors.blue[800]};
924
- }
925
- `,
926
- tanstackLogo: css`
927
- font-size: ${font.size.md};
928
- font-weight: ${font.weight.bold};
929
- line-height: ${font.lineHeight.xs};
930
- white-space: nowrap;
931
- color: ${colors.gray[300]};
932
- `,
933
- routerLogo: css`
934
- font-weight: ${font.weight.semibold};
935
- font-size: ${font.size.xs};
936
- background: linear-gradient(to right, #84cc16, #10b981);
937
- background-clip: text;
938
- -webkit-background-clip: text;
939
- line-height: 1;
940
- -webkit-text-fill-color: transparent;
941
- white-space: nowrap;
942
- `,
943
- devtoolsPanel: css`
944
- display: flex;
945
- font-size: ${fontSize.sm};
946
- font-family: ${fontFamily.sans};
947
- background-color: ${colors.darkGray[700]};
948
- color: ${colors.gray[300]};
949
-
950
- @media (max-width: 700px) {
951
- flex-direction: column;
952
- }
953
- @media (max-width: 600px) {
954
- font-size: ${fontSize.xs};
955
- }
956
- `,
957
- dragHandle: css`
958
- position: absolute;
959
- left: 0;
960
- top: 0;
961
- width: 100%;
962
- height: 4px;
963
- cursor: row-resize;
964
- z-index: 100000;
965
- &:hover {
966
- background-color: ${colors.purple[400]}${alpha[90]};
967
- }
968
- `,
969
- firstContainer: css`
970
- flex: 1 1 500px;
971
- min-height: 40%;
972
- max-height: 100%;
973
- overflow: auto;
974
- border-right: 1px solid ${colors.gray[700]};
975
- display: flex;
976
- flex-direction: column;
977
- `,
978
- routerExplorerContainer: css`
979
- overflow-y: auto;
980
- flex: 1;
981
- `,
982
- routerExplorer: css`
983
- padding: ${tokens.size[2]};
984
- `,
985
- row: css`
986
- display: flex;
987
- align-items: center;
988
- padding: ${tokens.size[2]} ${tokens.size[2.5]};
989
- gap: ${tokens.size[2.5]};
990
- border-bottom: ${colors.darkGray[500]} 1px solid;
991
- align-items: center;
992
- `,
993
- detailsHeader: css`
994
- font-family: ui-sans-serif, Inter, system-ui, sans-serif, sans-serif;
995
- position: sticky;
996
- top: 0;
997
- z-index: 2;
998
- background-color: ${colors.darkGray[600]};
999
- padding: 0px ${tokens.size[2]};
1000
- font-weight: ${font.weight.medium};
1001
- font-size: ${font.size.xs};
1002
- min-height: ${tokens.size[8]};
1003
- line-height: ${font.lineHeight.xs};
1004
- text-align: left;
1005
- display: flex;
1006
- align-items: center;
1007
- `,
1008
- maskedBadge: css`
1009
- background: ${colors.yellow[900]}${alpha[70]};
1010
- color: ${colors.yellow[300]};
1011
- display: inline-block;
1012
- padding: ${tokens.size[0]} ${tokens.size[2.5]};
1013
- border-radius: ${border.radius.full};
1014
- font-size: ${font.size.xs};
1015
- font-weight: ${font.weight.normal};
1016
- border: 1px solid ${colors.yellow[300]};
1017
- `,
1018
- maskedLocation: css`
1019
- color: ${colors.yellow[300]};
1020
- `,
1021
- detailsContent: css`
1022
- padding: ${tokens.size[1.5]} ${tokens.size[2]};
1023
- display: flex;
1024
- align-items: center;
1025
- justify-content: space-between;
1026
- font-size: ${font.size.xs};
1027
- `,
1028
- routeMatchesToggle: css`
1029
- display: flex;
1030
- align-items: center;
1031
- border: 1px solid ${colors.gray[500]};
1032
- border-radius: ${border.radius.sm};
1033
- overflow: hidden;
1034
- `,
1035
- routeMatchesToggleBtn: (active: boolean, showBorder: boolean) => {
1036
- const base = css`
1037
- appearance: none;
1038
- border: none;
1039
- font-size: 12px;
1040
- padding: 4px 8px;
1041
- background: transparent;
1042
- cursor: pointer;
1043
- font-family: ${fontFamily.sans};
1044
- font-weight: ${font.weight.medium};
1045
- `
1046
- const classes = [base]
1047
-
1048
- if (active) {
1049
- const activeStyles = css`
1050
- background: ${colors.darkGray[400]};
1051
- color: ${colors.gray[300]};
1052
- `
1053
- classes.push(activeStyles)
1054
- } else {
1055
- const inactiveStyles = css`
1056
- color: ${colors.gray[500]};
1057
- background: ${colors.darkGray[800]}${alpha[20]};
1058
- `
1059
- classes.push(inactiveStyles)
1060
- }
1061
-
1062
- if (showBorder) {
1063
- classes.push(css`
1064
- border-right: 1px solid ${tokens.colors.gray[500]};
1065
- `)
1066
- }
1067
-
1068
- return classes
1069
- },
1070
- detailsHeaderInfo: css`
1071
- flex: 1;
1072
- justify-content: flex-end;
1073
- display: flex;
1074
- align-items: center;
1075
- font-weight: ${font.weight.normal};
1076
- color: ${colors.gray[400]};
1077
- `,
1078
- matchRow: (active: boolean) => {
1079
- const base = css`
1080
- display: flex;
1081
- border-bottom: 1px solid ${colors.darkGray[400]};
1082
- cursor: pointer;
1083
- align-items: center;
1084
- padding: ${size[1]} ${size[2]};
1085
- gap: ${size[2]};
1086
- font-size: ${fontSize.xs};
1087
- color: ${colors.gray[300]};
1088
- `
1089
- const classes = [base]
1090
-
1091
- if (active) {
1092
- const activeStyles = css`
1093
- background: ${colors.darkGray[500]};
1094
- `
1095
- classes.push(activeStyles)
1096
- }
1097
-
1098
- return classes
1099
- },
1100
- matchIndicator: (
1101
- color: 'green' | 'red' | 'yellow' | 'gray' | 'blue' | 'purple',
1102
- ) => {
1103
- const base = css`
1104
- flex: 0 0 auto;
1105
- width: ${size[3]};
1106
- height: ${size[3]};
1107
- background: ${colors[color][900]};
1108
- border: 1px solid ${colors[color][500]};
1109
- border-radius: ${border.radius.full};
1110
- transition: all 0.25s ease-out;
1111
- box-sizing: border-box;
1112
- `
1113
- const classes = [base]
1114
-
1115
- if (color === 'gray') {
1116
- const grayStyles = css`
1117
- background: ${colors.gray[700]};
1118
- border-color: ${colors.gray[400]};
1119
- `
1120
- classes.push(grayStyles)
1121
- }
1122
-
1123
- return classes
1124
- },
1125
- matchID: css`
1126
- flex: 1;
1127
- line-height: ${lineHeight['xs']};
1128
- `,
1129
- ageTicker: (showWarning: boolean) => {
1130
- const base = css`
1131
- display: flex;
1132
- gap: ${size[1]};
1133
- font-size: ${fontSize.xs};
1134
- color: ${colors.gray[400]};
1135
- font-variant-numeric: tabular-nums;
1136
- line-height: ${lineHeight['xs']};
1137
- `
1138
-
1139
- const classes = [base]
1140
-
1141
- if (showWarning) {
1142
- const warningStyles = css`
1143
- color: ${colors.yellow[400]};
1144
- `
1145
- classes.push(warningStyles)
1146
- }
1147
-
1148
- return classes
1149
- },
1150
- secondContainer: css`
1151
- flex: 1 1 500px;
1152
- min-height: 40%;
1153
- max-height: 100%;
1154
- overflow: auto;
1155
- border-right: 1px solid ${colors.gray[700]};
1156
- display: flex;
1157
- flex-direction: column;
1158
- `,
1159
- thirdContainer: css`
1160
- flex: 1 1 500px;
1161
- overflow: auto;
1162
- display: flex;
1163
- flex-direction: column;
1164
- height: 100%;
1165
- border-right: 1px solid ${colors.gray[700]};
1166
-
1167
- @media (max-width: 700px) {
1168
- border-top: 2px solid ${colors.gray[700]};
1169
- }
1170
- `,
1171
- fourthContainer: css`
1172
- flex: 1 1 500px;
1173
- min-height: 40%;
1174
- max-height: 100%;
1175
- overflow: auto;
1176
- display: flex;
1177
- flex-direction: column;
1178
- `,
1179
- routesContainer: css`
1180
- overflow-x: auto;
1181
- overflow-y: visible;
1182
- `,
1183
- routesRowContainer: (active: boolean, isMatch: boolean) => {
1184
- const base = css`
1185
- display: flex;
1186
- border-bottom: 1px solid ${colors.darkGray[400]};
1187
- align-items: center;
1188
- padding: ${size[1]} ${size[2]};
1189
- gap: ${size[2]};
1190
- font-size: ${fontSize.xs};
1191
- color: ${colors.gray[300]};
1192
- cursor: ${isMatch ? 'pointer' : 'default'};
1193
- line-height: ${lineHeight['xs']};
1194
- `
1195
- const classes = [base]
1196
-
1197
- if (active) {
1198
- const activeStyles = css`
1199
- background: ${colors.darkGray[500]};
1200
- `
1201
- classes.push(activeStyles)
1202
- }
1203
-
1204
- return classes
1205
- },
1206
- routesRow: (isMatch: boolean) => {
1207
- const base = css`
1208
- flex: 1 0 auto;
1209
- display: flex;
1210
- justify-content: space-between;
1211
- align-items: center;
1212
- font-size: ${fontSize.xs};
1213
- line-height: ${lineHeight['xs']};
1214
- `
1215
-
1216
- const classes = [base]
1217
-
1218
- if (!isMatch) {
1219
- const matchStyles = css`
1220
- color: ${colors.gray[400]};
1221
- `
1222
- classes.push(matchStyles)
1223
- }
1224
-
1225
- return classes
1226
- },
1227
- routeParamInfo: css`
1228
- color: ${colors.gray[400]};
1229
- font-size: ${fontSize.xs};
1230
- line-height: ${lineHeight['xs']};
1231
- `,
1232
- nestedRouteRow: (isRoot: boolean) => {
1233
- const base = css`
1234
- margin-left: ${isRoot ? 0 : size[3.5]};
1235
- border-left: ${isRoot ? '' : `solid 1px ${colors.gray[700]}`};
1236
- `
1237
- return base
1238
- },
1239
- code: css`
1240
- font-size: ${fontSize.xs};
1241
- line-height: ${lineHeight['xs']};
1242
- `,
1243
- matchesContainer: css`
1244
- flex: 1 1 auto;
1245
- overflow-y: auto;
1246
- `,
1247
- cachedMatchesContainer: css`
1248
- flex: 1 1 auto;
1249
- overflow-y: auto;
1250
- max-height: 50%;
1251
- `,
1252
- maskedBadgeContainer: css`
1253
- flex: 1;
1254
- justify-content: flex-end;
1255
- display: flex;
1256
- `,
1257
- matchDetails: css`
1258
- display: flex;
1259
- flex-direction: column;
1260
- padding: ${tokens.size[2]};
1261
- font-size: ${tokens.font.size.xs};
1262
- color: ${tokens.colors.gray[300]};
1263
- line-height: ${tokens.font.lineHeight.sm};
1264
- `,
1265
- matchStatus: (
1266
- status: 'pending' | 'success' | 'error' | 'notFound' | 'redirected',
1267
- isFetching: false | 'beforeLoad' | 'loader',
1268
- ) => {
1269
- const colorMap = {
1270
- pending: 'yellow',
1271
- success: 'green',
1272
- error: 'red',
1273
- notFound: 'purple',
1274
- redirected: 'gray',
1275
- } as const
1276
-
1277
- const color =
1278
- isFetching && status === 'success'
1279
- ? isFetching === 'beforeLoad'
1280
- ? 'purple'
1281
- : 'blue'
1282
- : colorMap[status]
1283
-
1284
- return css`
1285
- display: flex;
1286
- justify-content: center;
1287
- align-items: center;
1288
- height: 40px;
1289
- border-radius: ${tokens.border.radius.sm};
1290
- font-weight: ${tokens.font.weight.normal};
1291
- background-color: ${tokens.colors[color][900]}${tokens.alpha[90]};
1292
- color: ${tokens.colors[color][300]};
1293
- border: 1px solid ${tokens.colors[color][600]};
1294
- margin-bottom: ${tokens.size[2]};
1295
- transition: all 0.25s ease-out;
1296
- `
1297
- },
1298
- matchDetailsInfo: css`
1299
- display: flex;
1300
- justify-content: flex-end;
1301
- flex: 1;
1302
- `,
1303
- matchDetailsInfoLabel: css`
1304
- display: flex;
1305
- `,
1306
- mainCloseBtn: css`
1307
- background: ${colors.darkGray[700]};
1308
- padding: ${size[1]} ${size[2]} ${size[1]} ${size[1.5]};
1309
- border-radius: ${border.radius.md};
1310
- position: fixed;
1311
- z-index: 99999;
1312
- display: inline-flex;
1313
- width: fit-content;
1314
- cursor: pointer;
1315
- appearance: none;
1316
- border: 0;
1317
- gap: 8px;
1318
- align-items: center;
1319
- border: 1px solid ${colors.gray[500]};
1320
- font-size: ${font.size.xs};
1321
- cursor: pointer;
1322
- transition: all 0.25s ease-out;
1323
-
1324
- &:hover {
1325
- background: ${colors.darkGray[500]};
1326
- }
1327
- `,
1328
- mainCloseBtnPosition: (
1329
- position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right',
1330
- ) => {
1331
- const base = css`
1332
- ${position === 'top-left' ? `top: ${size[2]}; left: ${size[2]};` : ''}
1333
- ${position === 'top-right' ? `top: ${size[2]}; right: ${size[2]};` : ''}
1334
- ${position === 'bottom-left'
1335
- ? `bottom: ${size[2]}; left: ${size[2]};`
1336
- : ''}
1337
- ${position === 'bottom-right'
1338
- ? `bottom: ${size[2]}; right: ${size[2]};`
1339
- : ''}
1340
- `
1341
- return base
1342
- },
1343
- mainCloseBtnAnimation: (isOpen: boolean) => {
1344
- if (isOpen) {
1345
- return css`
1346
- opacity: 1;
1347
- pointer-events: auto;
1348
- visibility: visible;
1349
- `
1350
- }
1351
- return css`
1352
- opacity: 0;
1353
- pointer-events: none;
1354
- visibility: hidden;
1355
- `
1356
- },
1357
- routerLogoCloseButton: css`
1358
- font-weight: ${font.weight.semibold};
1359
- font-size: ${font.size.xs};
1360
- background: linear-gradient(to right, #98f30c, #00f4a3);
1361
- background-clip: text;
1362
- -webkit-background-clip: text;
1363
- line-height: 1;
1364
- -webkit-text-fill-color: transparent;
1365
- white-space: nowrap;
1366
- `,
1367
- mainCloseBtnDivider: css`
1368
- width: 1px;
1369
- background: ${tokens.colors.gray[600]};
1370
- height: 100%;
1371
- border-radius: 999999px;
1372
- color: transparent;
1373
- `,
1374
- mainCloseBtnIconContainer: css`
1375
- position: relative;
1376
- width: ${size[5]};
1377
- height: ${size[5]};
1378
- background: pink;
1379
- border-radius: 999999px;
1380
- overflow: hidden;
1381
- `,
1382
- mainCloseBtnIconOuter: css`
1383
- width: ${size[5]};
1384
- height: ${size[5]};
1385
- position: absolute;
1386
- top: 50%;
1387
- left: 50%;
1388
- transform: translate(-50%, -50%);
1389
- filter: blur(3px) saturate(1.8) contrast(2);
1390
- `,
1391
- mainCloseBtnIconInner: css`
1392
- width: ${size[4]};
1393
- height: ${size[4]};
1394
- position: absolute;
1395
- top: 50%;
1396
- left: 50%;
1397
- transform: translate(-50%, -50%);
1398
- `,
1399
- panelCloseBtn: css`
1400
- position: absolute;
1401
- cursor: pointer;
1402
- z-index: 100001;
1403
- display: flex;
1404
- align-items: center;
1405
- justify-content: center;
1406
- outline: none;
1407
- background-color: ${colors.darkGray[700]};
1408
- &:hover {
1409
- background-color: ${colors.darkGray[500]};
1410
- }
1411
-
1412
- top: 0;
1413
- right: ${size[2]};
1414
- transform: translate(0, -100%);
1415
- border-right: ${colors.darkGray[300]} 1px solid;
1416
- border-left: ${colors.darkGray[300]} 1px solid;
1417
- border-top: ${colors.darkGray[300]} 1px solid;
1418
- border-bottom: none;
1419
- border-radius: ${border.radius.sm} ${border.radius.sm} 0px 0px;
1420
- padding: ${size[1]} ${size[1.5]} ${size[0.5]} ${size[1.5]};
1421
-
1422
- &::after {
1423
- content: ' ';
1424
- position: absolute;
1425
- top: 100%;
1426
- left: -${size[2.5]};
1427
- height: ${size[1.5]};
1428
- width: calc(100% + ${size[5]});
1429
- }
1430
- `,
1431
- panelCloseBtnIcon: css`
1432
- color: ${colors.gray[400]};
1433
- width: ${size[2]};
1434
- height: ${size[2]};
1435
- `,
1436
- }
1437
- }
1438
-
1439
- function useStyles() {
1440
- const shadowDomTarget = React.useContext(ShadowDomTargetContext)
1441
- const [_styles] = React.useState(() => stylesFactory(shadowDomTarget))
1442
- return _styles
1443
- }