@tanstack/router-devtools 1.16.5 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/cjs/Explorer.cjs +146 -92
  2. package/dist/cjs/Explorer.cjs.map +1 -1
  3. package/dist/cjs/Explorer.d.cts +0 -7
  4. package/dist/cjs/devtools.cjs +901 -894
  5. package/dist/cjs/devtools.cjs.map +1 -1
  6. package/dist/cjs/logo.cjs +1012 -0
  7. package/dist/cjs/logo.cjs.map +1 -0
  8. package/dist/cjs/logo.d.cts +2 -0
  9. package/dist/cjs/tokens.cjs +302 -0
  10. package/dist/cjs/tokens.cjs.map +1 -0
  11. package/dist/cjs/tokens.d.cts +298 -0
  12. package/dist/cjs/utils.cjs +5 -33
  13. package/dist/cjs/utils.cjs.map +1 -1
  14. package/dist/cjs/utils.d.cts +2 -2
  15. package/dist/esm/Explorer.d.ts +0 -7
  16. package/dist/esm/Explorer.js +147 -93
  17. package/dist/esm/Explorer.js.map +1 -1
  18. package/dist/esm/devtools.js +901 -894
  19. package/dist/esm/devtools.js.map +1 -1
  20. package/dist/esm/logo.d.ts +2 -0
  21. package/dist/esm/logo.js +1012 -0
  22. package/dist/esm/logo.js.map +1 -0
  23. package/dist/esm/tokens.d.ts +298 -0
  24. package/dist/esm/tokens.js +302 -0
  25. package/dist/esm/tokens.js.map +1 -0
  26. package/dist/esm/utils.d.ts +2 -2
  27. package/dist/esm/utils.js +5 -33
  28. package/dist/esm/utils.js.map +1 -1
  29. package/package.json +4 -2
  30. package/src/Explorer.tsx +148 -93
  31. package/src/devtools.tsx +956 -851
  32. package/src/logo.tsx +817 -0
  33. package/src/tokens.ts +305 -0
  34. package/src/utils.ts +12 -11
  35. package/dist/cjs/styledComponents.cjs +0 -93
  36. package/dist/cjs/styledComponents.cjs.map +0 -1
  37. package/dist/cjs/styledComponents.d.cts +0 -7
  38. package/dist/cjs/theme.cjs +0 -28
  39. package/dist/cjs/theme.cjs.map +0 -1
  40. package/dist/cjs/useMediaQuery.cjs +0 -27
  41. package/dist/cjs/useMediaQuery.cjs.map +0 -1
  42. package/dist/esm/styledComponents.d.ts +0 -7
  43. package/dist/esm/styledComponents.js +0 -93
  44. package/dist/esm/styledComponents.js.map +0 -1
  45. package/dist/esm/theme.js +0 -28
  46. package/dist/esm/theme.js.map +0 -1
  47. package/dist/esm/useMediaQuery.js +0 -28
  48. package/dist/esm/useMediaQuery.js.map +0 -1
  49. package/src/styledComponents.ts +0 -106
package/src/devtools.tsx CHANGED
@@ -19,10 +19,11 @@ import {
19
19
  useIsMounted,
20
20
  useSafeState,
21
21
  } from './utils'
22
- import { Panel, Button, Code, ActivePanel } from './styledComponents'
23
- import { ThemeProvider, defaultTheme as theme } from './theme'
24
- // import { getQueryStatusLabel, getQueryStatusColor } from './utils'
22
+ import { css } from 'goober'
23
+ import { clsx as cx } from 'clsx'
25
24
  import Explorer from './Explorer'
25
+ import { tokens } from './tokens'
26
+ import { TanStackLogo } from './logo'
26
27
 
27
28
  export type PartialKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
28
29
 
@@ -98,48 +99,33 @@ interface DevtoolsPanelOptions {
98
99
 
99
100
  const isServer = typeof window === 'undefined'
100
101
 
101
- function Logo(props: React.HTMLProps<HTMLDivElement>) {
102
+ function Logo(props: React.HTMLAttributes<HTMLButtonElement>) {
103
+ const { className, ...rest } = props
102
104
  return (
103
- <div
104
- {...props}
105
- style={{
106
- ...(props.style ?? {}),
107
- display: 'flex',
108
- alignItems: 'center',
109
- flexDirection: 'column',
110
- fontSize: '0.8rem',
111
- fontWeight: 'bolder',
112
- lineHeight: '1',
113
- }}
114
- >
115
- <div
116
- style={{
117
- letterSpacing: '-0.05rem',
118
- }}
119
- >
120
- TANSTACK
121
- </div>
122
- <div
123
- style={{
124
- backgroundImage:
125
- 'linear-gradient(to right, var(--tw-gradient-stops))',
126
- // @ts-ignore
127
- '--tw-gradient-from': '#84cc16',
128
- '--tw-gradient-stops':
129
- 'var(--tw-gradient-from), var(--tw-gradient-to)',
130
- '--tw-gradient-to': '#10b981',
131
- WebkitBackgroundClip: 'text',
132
- color: 'transparent',
133
- letterSpacing: '0.1rem',
134
- marginRight: '-0.2rem',
135
- }}
136
- >
137
- ROUTER
138
- </div>
139
- </div>
105
+ <button {...rest} className={cx(styles.logo, className)}>
106
+ <div className={styles.tanstackLogo}>TANSTACK</div>
107
+ <div className={styles.routerLogo}>React Router v1</div>
108
+ </button>
140
109
  )
141
110
  }
142
111
 
112
+ const DevtoolsOnCloseContext = React.createContext<
113
+ | {
114
+ onCloseClick: (e: React.MouseEvent<HTMLButtonElement>) => void
115
+ }
116
+ | undefined
117
+ >(undefined)
118
+
119
+ const useDevtoolsOnClose = () => {
120
+ const context = React.useContext(DevtoolsOnCloseContext)
121
+ if (!context) {
122
+ throw new Error(
123
+ 'useDevtoolsOnClose must be used within a TanStackRouterDevtools component',
124
+ )
125
+ }
126
+ return context
127
+ }
128
+
143
129
  export function TanStackRouterDevtools({
144
130
  initialIsOpen,
145
131
  panelProps = {},
@@ -199,40 +185,12 @@ export function TanStackRouterDevtools({
199
185
  document.addEventListener('mouseup', unsub)
200
186
  }
201
187
 
188
+ const isButtonClosed = isOpen ?? false
189
+
202
190
  React.useEffect(() => {
203
191
  setIsResolvedOpen(isOpen ?? false)
204
192
  }, [isOpen, isResolvedOpen, setIsResolvedOpen])
205
193
 
206
- // Toggle panel visibility before/after transition (depending on direction).
207
- // Prevents focusing in a closed panel.
208
- React.useEffect(() => {
209
- const ref = panelRef.current
210
-
211
- if (ref) {
212
- const handlePanelTransitionStart = () => {
213
- if (ref && isResolvedOpen) {
214
- ref.style.visibility = 'visible'
215
- }
216
- }
217
-
218
- const handlePanelTransitionEnd = () => {
219
- if (ref && !isResolvedOpen) {
220
- ref.style.visibility = 'hidden'
221
- }
222
- }
223
-
224
- ref.addEventListener('transitionstart', handlePanelTransitionStart)
225
- ref.addEventListener('transitionend', handlePanelTransitionEnd)
226
-
227
- return () => {
228
- ref.removeEventListener('transitionstart', handlePanelTransitionStart)
229
- ref.removeEventListener('transitionend', handlePanelTransitionEnd)
230
- }
231
- }
232
-
233
- return
234
- }, [isResolvedOpen])
235
-
236
194
  React[isServer ? 'useEffect' : 'useLayoutEffect'](() => {
237
195
  if (isResolvedOpen) {
238
196
  const previousValue = rootRef.current?.parentElement?.style.paddingBottom
@@ -263,6 +221,14 @@ export function TanStackRouterDevtools({
263
221
  return
264
222
  }, [isResolvedOpen])
265
223
 
224
+ React[isServer ? 'useEffect' : 'useLayoutEffect'](() => {
225
+ if (rootRef.current) {
226
+ const el = rootRef.current
227
+ const fontSize = getComputedStyle(el).fontSize
228
+ el.style.setProperty('--tsrd-font-size', fontSize)
229
+ }
230
+ }, [rootRef.current])
231
+
266
232
  const { style: panelStyle = {}, ...otherPanelProps } = panelProps
267
233
 
268
234
  const {
@@ -280,131 +246,63 @@ export function TanStackRouterDevtools({
280
246
  // Do not render on the server
281
247
  if (!isMounted()) return null
282
248
 
249
+ const resolvedHeight = devtoolsHeight ?? 500
250
+
283
251
  return (
284
252
  <Container ref={rootRef} className="TanStackRouterDevtools">
285
- <ThemeProvider theme={theme}>
253
+ <DevtoolsOnCloseContext.Provider
254
+ value={{
255
+ onCloseClick: onCloseClick ?? (() => {}),
256
+ }}
257
+ >
286
258
  <TanStackRouterDevtoolsPanel
287
259
  ref={panelRef as any}
288
260
  {...otherPanelProps}
289
261
  router={router}
262
+ className={cx(
263
+ styles.devtoolsPanelContainer,
264
+ styles.devtoolsPanelContainerVisibility(!!isOpen),
265
+ styles.devtoolsPanelContainerResizing(isResizing),
266
+ styles.devtoolsPanelContainerAnimation(
267
+ isResolvedOpen,
268
+ resolvedHeight + 16,
269
+ ),
270
+ )}
290
271
  style={{
291
- direction: 'ltr',
292
- position: 'fixed',
293
- bottom: '0',
294
- right: '0',
295
- zIndex: 99999,
296
- width: '100%',
297
- height: devtoolsHeight ?? 500,
298
- maxHeight: '90%',
299
- boxShadow: '0 0 20px rgba(0,0,0,.3)',
300
- borderTop: `1px solid ${theme.gray}`,
301
- transformOrigin: 'top',
302
- // visibility will be toggled after transitions, but set initial state here
303
- visibility: isOpen ? 'visible' : 'hidden',
272
+ height: resolvedHeight,
304
273
  ...panelStyle,
305
- ...(isResizing
306
- ? {
307
- transition: `none`,
308
- }
309
- : { transition: `all .2s ease` }),
310
- ...(isResolvedOpen
311
- ? {
312
- opacity: 1,
313
- pointerEvents: 'all',
314
- transform: `translateY(0) scale(1)`,
315
- }
316
- : {
317
- opacity: 0,
318
- pointerEvents: 'none',
319
- transform: `translateY(15px) scale(1.02)`,
320
- }),
321
274
  }}
322
275
  isOpen={isResolvedOpen}
323
276
  setIsOpen={setIsOpen}
324
277
  handleDragStart={(e) => handleDragStart(panelRef.current, e)}
325
278
  />
326
- {isResolvedOpen ? (
327
- <Button
328
- type="button"
329
- aria-label="Close TanStack Router Devtools"
330
- {...(otherCloseButtonProps as any)}
331
- onClick={(e) => {
332
- setIsOpen(false)
333
- onCloseClick && onCloseClick(e)
334
- }}
335
- style={{
336
- position: 'fixed',
337
- zIndex: 99999,
338
- margin: '.5em',
339
- bottom: 0,
340
- ...(position === 'top-right'
341
- ? {
342
- right: '0',
343
- }
344
- : position === 'top-left'
345
- ? {
346
- left: '0',
347
- }
348
- : position === 'bottom-right'
349
- ? {
350
- right: '0',
351
- }
352
- : {
353
- left: '0',
354
- }),
355
- ...closeButtonStyle,
356
- }}
357
- >
358
- Close
359
- </Button>
360
- ) : null}
361
- </ThemeProvider>
362
- {!isResolvedOpen ? (
363
- <button
364
- type="button"
365
- {...otherToggleButtonProps}
366
- aria-label="Open TanStack Router Devtools"
367
- onClick={(e) => {
368
- setIsOpen(true)
369
- onToggleClick && onToggleClick(e)
370
- }}
371
- style={{
372
- appearance: 'none',
373
- background: 'none',
374
- border: 0,
375
- padding: 0,
376
- position: 'fixed',
377
- zIndex: 99999,
378
- display: 'inline-flex',
379
- fontSize: '1.5em',
380
- margin: '.5em',
381
- cursor: 'pointer',
382
- width: 'fit-content',
383
- ...(position === 'top-right'
384
- ? {
385
- top: '0',
386
- right: '0',
387
- }
388
- : position === 'top-left'
389
- ? {
390
- top: '0',
391
- left: '0',
392
- }
393
- : position === 'bottom-right'
394
- ? {
395
- bottom: '0',
396
- right: '0',
397
- }
398
- : {
399
- bottom: '0',
400
- left: '0',
401
- }),
402
- ...toggleButtonStyle,
403
- }}
404
- >
405
- <Logo aria-hidden />
406
- </button>
407
- ) : null}
279
+ </DevtoolsOnCloseContext.Provider>
280
+
281
+ <button
282
+ type="button"
283
+ {...otherToggleButtonProps}
284
+ aria-label="Open TanStack Router Devtools"
285
+ onClick={(e) => {
286
+ setIsOpen(true)
287
+ onToggleClick && onToggleClick(e)
288
+ }}
289
+ className={cx(
290
+ styles.mainCloseBtn,
291
+ styles.mainCloseBtnPosition(position),
292
+ styles.mainCloseBtnAnimation(!isButtonClosed),
293
+ )}
294
+ >
295
+ <div className={styles.mainCloseBtnIconContainer}>
296
+ <div className={styles.mainCloseBtnIconOuter}>
297
+ <TanStackLogo />
298
+ </div>
299
+ <div className={styles.mainCloseBtnIconInner}>
300
+ <TanStackLogo />
301
+ </div>
302
+ </div>
303
+ <div className={styles.mainCloseBtnDivider}>-</div>
304
+ <div className={styles.routerLogoCloseButton}>React Router</div>
305
+ </button>
408
306
  </Container>
409
307
  )
410
308
  }
@@ -428,6 +326,24 @@ function RouteComp({
428
326
 
429
327
  const match = routerState.matches.find((d) => d.routeId === route.id)
430
328
 
329
+ const param = React.useMemo(() => {
330
+ try {
331
+ if (match?.params) {
332
+ const p = match.params
333
+ const r: string = route.path || trimPath(route.id)
334
+ if (r.startsWith('$')) {
335
+ const trimmed = r.slice(1)
336
+ if (p[trimmed]) {
337
+ return `(${p[trimmed]})`
338
+ }
339
+ }
340
+ }
341
+ return ''
342
+ } catch (error) {
343
+ return ''
344
+ }
345
+ }, [match, route])
346
+
431
347
  return (
432
348
  <div>
433
349
  <div
@@ -438,64 +354,27 @@ function RouteComp({
438
354
  setActiveId(activeId === route.id ? '' : route.id)
439
355
  }
440
356
  }}
441
- style={{
442
- display: 'flex',
443
- borderBottom: `solid 1px ${theme.grayAlt}`,
444
- cursor: match ? 'pointer' : 'default',
445
- alignItems: 'center',
446
- background:
447
- route.id === activeId ? 'rgba(255,255,255,.1)' : undefined,
448
- padding: '.25rem .5rem',
449
- gap: '.5rem',
450
- }}
451
- >
452
- {isRoot ? null : (
453
- <div
454
- style={{
455
- flex: '0 0 auto',
456
- width: '.7rem',
457
- height: '.7rem',
458
- alignItems: 'center',
459
- justifyContent: 'center',
460
- fontWeight: 'bold',
461
- borderRadius: '100%',
462
- transition: 'all .2s ease-out',
463
- background: getRouteStatusColor(matches, route, theme),
464
- opacity: match ? 1 : 0.3,
465
- }}
466
- />
357
+ className={cx(
358
+ styles.routesRowContainer(route.id === activeId, !!match),
467
359
  )}
360
+ >
468
361
  <div
469
- style={{
470
- flex: '1 0 auto',
471
- display: 'flex',
472
- justifyContent: 'space-between',
473
- alignItems: 'center',
474
- padding: isRoot ? '0 .25rem' : 0,
475
- opacity: match ? 1 : 0.7,
476
- fontSize: '0.7rem',
477
- }}
478
- >
479
- <Code>{route.path || trimPath(route.id)} </Code>
480
- <div
481
- style={{
482
- display: 'flex',
483
- alignItems: 'center',
484
- gap: '.5rem',
485
- }}
486
- >
487
- {match ? <Code style={{ opacity: 0.3 }}>{match.id}</Code> : null}
488
- <AgeTicker match={match} />
362
+ className={cx(
363
+ styles.matchIndicator(getRouteStatusColor(matches, route)),
364
+ )}
365
+ />
366
+ <div className={cx(styles.routesRow(!!match))}>
367
+ <div>
368
+ <code className={styles.code}>
369
+ {isRoot ? '__root__' : route.path || trimPath(route.id)}{' '}
370
+ </code>
371
+ <code className={styles.routeParamInfo}>{param}</code>
489
372
  </div>
373
+ <AgeTicker match={match} />
490
374
  </div>
491
375
  </div>
492
376
  {(route.children as Route[])?.length ? (
493
- <div
494
- style={{
495
- marginLeft: isRoot ? 0 : '1rem',
496
- borderLeft: isRoot ? '' : `solid 1px ${theme.grayAlt}`,
497
- }}
498
- >
377
+ <div className={styles.nestedRouteRow(!!isRoot)}>
499
378
  {[...(route.children as Route[])]
500
379
  .sort((a, b) => {
501
380
  return a.rank - b.rank
@@ -526,6 +405,9 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
526
405
  ...panelProps
527
406
  } = props
528
407
 
408
+ const { onCloseClick } = useDevtoolsOnClose()
409
+ const { className, ...otherPanelProps } = panelProps
410
+
529
411
  const contextRouter = useRouter({ warn: false })
530
412
  const router = userRouter ?? contextRouter
531
413
  const routerState = useRouterState({
@@ -568,639 +450,304 @@ export const TanStackRouterDevtoolsPanel = React.forwardRef<
568
450
  }
569
451
 
570
452
  return (
571
- <ThemeProvider theme={theme}>
572
- <Panel ref={ref} className="TanStackRouterDevtoolsPanel" {...panelProps}>
573
- <style
574
- dangerouslySetInnerHTML={{
575
- __html: `
576
-
577
- .TanStackRouterDevtoolsPanel * {
578
- scrollbar-color: ${theme.backgroundAlt} ${theme.gray};
579
- }
580
-
581
- .TanStackRouterDevtoolsPanel *::-webkit-scrollbar, .TanStackRouterDevtoolsPanel scrollbar {
582
- width: 1em;
583
- height: 1em;
584
- }
585
-
586
- .TanStackRouterDevtoolsPanel *::-webkit-scrollbar-track, .TanStackRouterDevtoolsPanel scrollbar-track {
587
- background: ${theme.backgroundAlt};
588
- }
589
-
590
- .TanStackRouterDevtoolsPanel *::-webkit-scrollbar-thumb, .TanStackRouterDevtoolsPanel scrollbar-thumb {
591
- background: ${theme.gray};
592
- border-radius: .5em;
593
- border: 3px solid ${theme.backgroundAlt};
594
- }
595
-
596
- .TanStackRouterDevtoolsPanel table {
597
- width: 100%;
598
- }
599
-
600
- .TanStackRouterDevtoolsPanel table tr {
601
- border-bottom: 2px dotted rgba(255, 255, 255, .2);
602
- }
603
-
604
- .TanStackRouterDevtoolsPanel table tr:last-child {
605
- border-bottom: none
606
- }
607
-
608
- .TanStackRouterDevtoolsPanel table td {
609
- padding: .25rem .5rem;
610
- border-right: 2px dotted rgba(255, 255, 255, .05);
611
- }
612
-
613
- .TanStackRouterDevtoolsPanel table td:last-child {
614
- border-right: none
615
- }
616
-
617
- `,
618
- }}
619
- />
620
- {handleDragStart ? (
621
- <div
622
- style={{
623
- position: 'absolute',
624
- left: 0,
625
- top: 0,
626
- width: '100%',
627
- height: '4px',
628
- marginBottom: '-4px',
629
- cursor: 'row-resize',
630
- zIndex: 100000,
631
- }}
632
- onMouseDown={handleDragStart}
633
- ></div>
634
- ) : null}
635
- <div
636
- style={{
637
- flex: '1 1 500px',
638
- minHeight: '40%',
639
- maxHeight: '100%',
640
- overflow: 'auto',
641
- borderRight: `1px solid ${theme.grayAlt}`,
642
- display: 'flex',
643
- flexDirection: 'column',
644
- }}
453
+ <div
454
+ ref={ref}
455
+ className={cx(
456
+ styles.devtoolsPanel,
457
+ 'TanStackRouterDevtoolsPanel',
458
+ className,
459
+ )}
460
+ {...otherPanelProps}
461
+ >
462
+ {handleDragStart ? (
463
+ <div className={styles.dragHandle} onMouseDown={handleDragStart}></div>
464
+ ) : null}
465
+ <button
466
+ className={styles.panelCloseBtn}
467
+ onClick={(e) => {
468
+ setIsOpen(false)
469
+ onCloseClick(e)
470
+ }}
471
+ >
472
+ <svg
473
+ xmlns="http://www.w3.org/2000/svg"
474
+ width="10"
475
+ height="6"
476
+ fill="none"
477
+ viewBox="0 0 10 6"
478
+ className={styles.panelCloseBtnIcon}
645
479
  >
646
- <div
647
- style={{
648
- display: 'flex',
649
- justifyContent: 'start',
650
- gap: '1rem',
651
- padding: '1rem',
652
- alignItems: 'center',
653
- background: theme.backgroundAlt,
480
+ <path
481
+ stroke="currentColor"
482
+ strokeLinecap="round"
483
+ strokeLinejoin="round"
484
+ strokeWidth="1.667"
485
+ d="M1 1l4 4 4-4"
486
+ ></path>
487
+ </svg>
488
+ </button>
489
+ <div className={styles.firstContainer}>
490
+ <div className={styles.row}>
491
+ <Logo
492
+ aria-hidden
493
+ onClick={(e) => {
494
+ setIsOpen(false)
495
+ onCloseClick(e)
654
496
  }}
655
- >
656
- <Logo aria-hidden />
657
- <div
658
- style={{
659
- fontSize: 'clamp(.8rem, 2vw, 1.3rem)',
660
- fontWeight: 'bold',
497
+ />
498
+ </div>
499
+ <div className={styles.routerExplorerContainer}>
500
+ <div className={styles.routerExplorer}>
501
+ <Explorer
502
+ label="Router"
503
+ value={Object.fromEntries(
504
+ multiSortBy(
505
+ Object.keys(explorerState),
506
+ (
507
+ [
508
+ 'state',
509
+ 'routesById',
510
+ 'routesByPath',
511
+ 'flatRoutes',
512
+ 'options',
513
+ ] as const
514
+ ).map((d) => (dd) => dd !== d),
515
+ )
516
+ .map((key) => [key, (explorerState as any)[key]])
517
+ .filter(
518
+ (d) =>
519
+ typeof d[1] !== 'function' &&
520
+ ![
521
+ '__store',
522
+ 'basepath',
523
+ 'injectedHtml',
524
+ 'subscribers',
525
+ 'latestLoadPromise',
526
+ 'navigateTimeout',
527
+ 'resetNextScroll',
528
+ 'tempLocationKey',
529
+ 'latestLocation',
530
+ 'routeTree',
531
+ 'history',
532
+ ].includes(d[0]),
533
+ ),
534
+ )}
535
+ defaultExpanded={{
536
+ state: {} as any,
537
+ context: {} as any,
538
+ options: {} as any,
661
539
  }}
662
- >
663
- <span
664
- style={{
665
- fontWeight: 100,
666
- }}
667
- >
668
- Devtools
669
- </span>
670
- </div>
671
- </div>
672
- <div
673
- style={{
674
- overflowY: 'auto',
675
- flex: '1',
676
- }}
677
- >
678
- <div
679
- style={{
680
- padding: '.5em',
540
+ filterSubEntries={(subEntries) => {
541
+ return subEntries.filter((d) => typeof d.value !== 'function')
681
542
  }}
682
- >
683
- <Explorer
684
- label="Router"
685
- value={Object.fromEntries(
686
- multiSortBy(
687
- Object.keys(explorerState),
688
- (
689
- [
690
- 'state',
691
- 'routesById',
692
- 'routesByPath',
693
- 'flatRoutes',
694
- 'options',
695
- ] as const
696
- ).map((d) => (dd) => dd !== d),
697
- )
698
- .map((key) => [key, (explorerState as any)[key]])
699
- .filter(
700
- (d) =>
701
- typeof d[1] !== 'function' &&
702
- ![
703
- '__store',
704
- 'basepath',
705
- 'injectedHtml',
706
- 'subscribers',
707
- 'latestLoadPromise',
708
- 'navigateTimeout',
709
- 'resetNextScroll',
710
- 'tempLocationKey',
711
- 'latestLocation',
712
- 'routeTree',
713
- 'history',
714
- ].includes(d[0]),
715
- ),
716
- )}
717
- defaultExpanded={{
718
- state: {} as any,
719
- context: {} as any,
720
- options: {} as any,
721
- }}
722
- filterSubEntries={(subEntries) => {
723
- return subEntries.filter((d) => typeof d.value !== 'function')
724
- }}
725
- />
726
- </div>
543
+ />
727
544
  </div>
728
545
  </div>
729
- <div
730
- style={{
731
- flex: '1 1 500px',
732
- minHeight: '40%',
733
- maxHeight: '100%',
734
- overflow: 'auto',
735
- borderRight: `1px solid ${theme.grayAlt}`,
736
- display: 'flex',
737
- flexDirection: 'column',
738
- }}
739
- >
740
- <div
741
- style={{
742
- flex: '1 1 auto',
743
- overflowY: 'auto',
744
- }}
745
- >
746
- <div
747
- style={{
748
- padding: '.5em',
749
- background: theme.backgroundAlt,
750
- position: 'sticky',
751
- top: 0,
752
- zIndex: 1,
753
- display: 'flex',
754
- alignItems: 'center',
755
- gap: '.5rem',
756
- fontWeight: 'bold',
757
- }}
758
- >
759
- Pathname{' '}
760
- {routerState.location.maskedLocation ? (
761
- <div
762
- style={{
763
- padding: '.1rem .5rem',
764
- background: theme.warning,
765
- color: 'black',
766
- borderRadius: '.5rem',
767
- }}
768
- >
769
- Masked
770
- </div>
771
- ) : null}
772
- </div>
773
- <div
774
- style={{
775
- padding: '.5rem',
776
- display: 'flex',
777
- gap: '.5rem',
778
- alignItems: 'center',
779
- }}
780
- >
781
- <code
782
- style={{
783
- opacity: 0.6,
784
- }}
785
- >
786
- {routerState.location.pathname}
546
+ </div>
547
+ <div className={styles.secondContainer}>
548
+ <div className={styles.matchesContainer}>
549
+ <div className={styles.detailsHeader}>
550
+ <span>Pathname</span>
551
+ {routerState.location.maskedLocation ? (
552
+ <div className={styles.maskedBadgeContainer}>
553
+ <span className={styles.maskedBadge}>masked</span>
554
+ </div>
555
+ ) : null}
556
+ </div>
557
+ <div className={styles.detailsContent}>
558
+ <code>{routerState.location.pathname}</code>
559
+ {routerState.location.maskedLocation ? (
560
+ <code className={styles.maskedLocation}>
561
+ {routerState.location.maskedLocation.pathname}
787
562
  </code>
788
- {routerState.location.maskedLocation ? (
789
- <code
790
- style={{
791
- color: theme.warning,
792
- fontWeight: 'bold',
793
- }}
794
- >
795
- {routerState.location.maskedLocation.pathname}
796
- </code>
797
- ) : null}
798
- </div>
799
- <div
800
- style={{
801
- padding: '.5em',
802
- background: theme.backgroundAlt,
803
- position: 'sticky',
804
- top: 0,
805
- zIndex: 1,
806
- display: 'flex',
807
- alignItems: 'center',
808
- justifyContent: 'space-between',
809
- gap: '.5rem',
810
- fontWeight: 'bold',
811
- }}
812
- >
813
- <div
814
- style={{
815
- display: 'flex',
816
- alignItems: 'center',
817
- gap: '.5rem',
563
+ ) : null}
564
+ </div>
565
+ <div className={styles.detailsHeader}>
566
+ <div className={styles.routeMatchesToggle}>
567
+ <button
568
+ type="button"
569
+ onClick={() => {
570
+ setShowMatches(false)
818
571
  }}
572
+ disabled={!showMatches}
573
+ className={cx(styles.routeMatchesToggleBtn(!showMatches, true))}
819
574
  >
820
- <button
821
- type="button"
822
- onClick={() => {
823
- setShowMatches(false)
824
- }}
825
- disabled={!showMatches}
826
- style={{
827
- appearance: 'none',
828
- opacity: showMatches ? 0.5 : 1,
829
- border: 0,
830
- background: 'transparent',
831
- color: 'inherit',
832
- cursor: 'pointer',
833
- }}
834
- >
835
- Routes
836
- </button>
837
- /
838
- <button
839
- type="button"
840
- onClick={() => {
841
- setShowMatches(true)
842
- }}
843
- disabled={showMatches}
844
- style={{
845
- appearance: 'none',
846
- opacity: !showMatches ? 0.5 : 1,
847
- border: 0,
848
- background: 'transparent',
849
- color: 'inherit',
850
- cursor: 'pointer',
851
- }}
852
- >
853
- Matches
854
- </button>
855
- </div>
856
- <div
857
- style={{
858
- opacity: 0.3,
859
- fontSize: '0.7rem',
860
- fontWeight: 'normal',
575
+ Routes
576
+ </button>
577
+ <button
578
+ type="button"
579
+ onClick={() => {
580
+ setShowMatches(true)
861
581
  }}
582
+ disabled={showMatches}
583
+ className={cx(
584
+ styles.routeMatchesToggleBtn(!!showMatches, false),
585
+ )}
862
586
  >
587
+ Matches
588
+ </button>
589
+ </div>
590
+ <div className={styles.detailsHeaderInfo}>
591
+ <div>age / staleTime / gcTime</div>
592
+ </div>
593
+ </div>
594
+ {!showMatches ? (
595
+ <RouteComp
596
+ route={router.routeTree}
597
+ isRoot
598
+ activeId={activeId}
599
+ setActiveId={setActiveId}
600
+ />
601
+ ) : (
602
+ <div>
603
+ {(routerState.status === 'pending'
604
+ ? routerState.pendingMatches ?? []
605
+ : routerState.matches
606
+ ).map((match, i) => {
607
+ return (
608
+ <div
609
+ key={match.id || i}
610
+ role="button"
611
+ aria-label={`Open match details for ${match.id}`}
612
+ onClick={() =>
613
+ setActiveId(activeId === match.id ? '' : match.id)
614
+ }
615
+ className={cx(styles.matchRow(match === activeMatch))}
616
+ >
617
+ <div
618
+ className={cx(
619
+ styles.matchIndicator(getStatusColor(match)),
620
+ )}
621
+ />
622
+
623
+ <code
624
+ className={styles.matchID}
625
+ >{`${match.routeId === '__root__' ? '__root__' : match.pathname}`}</code>
626
+ <AgeTicker match={match} />
627
+ </div>
628
+ )
629
+ })}
630
+ </div>
631
+ )}
632
+ </div>
633
+ {routerState.cachedMatches?.length ? (
634
+ <div className={styles.cachedMatchesContainer}>
635
+ <div className={styles.detailsHeader}>
636
+ <div>Cached Matches</div>
637
+ <div className={styles.detailsHeaderInfo}>
863
638
  age / staleTime / gcTime
864
639
  </div>
865
640
  </div>
866
- {!showMatches ? (
867
- <RouteComp
868
- route={router.routeTree}
869
- isRoot
870
- activeId={activeId}
871
- setActiveId={setActiveId}
872
- />
873
- ) : (
874
- <div>
875
- {(routerState.status === 'pending'
876
- ? routerState.pendingMatches ?? []
877
- : routerState.matches
878
- ).map((match, i) => {
879
- return (
641
+ <div>
642
+ {routerState.cachedMatches.map((match) => {
643
+ return (
644
+ <div
645
+ key={match.id}
646
+ role="button"
647
+ aria-label={`Open match details for ${match.id}`}
648
+ onClick={() =>
649
+ setActiveId(activeId === match.id ? '' : match.id)
650
+ }
651
+ className={cx(styles.matchRow(match === activeMatch))}
652
+ >
880
653
  <div
881
- key={match.id || i}
882
- role="button"
883
- aria-label={`Open match details for ${match.id}`}
884
- onClick={() =>
885
- setActiveId(activeId === match.id ? '' : match.id)
886
- }
887
- style={{
888
- display: 'flex',
889
- borderBottom: `solid 1px ${theme.grayAlt}`,
890
- cursor: 'pointer',
891
- alignItems: 'center',
892
- background:
893
- match === activeMatch
894
- ? 'rgba(255,255,255,.1)'
895
- : undefined,
896
- }}
897
- >
898
- <div
899
- style={{
900
- flex: '0 0 auto',
901
- width: '1.3rem',
902
- height: '1.3rem',
903
- marginLeft: '.25rem',
904
- background: getStatusColor(match, theme),
905
- alignItems: 'center',
906
- justifyContent: 'center',
907
- fontWeight: 'bold',
908
- borderRadius: '.25rem',
909
- transition: 'all .2s ease-out',
910
- }}
911
- />
912
-
913
- <Code
914
- style={{
915
- padding: '.5em',
916
- fontSize: '0.7rem',
917
- }}
918
- >
919
- {`${match.id}`}
920
- </Code>
921
- <AgeTicker match={match} />
922
- </div>
923
- )
924
- })}
925
- </div>
926
- )}
654
+ className={cx(
655
+ styles.matchIndicator(getStatusColor(match)),
656
+ )}
657
+ />
658
+
659
+ <code className={styles.matchID}>{`${match.id}`}</code>
660
+
661
+ <AgeTicker match={match} />
662
+ </div>
663
+ )
664
+ })}
665
+ </div>
927
666
  </div>
928
- {routerState.cachedMatches?.length ? (
929
- <div
930
- style={{
931
- flex: '1 1 auto',
932
- overflowY: 'auto',
933
- maxHeight: '50%',
934
- }}
935
- >
667
+ ) : null}
668
+ </div>
669
+ {activeMatch ? (
670
+ <div className={styles.thirdContainer}>
671
+ <div className={styles.detailsHeader}>Match Details</div>
672
+ <div>
673
+ <div className={styles.matchDetails}>
936
674
  <div
937
- style={{
938
- padding: '.5em',
939
- background: theme.backgroundAlt,
940
- position: 'sticky',
941
- top: 0,
942
- zIndex: 1,
943
- display: 'flex',
944
- alignItems: 'center',
945
- justifyContent: 'space-between',
946
- gap: '.5rem',
947
- fontWeight: 'bold',
948
- }}
675
+ className={styles.matchStatus(
676
+ activeMatch.status,
677
+ activeMatch.isFetching,
678
+ )}
949
679
  >
950
- <div>Cached Matches</div>
951
- <div
952
- style={{
953
- opacity: 0.3,
954
- fontSize: '0.7rem',
955
- fontWeight: 'normal',
956
- }}
957
- >
958
- age / staleTime / gcTime
680
+ <div>
681
+ {activeMatch.status === 'success' && activeMatch.isFetching
682
+ ? 'fetching'
683
+ : activeMatch.status}
959
684
  </div>
960
685
  </div>
961
- <div>
962
- {routerState.cachedMatches.map((match) => {
963
- return (
964
- <div
965
- key={match.id}
966
- role="button"
967
- aria-label={`Open match details for ${match.id}`}
968
- onClick={() =>
969
- setActiveId(activeId === match.id ? '' : match.id)
970
- }
971
- style={{
972
- display: 'flex',
973
- borderBottom: `solid 1px ${theme.grayAlt}`,
974
- cursor: 'pointer',
975
- alignItems: 'center',
976
- background:
977
- match === activeMatch
978
- ? 'rgba(255,255,255,.1)'
979
- : undefined,
980
- fontSize: '0.7rem',
981
- }}
982
- >
983
- <div
984
- style={{
985
- flex: '0 0 auto',
986
- width: '.75rem',
987
- height: '.75rem',
988
- marginLeft: '.25rem',
989
- background: getStatusColor(match, theme),
990
- alignItems: 'center',
991
- justifyContent: 'center',
992
- fontWeight: 'bold',
993
- borderRadius: '100%',
994
- transition: 'all 1s ease-out',
995
- }}
996
- />
997
-
998
- <Code
999
- style={{
1000
- padding: '.5em',
1001
- }}
1002
- >
1003
- {`${match.id}`}
1004
- </Code>
1005
-
1006
- <div style={{ marginLeft: 'auto' }}>
1007
- <AgeTicker match={match} />
1008
- </div>
1009
- </div>
686
+ <div className={styles.matchDetailsInfoLabel}>
687
+ <div>ID:</div>
688
+ <div className={styles.matchDetailsInfo}>
689
+ <code>{activeMatch.id}</code>
690
+ </div>
691
+ </div>
692
+ <div className={styles.matchDetailsInfoLabel}>
693
+ <div>State:</div>
694
+ <div className={styles.matchDetailsInfo}>
695
+ {routerState.pendingMatches?.find(
696
+ (d) => d.id === activeMatch.id,
1010
697
  )
1011
- })}
698
+ ? 'Pending'
699
+ : routerState.matches?.find((d) => d.id === activeMatch.id)
700
+ ? 'Active'
701
+ : 'Cached'}
702
+ </div>
703
+ </div>
704
+ <div className={styles.matchDetailsInfoLabel}>
705
+ <div>Last Updated:</div>
706
+ <div className={styles.matchDetailsInfo}>
707
+ {activeMatch.updatedAt
708
+ ? new Date(
709
+ activeMatch.updatedAt as number,
710
+ ).toLocaleTimeString()
711
+ : 'N/A'}
712
+ </div>
1012
713
  </div>
1013
714
  </div>
715
+ </div>
716
+ {activeMatch.loaderData ? (
717
+ <>
718
+ <div className={styles.detailsHeader}>Loader Data</div>
719
+ <div className={styles.detailsContent}>
720
+ <Explorer
721
+ label="loaderData"
722
+ value={activeMatch.loaderData}
723
+ defaultExpanded={{}}
724
+ />
725
+ </div>
726
+ </>
1014
727
  ) : null}
728
+ <div className={styles.detailsHeader}>Explorer</div>
729
+ <div className={styles.detailsContent}>
730
+ <Explorer label="Match" value={activeMatch} defaultExpanded={{}} />
731
+ </div>
1015
732
  </div>
1016
- {activeMatch ? (
1017
- <ActivePanel>
1018
- <div
1019
- style={{
1020
- padding: '.5em',
1021
- background: theme.backgroundAlt,
1022
- position: 'sticky',
1023
- top: 0,
1024
- bottom: 0,
1025
- zIndex: 1,
1026
- }}
1027
- >
1028
- Match Details
1029
- </div>
1030
- <div>
1031
- <table
1032
- style={{
1033
- fontSize: '0.8rem',
1034
- }}
1035
- >
1036
- <tbody>
1037
- <tr>
1038
- <td style={{ opacity: '.5' }}>ID</td>
1039
- <td>
1040
- <Code
1041
- style={{
1042
- lineHeight: '1.8em',
1043
- }}
1044
- >
1045
- {JSON.stringify(activeMatch.id, null, 2)}
1046
- </Code>
1047
- </td>
1048
- </tr>
1049
- <tr>
1050
- <td style={{ opacity: '.5' }}>Status</td>
1051
- <td>
1052
- {routerState.pendingMatches?.find(
1053
- (d) => d.id === activeMatch.id,
1054
- )
1055
- ? 'Pending'
1056
- : routerState.matches?.find(
1057
- (d) => d.id === activeMatch.id,
1058
- )
1059
- ? 'Active'
1060
- : 'Cached'}{' '}
1061
- - {activeMatch.status}
1062
- </td>
1063
- </tr>
1064
- {/* <tr>
1065
- <td style={{ opacity: '.5' }}>Invalid</td>
1066
- <td>{activeMatch.getIsInvalid().toString()}</td>
1067
- </tr> */}
1068
- <tr>
1069
- <td style={{ opacity: '.5' }}>Last Updated</td>
1070
- <td>
1071
- {activeMatch.updatedAt
1072
- ? new Date(
1073
- activeMatch.updatedAt as number,
1074
- ).toLocaleTimeString()
1075
- : 'N/A'}
1076
- </td>
1077
- </tr>
1078
- </tbody>
1079
- </table>
1080
- </div>
1081
- {/* <div
1082
- style={{
1083
- background: theme.backgroundAlt,
1084
- padding: '.5em',
1085
- position: 'sticky',
1086
- top: 0,
1087
- bottom: 0,
1088
- zIndex: 1,
1089
- }}
1090
- >
1091
- Actions
1092
- </div>
1093
- <div
1094
- style={{
1095
- padding: '0.5em',
1096
- }}
1097
- >
1098
- <Button
1099
- type="button"
1100
- onClick={() => activeMatch.__store.setState(d => ({...d, status: 'pending'}))}
1101
- style={{
1102
- background: theme.gray,
1103
- }}
1104
- >
1105
- Reload
1106
- </Button>
1107
- </div> */}
1108
- {activeMatch.loaderData ? (
1109
- <>
1110
- <div
1111
- style={{
1112
- background: theme.backgroundAlt,
1113
- padding: '.5em',
1114
- position: 'sticky',
1115
- top: 0,
1116
- bottom: 0,
1117
- zIndex: 1,
1118
- }}
1119
- >
1120
- Loader Data
1121
- </div>
1122
- <div
1123
- style={{
1124
- padding: '.5em',
1125
- }}
1126
- >
1127
- <Explorer
1128
- label="loaderData"
1129
- value={activeMatch.loaderData}
1130
- defaultExpanded={{}}
1131
- />
1132
- </div>
1133
- </>
1134
- ) : null}
1135
- <div
1136
- style={{
1137
- background: theme.backgroundAlt,
1138
- padding: '.5em',
1139
- position: 'sticky',
1140
- top: 0,
1141
- bottom: 0,
1142
- zIndex: 1,
1143
- }}
1144
- >
1145
- Explorer
1146
- </div>
1147
- <div
1148
- style={{
1149
- padding: '.5em',
1150
- }}
1151
- >
1152
- <Explorer
1153
- label="Match"
1154
- value={activeMatch}
1155
- defaultExpanded={{}}
1156
- />
1157
- </div>
1158
- </ActivePanel>
1159
- ) : null}
1160
- {hasSearch ? (
1161
- <div
1162
- style={{
1163
- flex: '1 1 500px',
1164
- minHeight: '40%',
1165
- maxHeight: '100%',
1166
- overflow: 'auto',
1167
- borderRight: `1px solid ${theme.grayAlt}`,
1168
- display: 'flex',
1169
- flexDirection: 'column',
1170
- }}
1171
- >
1172
- <div
1173
- style={{
1174
- padding: '.5em',
1175
- background: theme.backgroundAlt,
1176
- position: 'sticky',
1177
- top: 0,
1178
- bottom: 0,
1179
- zIndex: 1,
1180
- fontWeight: 'bold',
1181
- }}
1182
- >
1183
- Search Params
1184
- </div>
1185
- <div
1186
- style={{
1187
- padding: '.5em',
1188
- }}
1189
- >
1190
- <Explorer
1191
- value={routerState.location.search || {}}
1192
- defaultExpanded={Object.keys(
1193
- (routerState.location.search as {}) || {},
1194
- ).reduce((obj: any, next) => {
1195
- obj[next] = {}
1196
- return obj
1197
- }, {})}
1198
- />
1199
- </div>
733
+ ) : null}
734
+ {hasSearch ? (
735
+ <div className={styles.fourthContainer}>
736
+ <div className={styles.detailsHeader}>Search Params</div>
737
+ <div className={styles.detailsContent}>
738
+ <Explorer
739
+ value={routerState.location.search || {}}
740
+ defaultExpanded={Object.keys(
741
+ (routerState.location.search as {}) || {},
742
+ ).reduce((obj: any, next) => {
743
+ obj[next] = {}
744
+ return obj
745
+ }, {})}
746
+ />
1200
747
  </div>
1201
- ) : null}
1202
- </Panel>
1203
- </ThemeProvider>
748
+ </div>
749
+ ) : null}
750
+ </div>
1204
751
  )
1205
752
  })
1206
753
 
@@ -1239,15 +786,8 @@ function AgeTicker({ match }: { match?: AnyRouteMatch }) {
1239
786
  route.options.gcTime ?? router.options.defaultGcTime ?? 30 * 60 * 1000
1240
787
 
1241
788
  return (
1242
- <div
1243
- style={{
1244
- display: 'inline-flex',
1245
- alignItems: 'center',
1246
- gap: '.25rem',
1247
- color: age > staleTime ? theme.warning : undefined,
1248
- }}
1249
- >
1250
- <div style={{}}>{formatTime(age)}</div>
789
+ <div className={cx(styles.ageTicker(age > staleTime))}>
790
+ <div>{formatTime(age)}</div>
1251
791
  <div>/</div>
1252
792
  <div>{formatTime(staleTime)}</div>
1253
793
  <div>/</div>
@@ -1274,3 +814,568 @@ function formatTime(ms: number) {
1274
814
 
1275
815
  return formatter.format(values[chosenUnitIndex]!) + units[chosenUnitIndex]
1276
816
  }
817
+
818
+ const stylesFactory = () => {
819
+ const { colors, font, size, alpha, shadow, border } = tokens
820
+ const { fontFamily, lineHeight, size: fontSize } = font
821
+
822
+ return {
823
+ devtoolsPanelContainer: css`
824
+ direction: ltr;
825
+ position: fixed;
826
+ bottom: 0;
827
+ right: 0;
828
+ z-index: 99999;
829
+ width: 100%;
830
+ max-height: 90%;
831
+ border-top: 1px solid ${colors.gray[700]};
832
+ transform-origin: top;
833
+ `,
834
+ devtoolsPanelContainerVisibility: (isOpen: boolean) => {
835
+ return css`
836
+ visibility: ${isOpen ? 'visible' : 'hidden'};
837
+ `
838
+ },
839
+ devtoolsPanelContainerResizing: (isResizing: boolean) => {
840
+ if (isResizing) {
841
+ return css`
842
+ transition: none;
843
+ `
844
+ }
845
+
846
+ return css`
847
+ transition: all 0.4s ease;
848
+ `
849
+ },
850
+ devtoolsPanelContainerAnimation: (isOpen: boolean, height: number) => {
851
+ if (isOpen) {
852
+ return css`
853
+ pointer-events: auto;
854
+ transform: translateY(0);
855
+ `
856
+ }
857
+ return css`
858
+ pointer-events: none;
859
+ transform: translateY(${height}px);
860
+ `
861
+ },
862
+ logo: css`
863
+ cursor: pointer;
864
+ display: flex;
865
+ flex-direction: column;
866
+ background-color: transparent;
867
+ border: none;
868
+ font-family: ${fontFamily.sans};
869
+ gap: ${tokens.size[0.5]};
870
+ padding: 0px;
871
+ &:hover {
872
+ opacity: 0.7;
873
+ }
874
+ &:focus-visible {
875
+ outline-offset: 4px;
876
+ border-radius: ${border.radius.xs};
877
+ outline: 2px solid ${colors.blue[800]};
878
+ }
879
+ `,
880
+ tanstackLogo: css`
881
+ font-size: ${font.size.md};
882
+ font-weight: ${font.weight.bold};
883
+ line-height: ${font.lineHeight.xs};
884
+ white-space: nowrap;
885
+ color: ${colors.gray[300]};
886
+ `,
887
+ routerLogo: css`
888
+ font-weight: ${font.weight.semibold};
889
+ font-size: ${font.size.xs};
890
+ background: linear-gradient(to right, #84cc16, #10b981);
891
+ background-clip: text;
892
+ -webkit-background-clip: text;
893
+ line-height: 1;
894
+ -webkit-text-fill-color: transparent;
895
+ white-space: nowrap;
896
+ `,
897
+ devtoolsPanel: css`
898
+ display: flex;
899
+ font-size: ${fontSize.sm};
900
+ font-family: ${fontFamily.sans};
901
+ background-color: ${colors.darkGray[700]};
902
+ color: ${colors.gray[300]};
903
+
904
+ @media (max-width: 700px) {
905
+ flex-direction: column;
906
+ }
907
+ @media (max-width: 600px) {
908
+ font-size: ${fontSize.xs};
909
+ }
910
+ `,
911
+ dragHandle: css`
912
+ position: absolute;
913
+ left: 0;
914
+ top: 0;
915
+ width: 100%;
916
+ height: 4px;
917
+ cursor: row-resize;
918
+ z-index: 100000;
919
+ &:hover {
920
+ background-color: ${colors.purple[400]}${alpha[90]};
921
+ }
922
+ `,
923
+ firstContainer: css`
924
+ flex: 1 1 500px;
925
+ min-height: 40%;
926
+ max-height: 100%;
927
+ overflow: auto;
928
+ border-right: 1px solid ${colors.gray[700]};
929
+ display: flex;
930
+ flex-direction: column;
931
+ `,
932
+ routerExplorerContainer: css`
933
+ overflow-y: auto;
934
+ flex: 1;
935
+ `,
936
+ routerExplorer: css`
937
+ padding: ${tokens.size[2]};
938
+ `,
939
+ row: css`
940
+ display: flex;
941
+ align-items: center;
942
+ padding: ${tokens.size[2]} ${tokens.size[2.5]};
943
+ gap: ${tokens.size[2.5]};
944
+ border-bottom: ${colors.darkGray[500]} 1px solid;
945
+ align-items: center;
946
+ `,
947
+ detailsHeader: css`
948
+ font-family: ui-sans-serif, Inter, system-ui, sans-serif, sans-serif;
949
+ position: sticky;
950
+ top: 0;
951
+ z-index: 2;
952
+ background-color: ${colors.darkGray[600]};
953
+ padding: 0px ${tokens.size[2]};
954
+ font-weight: ${font.weight.medium};
955
+ font-size: ${font.size.xs};
956
+ min-height: ${tokens.size[8]};
957
+ line-height: ${font.lineHeight.xs};
958
+ text-align: left;
959
+ display: flex;
960
+ align-items: center;
961
+ `,
962
+ maskedBadge: css`
963
+ background: ${colors.yellow[900]}${alpha[70]};
964
+ color: ${colors.yellow[300]};
965
+ display: inline-block;
966
+ padding: ${tokens.size[0]} ${tokens.size[2.5]};
967
+ border-radius: ${border.radius.full};
968
+ font-size: ${font.size.xs};
969
+ font-weight: ${font.weight.normal};
970
+ border: 1px solid ${colors.yellow[300]};
971
+ `,
972
+ maskedLocation: css`
973
+ color: ${colors.yellow[300]};
974
+ `,
975
+ detailsContent: css`
976
+ padding: ${tokens.size[1.5]} ${tokens.size[2]};
977
+ display: flex;
978
+ align-items: center;
979
+ font-size: ${font.size.xs};
980
+ `,
981
+ routeMatchesToggle: css`
982
+ display: flex;
983
+ align-items: center;
984
+ border: 1px solid ${colors.gray[500]};
985
+ border-radius: ${border.radius.sm};
986
+ overflow: hidden;
987
+ `,
988
+ routeMatchesToggleBtn: (active: boolean, showBorder: boolean) => {
989
+ const base = css`
990
+ appearance: none;
991
+ border: none;
992
+ font-size: 12px;
993
+ padding: 4px 8px;
994
+ background: transparent;
995
+ cursor: pointer;
996
+ font-family: ${fontFamily.sans};
997
+ font-weight: ${font.weight.medium};
998
+ `
999
+ const classes = [base]
1000
+
1001
+ if (active) {
1002
+ const activeStyles = css`
1003
+ background: ${colors.darkGray[400]};
1004
+ color: ${colors.gray[300]};
1005
+ `
1006
+ classes.push(activeStyles)
1007
+ } else {
1008
+ const inactiveStyles = css`
1009
+ color: ${colors.gray[500]};
1010
+ background: ${colors.darkGray[800]}${alpha[20]};
1011
+ `
1012
+ classes.push(inactiveStyles)
1013
+ }
1014
+
1015
+ if (showBorder) {
1016
+ const border = css`
1017
+ border-right: 1px solid ${tokens.colors.gray[500]};
1018
+ `
1019
+ classes.push(border)
1020
+ }
1021
+
1022
+ return classes
1023
+ },
1024
+ detailsHeaderInfo: css`
1025
+ flex: 1;
1026
+ justify-content: flex-end;
1027
+ display: flex;
1028
+ align-items: center;
1029
+ font-weight: ${font.weight.normal};
1030
+ color: ${colors.gray[400]};
1031
+ `,
1032
+ matchRow: (active: boolean) => {
1033
+ const base = css`
1034
+ display: flex;
1035
+ border-bottom: 1px solid ${colors.darkGray[400]};
1036
+ cursor: pointer;
1037
+ align-items: center;
1038
+ padding: ${size[1]} ${size[2]};
1039
+ gap: ${size[2]};
1040
+ font-size: ${fontSize.xs};
1041
+ color: ${colors.gray[300]};
1042
+ `
1043
+ const classes = [base]
1044
+
1045
+ if (active) {
1046
+ const activeStyles = css`
1047
+ background: ${colors.darkGray[500]};
1048
+ `
1049
+ classes.push(activeStyles)
1050
+ }
1051
+
1052
+ return classes
1053
+ },
1054
+ matchIndicator: (color: 'green' | 'red' | 'yellow' | 'gray' | 'blue') => {
1055
+ const base = css`
1056
+ flex: 0 0 auto;
1057
+ width: ${size[3]};
1058
+ height: ${size[3]};
1059
+ background: ${colors[color][900]};
1060
+ border: 1px solid ${colors[color][500]};
1061
+ border-radius: ${border.radius.full};
1062
+ transition: all 0.25s ease-out;
1063
+ box-sizing: border-box;
1064
+ `
1065
+ const classes = [base]
1066
+
1067
+ if (color === 'gray') {
1068
+ const grayStyles = css`
1069
+ background: ${colors.gray[700]};
1070
+ border-color: ${colors.gray[400]};
1071
+ `
1072
+ classes.push(grayStyles)
1073
+ }
1074
+
1075
+ return classes
1076
+ },
1077
+ matchID: css`
1078
+ flex: 1;
1079
+ line-height: ${lineHeight['xs']};
1080
+ `,
1081
+ ageTicker: (showWarning: boolean) => {
1082
+ const base = css`
1083
+ display: flex;
1084
+ gap: ${size[1]};
1085
+ font-size: ${fontSize.xs};
1086
+ color: ${colors.gray[400]};
1087
+ font-variant-numeric: tabular-nums;
1088
+ line-height: ${lineHeight['xs']};
1089
+ `
1090
+
1091
+ const classes = [base]
1092
+
1093
+ if (showWarning) {
1094
+ const warningStyles = css`
1095
+ color: ${colors.yellow[400]};
1096
+ `
1097
+ classes.push(warningStyles)
1098
+ }
1099
+
1100
+ return classes
1101
+ },
1102
+ secondContainer: css`
1103
+ flex: 1 1 500px;
1104
+ min-height: 40%;
1105
+ max-height: 100%;
1106
+ overflow: auto;
1107
+ border-right: 1px solid ${colors.gray[700]};
1108
+ display: flex;
1109
+ flex-direction: column;
1110
+ `,
1111
+ thirdContainer: css`
1112
+ flex: 1 1 500px;
1113
+ overflow: auto;
1114
+ display: flex;
1115
+ flex-direction: column;
1116
+ height: 100%;
1117
+ border-right: 1px solid ${colors.gray[700]};
1118
+
1119
+ @media (max-width: 700px) {
1120
+ border-top: 2px solid ${colors.gray[700]};
1121
+ }
1122
+ `,
1123
+ fourthContainer: css`
1124
+ flex: 1 1 500px;
1125
+ min-height: 40%;
1126
+ max-height: 100%;
1127
+ overflow: auto;
1128
+ display: flex;
1129
+ flex-direction: column;
1130
+ `,
1131
+ routesRowContainer: (active: boolean, isMatch: boolean) => {
1132
+ const base = css`
1133
+ display: flex;
1134
+ border-bottom: 1px solid ${colors.darkGray[400]};
1135
+ align-items: center;
1136
+ padding: ${size[1]} ${size[2]};
1137
+ gap: ${size[2]};
1138
+ font-size: ${fontSize.xs};
1139
+ color: ${colors.gray[300]};
1140
+ cursor: ${isMatch ? 'pointer' : 'default'};
1141
+ line-height: ${lineHeight['xs']};
1142
+ `
1143
+ const classes = [base]
1144
+
1145
+ if (active) {
1146
+ const activeStyles = css`
1147
+ background: ${colors.darkGray[500]};
1148
+ `
1149
+ classes.push(activeStyles)
1150
+ }
1151
+
1152
+ return classes
1153
+ },
1154
+ routesRow: (isMatch: boolean) => {
1155
+ const base = css`
1156
+ flex: 1 0 auto;
1157
+ display: flex;
1158
+ justify-content: space-between;
1159
+ align-items: center;
1160
+ font-size: ${fontSize.xs};
1161
+ line-height: ${lineHeight['xs']};
1162
+ `
1163
+
1164
+ const classes = [base]
1165
+
1166
+ if (!isMatch) {
1167
+ const matchStyles = css`
1168
+ color: ${colors.gray[400]};
1169
+ `
1170
+ classes.push(matchStyles)
1171
+ }
1172
+
1173
+ return classes
1174
+ },
1175
+ routeParamInfo: css`
1176
+ color: ${colors.gray[400]};
1177
+ font-size: ${fontSize.xs};
1178
+ line-height: ${lineHeight['xs']};
1179
+ `,
1180
+ nestedRouteRow: (isRoot: boolean) => {
1181
+ const base = css`
1182
+ margin-left: ${isRoot ? 0 : size[3.5]};
1183
+ border-left: ${isRoot ? '' : `solid 1px ${colors.gray[700]}`};
1184
+ `
1185
+ return base
1186
+ },
1187
+ code: css`
1188
+ font-size: ${fontSize.xs};
1189
+ line-height: ${lineHeight['xs']};
1190
+ `,
1191
+ matchesContainer: css`
1192
+ flex: 1 1 auto;
1193
+ overflow-y: auto;
1194
+ `,
1195
+ cachedMatchesContainer: css`
1196
+ flex: 1 1 auto;
1197
+ overflow-y: auto;
1198
+ max-height: 50%;
1199
+ `,
1200
+ maskedBadgeContainer: css`
1201
+ flex: 1;
1202
+ justify-content: flex-end;
1203
+ display: flex;
1204
+ `,
1205
+ matchDetails: css`
1206
+ display: flex;
1207
+ flex-direction: column;
1208
+ padding: ${tokens.size[2]};
1209
+ font-size: ${tokens.font.size.xs};
1210
+ color: ${tokens.colors.gray[300]};
1211
+ line-height: ${tokens.font.lineHeight.sm};
1212
+ `,
1213
+ matchStatus: (
1214
+ status: 'pending' | 'success' | 'error',
1215
+ isFetching: boolean,
1216
+ ) => {
1217
+ const colorMap = {
1218
+ pending: 'yellow',
1219
+ success: 'green',
1220
+ error: 'red',
1221
+ } as const
1222
+
1223
+ const color =
1224
+ isFetching && status === 'success' ? 'blue' : colorMap[status]
1225
+
1226
+ return css`
1227
+ display: flex;
1228
+ justify-content: center;
1229
+ align-items: center;
1230
+ height: 40px;
1231
+ border-radius: ${tokens.border.radius.sm};
1232
+ font-weight: ${tokens.font.weight.normal};
1233
+ background-color: ${tokens.colors[color][900]}${tokens.alpha[90]};
1234
+ color: ${tokens.colors[color][300]};
1235
+ border: 1px solid ${tokens.colors[color][600]};
1236
+ margin-bottom: ${tokens.size[2]};
1237
+ transition: all 0.25s ease-out;
1238
+ `
1239
+ },
1240
+ matchDetailsInfo: css`
1241
+ display: flex;
1242
+ justify-content: flex-end;
1243
+ flex: 1;
1244
+ `,
1245
+ matchDetailsInfoLabel: css`
1246
+ display: flex;
1247
+ `,
1248
+ mainCloseBtn: css`
1249
+ background: ${colors.darkGray[700]};
1250
+ padding: ${size[1]} ${size[2]} ${size[1]} ${size[1.5]};
1251
+ border-radius: ${border.radius.md};
1252
+ position: fixed;
1253
+ z-index: 99999;
1254
+ display: inline-flex;
1255
+ width: fit-content;
1256
+ cursor: pointer;
1257
+ appearance: none;
1258
+ border: 0;
1259
+ gap: 8px;
1260
+ align-items: center;
1261
+ border: 1px solid ${colors.gray[500]};
1262
+ font-size: ${font.size.xs};
1263
+ cursor: pointer;
1264
+ transition: all 0.25s ease-out;
1265
+
1266
+ &:hover {
1267
+ background: ${colors.darkGray[500]};
1268
+ }
1269
+ `,
1270
+ mainCloseBtnPosition: (
1271
+ position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right',
1272
+ ) => {
1273
+ const base = css`
1274
+ ${position === 'top-left' ? `top: ${size[2]}; left: ${size[2]};` : ''}
1275
+ ${position === 'top-right' ? `top: ${size[2]}; right: ${size[2]};` : ''}
1276
+ ${position === 'bottom-left'
1277
+ ? `bottom: ${size[2]}; left: ${size[2]};`
1278
+ : ''}
1279
+ ${position === 'bottom-right'
1280
+ ? `bottom: ${size[2]}; right: ${size[2]};`
1281
+ : ''}
1282
+ `
1283
+ return base
1284
+ },
1285
+ mainCloseBtnAnimation: (isOpen: boolean) => {
1286
+ if (isOpen) {
1287
+ return css`
1288
+ opacity: 1;
1289
+ pointer-events: auto;
1290
+ visibility: visible;
1291
+ `
1292
+ }
1293
+ return css`
1294
+ opacity: 0;
1295
+ pointer-events: none;
1296
+ visibility: hidden;
1297
+ `
1298
+ },
1299
+ routerLogoCloseButton: css`
1300
+ font-weight: ${font.weight.semibold};
1301
+ font-size: ${font.size.xs};
1302
+ background: linear-gradient(to right, #98f30c, #00f4a3);
1303
+ background-clip: text;
1304
+ -webkit-background-clip: text;
1305
+ line-height: 1;
1306
+ -webkit-text-fill-color: transparent;
1307
+ white-space: nowrap;
1308
+ `,
1309
+ mainCloseBtnDivider: css`
1310
+ width: 1px;
1311
+ background: ${tokens.colors.gray[600]};
1312
+ height: 100%;
1313
+ border-radius: 999999px;
1314
+ color: transparent;
1315
+ `,
1316
+ mainCloseBtnIconContainer: css`
1317
+ position: relative;
1318
+ width: ${size[5]};
1319
+ height: ${size[5]};
1320
+ background: pink;
1321
+ border-radius: 999999px;
1322
+ overflow: hidden;
1323
+ `,
1324
+ mainCloseBtnIconOuter: css`
1325
+ width: ${size[5]};
1326
+ height: ${size[5]};
1327
+ position: absolute;
1328
+ top: 50%;
1329
+ left: 50%;
1330
+ transform: translate(-50%, -50%);
1331
+ filter: blur(3px) saturate(1.8) contrast(2);
1332
+ `,
1333
+ mainCloseBtnIconInner: css`
1334
+ width: ${size[4]};
1335
+ height: ${size[4]};
1336
+ position: absolute;
1337
+ top: 50%;
1338
+ left: 50%;
1339
+ transform: translate(-50%, -50%);
1340
+ `,
1341
+ panelCloseBtn: css`
1342
+ position: absolute;
1343
+ cursor: pointer;
1344
+ z-index: 100001;
1345
+ display: flex;
1346
+ align-items: center;
1347
+ justify-content: center;
1348
+ outline: none;
1349
+ background-color: ${colors.darkGray[700]};
1350
+ &:hover {
1351
+ background-color: ${colors.darkGray[500]};
1352
+ }
1353
+
1354
+ top: 0;
1355
+ right: ${size[2]};
1356
+ transform: translate(0, -100%);
1357
+ border-right: ${colors.darkGray[300]} 1px solid;
1358
+ border-left: ${colors.darkGray[300]} 1px solid;
1359
+ border-top: ${colors.darkGray[300]} 1px solid;
1360
+ border-bottom: none;
1361
+ border-radius: ${border.radius.sm} ${border.radius.sm} 0px 0px;
1362
+ padding: ${size[1]} ${size[1.5]} ${size[0.5]} ${size[1.5]};
1363
+
1364
+ &::after {
1365
+ content: ' ';
1366
+ position: absolute;
1367
+ top: 100%;
1368
+ left: -${size[2.5]};
1369
+ height: ${size[1.5]};
1370
+ width: calc(100% + ${size[5]});
1371
+ }
1372
+ `,
1373
+ panelCloseBtnIcon: css`
1374
+ color: ${colors.gray[400]};
1375
+ width: ${size[2]};
1376
+ height: ${size[2]};
1377
+ `,
1378
+ }
1379
+ }
1380
+
1381
+ const styles = stylesFactory()