@stoker-platform/web-app 0.5.119 → 0.5.120

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @stoker-platform/web-app
2
2
 
3
+ ## 0.5.120
4
+
5
+ ### Patch Changes
6
+
7
+ - feat: improve mobile collection transitions
8
+
3
9
  ## 0.5.119
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stoker-platform/web-app",
3
- "version": "0.5.119",
3
+ "version": "0.5.120",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "scripts": {
package/src/App.css CHANGED
@@ -1,3 +1,9 @@
1
+ html,
2
+ body {
3
+ background-color: #000000 !important;
4
+ background-image: none !important;
5
+ }
6
+
1
7
  @media (min-width: 1280px) {
2
8
  html,
3
9
  body {
@@ -8,6 +14,7 @@
8
14
  /* Print */
9
15
 
10
16
  @media print {
17
+ html,
11
18
  body {
12
19
  background-color: white !important;
13
20
  background-image: none !important;
@@ -1687,17 +1687,20 @@ function Collection({
1687
1687
  [recordTitle],
1688
1688
  )
1689
1689
 
1690
- const [showCollection, setShowCollection] = useState(false)
1690
+ const [showCollection, setShowCollection] = useState(window.innerWidth > 1280)
1691
1691
  useEffect(() => {
1692
1692
  if (isInitialized) {
1693
- setShowCollection(true)
1693
+ setTimeout(() => {
1694
+ setShowCollection(true)
1695
+ }, 150)
1694
1696
  }
1695
1697
  }, [isInitialized])
1696
1698
 
1697
1699
  if (!permissions.collections?.[labels.collection]) return null
1698
1700
 
1699
1701
  return (
1700
- !collection.singleton && (
1702
+ !collection.singleton &&
1703
+ showCollection && (
1701
1704
  <>
1702
1705
  <div
1703
1706
  ref={mainContentRef}
@@ -2417,7 +2420,7 @@ function Collection({
2417
2420
  )}
2418
2421
  </div>
2419
2422
  </div>
2420
- {tab && showCollection ? (
2423
+ {tab && isInitialized ? (
2421
2424
  <>
2422
2425
  <TabsContent
2423
2426
  value="list"
package/src/Tenant.tsx CHANGED
@@ -60,7 +60,7 @@ import { getFunctions, httpsCallable } from "firebase/functions"
60
60
  import { getApp } from "firebase/app"
61
61
  import { useTheme } from "./components/theme-provider"
62
62
 
63
- const MOBILE_SHEET_CLOSE_MS = 320
63
+ const MOBILE_SHEET_CLOSE_MS = 400
64
64
 
65
65
  function Tenant() {
66
66
  const [dialogContent, setDialogContent] = useDialog()
@@ -104,6 +104,10 @@ function Tenant() {
104
104
  const [search, setSearch] = useState<string>("")
105
105
  const [searchFocused, setSearchFocused] = useState(false)
106
106
  const [background, setBackground] = useState<Background | undefined>(undefined)
107
+ const [backgroundStyle, setBackgroundStyle] = useState<{
108
+ backgroundColor: string
109
+ backgroundImage: string
110
+ }>({ backgroundColor: "transparent", backgroundImage: "none" })
107
111
  const [sidebarOpen, setSidebarOpen] = useState(false)
108
112
 
109
113
  const { unsubscribe } = useCache()
@@ -242,29 +246,18 @@ function Tenant() {
242
246
 
243
247
  useEffect(() => {
244
248
  const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches
249
+ const resolveTheme = theme === "system" || theme === undefined ? (prefersDark ? "dark" : "light") : theme
245
250
  if (background) {
246
- if (theme === "light") {
247
- document.body.style.backgroundColor = background?.light?.color || "transparent"
248
- document.body.style.backgroundImage = background?.light?.image || "none"
249
- } else if (theme === "dark") {
250
- document.body.style.backgroundColor = background?.dark?.color || "transparent"
251
- document.body.style.backgroundImage = background?.dark?.image || "none"
252
- } else if (prefersDark) {
253
- document.body.style.backgroundColor = background?.dark?.color || "transparent"
254
- document.body.style.backgroundImage = background?.dark?.image || "none"
255
- } else {
256
- document.body.style.backgroundColor = background?.light?.color || "transparent"
257
- document.body.style.backgroundImage = background?.light?.image || "none"
258
- }
251
+ const variant = resolveTheme === "dark" ? background.dark : background.light
252
+ setBackgroundStyle({
253
+ backgroundColor: variant?.color || "transparent",
254
+ backgroundImage: variant?.image || "none",
255
+ })
259
256
  } else {
260
- if (theme === "dark") {
261
- document.body.style.backgroundColor = "#000000"
262
- } else if (theme === "light") {
263
- document.body.style.backgroundColor = "#ffffff"
264
- } else {
265
- document.body.style.backgroundColor = prefersDark ? "#000000" : "#ffffff"
266
- }
267
- document.body.style.backgroundImage = "none"
257
+ setBackgroundStyle({
258
+ backgroundColor: resolveTheme === "dark" ? "#000000" : "#ffffff",
259
+ backgroundImage: "none",
260
+ })
268
261
  }
269
262
  }, [background, theme])
270
263
 
@@ -297,9 +290,9 @@ function Tenant() {
297
290
 
298
291
  const navigateFromSidebar = useCallback(
299
292
  (path: string) => {
300
- setSidebarOpen(false)
293
+ runViewTransition(() => navigate(path))
301
294
  window.setTimeout(() => {
302
- runViewTransition(() => navigate(path))
295
+ setSidebarOpen(false)
303
296
  }, MOBILE_SHEET_CLOSE_MS)
304
297
  },
305
298
  [navigate],
@@ -504,269 +497,283 @@ function Tenant() {
504
497
  return (
505
498
  appName &&
506
499
  collectionTitles && (
507
- <>
508
- <Helmet>
509
- {meta?.description && <meta name="description" content={meta.description} />}
510
- {meta?.icons ? (
511
- meta.icons.map((icon) => (
512
- <link key={icon.rel} rel={icon.rel} type={icon.type} href={icon.url} />
513
- ))
514
- ) : (
515
- <link key="favicon" rel="icon" type="image/png" href="./favicon.ico" />
516
- )}
517
- </Helmet>
518
-
519
- <Dialog
520
- open={dialogContent !== null}
521
- onOpenChange={() => {
522
- if (!dialogContent?.disableClose) {
523
- setDialogContent(null)
524
- }
525
- }}
526
- >
527
- <DialogContent
528
- onEscapeKeyDown={(e) => {
529
- if (dialogContent?.disableClose) {
530
- e.preventDefault()
500
+ <div className="relative isolate min-h-dvh">
501
+ <div
502
+ aria-hidden="true"
503
+ className="absolute inset-0 z-0 pointer-events-none print:hidden"
504
+ style={backgroundStyle}
505
+ />
506
+ <div className="relative z-10 flex min-h-dvh flex-col">
507
+ <Helmet>
508
+ {meta?.description && <meta name="description" content={meta.description} />}
509
+ {meta?.icons ? (
510
+ meta.icons.map((icon) => (
511
+ <link key={icon.rel} rel={icon.rel} type={icon.type} href={icon.url} />
512
+ ))
513
+ ) : (
514
+ <link key="favicon" rel="icon" type="image/png" href="./favicon.ico" />
515
+ )}
516
+ </Helmet>
517
+
518
+ <Dialog
519
+ open={dialogContent !== null}
520
+ onOpenChange={() => {
521
+ if (!dialogContent?.disableClose) {
522
+ setDialogContent(null)
531
523
  }
532
524
  }}
533
- hideCloseButton={dialogContent?.disableClose}
534
525
  >
535
- <DialogHeader>
536
- <DialogTitle>{dialogContent?.title}</DialogTitle>
537
- <DialogDescription>{dialogContent?.description}</DialogDescription>
538
- </DialogHeader>
539
-
540
- {mfaDialog && (
541
- <div className="flex flex-col gap-4 items-center">
542
- <QRCodeSVG value={mfaDialog.totpUri} size={180} />
543
- <div className="w-full flex flex-col items-center">
544
- <label htmlFor="mfa-code" className="mb-1">
545
- Enter 6-digit code
546
- </label>
547
- <Input
548
- id="mfa-code"
549
- type="text"
550
- inputMode="numeric"
551
- pattern="[0-9]{6}"
552
- maxLength={6}
553
- value={mfaCode}
554
- onChange={(e) => {
555
- const value = e.target.value.replace(/[^0-9]/g, "")
556
- setMfaCode(value)
557
- mfaCodeRef.current = value
558
- setMfaError("")
559
- }}
560
- className="text-center w-32"
561
- />
562
- {mfaError && <div className="text-destructive text-xs mt-1">{mfaError}</div>}
563
- </div>
564
- </div>
565
- )}
566
-
567
- {(!dialogContent?.disableClose || dialogContent?.buttons) && (
568
- <DialogFooter>
569
- <div className="flex gap-2 justify-end">
570
- {dialogContent?.buttons?.map((button) => (
571
- <Button key={button.label} onClick={button.onClick}>
572
- {button.label}
573
- </Button>
574
- ))}
575
- {!dialogContent?.disableClose && (
576
- <Button onClick={() => setDialogContent(null)} variant="outline">
577
- Close
578
- </Button>
579
- )}
580
- </div>
581
- </DialogFooter>
582
- )}
583
- </DialogContent>
584
- </Dialog>
585
-
586
- <header className="sticky top-0 z-50 flex h-16 items-center gap-4 border-b border-[rgb(39,39,42)] bg-black px-4 lg:px-6 print:hidden select-none">
587
- <nav className="hidden h-full flex-col gap-6 text-lg font-medium lg:flex lg:flex-row lg:items-center lg:text-sm lg:gap-6 dark">
588
- <button
589
- className="flex h-full items-center gap-2"
590
- key="home"
591
- onClick={() => {
592
- runViewTransition(() => navigate("/"))
526
+ <DialogContent
527
+ onEscapeKeyDown={(e) => {
528
+ if (dialogContent?.disableClose) {
529
+ e.preventDefault()
530
+ }
593
531
  }}
532
+ hideCloseButton={dialogContent?.disableClose}
594
533
  >
595
- <img src={logo || defaultLogo} alt="Logo" className="h-8 mr-2" />
596
- </button>
597
- {links}
598
- </nav>
599
- <div className="hidden items-center text-sm font-medium gap-4 lg:ml-auto lg:flex lg:gap-4">
600
- {isLoading && connectionStatus === "online" && <LoadingSpinner size={7} dark />}
601
- {connectionStatus === "offline" && <Badge variant="destructive">Offline</Badge>}
602
- {showSearchAll && (
603
- <div className="ml-auto flex-1 lg:flex-initial text-primary dark">
604
- <Popover open={searchFocused && Boolean(search)}>
605
- <PopoverTrigger asChild>
606
- <search className="relative">
607
- <Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
608
- <Input
609
- type="search"
610
- value={search}
611
- onChange={(e) => setSearch(e.target.value)}
612
- placeholder="Search all..."
613
- className="pl-8 lg:w-[195px] xl:w-[275px]"
614
- onFocus={() => {
615
- setTimeout(() => {
616
- setSearchFocused(true)
617
- }, 100)
618
- }}
619
- onBlur={() => setSearchFocused(false)}
620
- onKeyDown={(e) => {
621
- if (e.key === "Escape") {
622
- setSearchFocused(false)
623
- } else {
624
- setSearchFocused(true)
625
- }
626
- }}
627
- />
628
- </search>
629
- </PopoverTrigger>
630
- {search && showSearchAll && <SearchAll query={search} />}
631
- </Popover>
632
- </div>
633
- )}
634
- <ModeToggle />
635
- {enableMfa && (mfaActive || mfaEnabled) && !mfaRevoked && (
636
- <DropdownMenu>
637
- <DropdownMenuTrigger asChild>
638
- <Button variant="outline" size="icon">
639
- <User />
640
- </Button>
641
- </DropdownMenuTrigger>
642
- <DropdownMenuContent className="w-56 dark print:hidden">
643
- <DropdownMenuGroup>
644
- <DropdownMenuItem
645
- onClick={() => {
646
- revokeMfa()
534
+ <DialogHeader>
535
+ <DialogTitle>{dialogContent?.title}</DialogTitle>
536
+ <DialogDescription>{dialogContent?.description}</DialogDescription>
537
+ </DialogHeader>
538
+
539
+ {mfaDialog && (
540
+ <div className="flex flex-col gap-4 items-center">
541
+ <QRCodeSVG value={mfaDialog.totpUri} size={180} />
542
+ <div className="w-full flex flex-col items-center">
543
+ <label htmlFor="mfa-code" className="mb-1">
544
+ Enter 6-digit code
545
+ </label>
546
+ <Input
547
+ id="mfa-code"
548
+ type="text"
549
+ inputMode="numeric"
550
+ pattern="[0-9]{6}"
551
+ maxLength={6}
552
+ value={mfaCode}
553
+ onChange={(e) => {
554
+ const value = e.target.value.replace(/[^0-9]/g, "")
555
+ setMfaCode(value)
556
+ mfaCodeRef.current = value
557
+ setMfaError("")
647
558
  }}
648
- >
649
- Revoke MFA
650
- </DropdownMenuItem>
651
- </DropdownMenuGroup>
652
- </DropdownMenuContent>
653
- </DropdownMenu>
654
- )}
655
- <button className="text-muted-foreground hover:text-foreground dark" onClick={signOutUser}>
656
- Sign Out
657
- </button>
658
- </div>
659
-
660
- <Sheet open={sidebarOpen} onOpenChange={setSidebarOpen}>
661
- <SheetTrigger asChild>
662
- <Button size="icon" variant="outline" className="absolute left-4 lg:hidden dark">
663
- <PanelLeft className="h-5 w-5 text-primary" />
664
- <span className="sr-only">Toggle Menu</span>
665
- </Button>
666
- </SheetTrigger>
667
- <SheetContent side="left" className="sm:max-w-xs overflow-y-auto">
668
- <nav className="grid gap-6 text-lg font-medium pt-12">
669
- {hasDashboard && (
670
- <button
671
- className={
672
- window.location.pathname === "/"
673
- ? "flex items-center gap-4 px-2.5 text-foreground"
674
- : "flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
675
- }
676
- onClick={() => navigateFromSidebar("/")}
677
- >
678
- <ChartBar className="h-5 w-5" />
679
- Dashboard
680
- </button>
681
- )}
682
- {sidebarMenu.map((group) => {
683
- if (group.collections.length === 1) {
684
- const className = "flex items-center gap-4 px-2.5 text-primary"
685
- return (
686
- <button
687
- key={group.collections[0]}
688
- className={
689
- isMatch(group.collections[0])
690
- ? cn(className, "text-foreground")
691
- : cn(className, "text-muted-foreground hover:text-foreground")
692
- }
693
- onClick={() =>
694
- navigateFromSidebar(`/${group.collections[0].toLowerCase()}`)
695
- }
559
+ className="text-center w-32"
560
+ />
561
+ {mfaError && <div className="text-destructive text-xs mt-1">{mfaError}</div>}
562
+ </div>
563
+ </div>
564
+ )}
565
+
566
+ {(!dialogContent?.disableClose || dialogContent?.buttons) && (
567
+ <DialogFooter>
568
+ <div className="flex gap-2 justify-end">
569
+ {dialogContent?.buttons?.map((button) => (
570
+ <Button key={button.label} onClick={button.onClick}>
571
+ {button.label}
572
+ </Button>
573
+ ))}
574
+ {!dialogContent?.disableClose && (
575
+ <Button onClick={() => setDialogContent(null)} variant="outline">
576
+ Close
577
+ </Button>
578
+ )}
579
+ </div>
580
+ </DialogFooter>
581
+ )}
582
+ </DialogContent>
583
+ </Dialog>
584
+
585
+ <header className="sticky top-0 z-50 flex h-16 items-center gap-4 border-b border-[rgb(39,39,42)] bg-black px-4 lg:px-6 print:hidden select-none">
586
+ <nav className="hidden h-full flex-col gap-6 text-lg font-medium lg:flex lg:flex-row lg:items-center lg:text-sm lg:gap-6 dark">
587
+ <button
588
+ className="flex h-full items-center gap-2"
589
+ key="home"
590
+ onClick={() => {
591
+ runViewTransition(() => navigate("/"))
592
+ }}
593
+ >
594
+ <img src={logo || defaultLogo} alt="Logo" className="h-8 mr-2" />
595
+ </button>
596
+ {links}
597
+ </nav>
598
+ <div className="hidden items-center text-sm font-medium gap-4 lg:ml-auto lg:flex lg:gap-4">
599
+ {isLoading && connectionStatus === "online" && <LoadingSpinner size={7} dark />}
600
+ {connectionStatus === "offline" && <Badge variant="destructive">Offline</Badge>}
601
+ {showSearchAll && (
602
+ <div className="ml-auto flex-1 lg:flex-initial text-primary dark">
603
+ <Popover open={searchFocused && Boolean(search)}>
604
+ <PopoverTrigger asChild>
605
+ <search className="relative">
606
+ <Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
607
+ <Input
608
+ type="search"
609
+ value={search}
610
+ onChange={(e) => setSearch(e.target.value)}
611
+ placeholder="Search all..."
612
+ className="pl-8 lg:w-[195px] xl:w-[275px]"
613
+ onFocus={() => {
614
+ setTimeout(() => {
615
+ setSearchFocused(true)
616
+ }, 100)
617
+ }}
618
+ onBlur={() => setSearchFocused(false)}
619
+ onKeyDown={(e) => {
620
+ if (e.key === "Escape") {
621
+ setSearchFocused(false)
622
+ } else {
623
+ setSearchFocused(true)
624
+ }
625
+ }}
626
+ />
627
+ </search>
628
+ </PopoverTrigger>
629
+ {search && showSearchAll && <SearchAll query={search} />}
630
+ </Popover>
631
+ </div>
632
+ )}
633
+ <ModeToggle />
634
+ {enableMfa && (mfaActive || mfaEnabled) && !mfaRevoked && (
635
+ <DropdownMenu>
636
+ <DropdownMenuTrigger asChild>
637
+ <Button variant="outline" size="icon">
638
+ <User />
639
+ </Button>
640
+ </DropdownMenuTrigger>
641
+ <DropdownMenuContent className="w-56 dark print:hidden">
642
+ <DropdownMenuGroup>
643
+ <DropdownMenuItem
644
+ onClick={() => {
645
+ revokeMfa()
646
+ }}
696
647
  >
697
- {/* eslint-disable security/detect-object-injection */}
698
- {iconNames[group.collections[0]]
699
- ? createElement(iconNames[group.collections[0]], {
700
- className: "h-5 w-5",
701
- })
702
- : null}
703
- {collectionTitles[group.collections[0]]}
704
- {/* eslint-enable security/detect-object-injection */}
705
- </button>
706
- )
707
- } else {
708
- return group.collections.map((collection) => {
648
+ Revoke MFA
649
+ </DropdownMenuItem>
650
+ </DropdownMenuGroup>
651
+ </DropdownMenuContent>
652
+ </DropdownMenu>
653
+ )}
654
+ <button className="text-muted-foreground hover:text-foreground dark" onClick={signOutUser}>
655
+ Sign Out
656
+ </button>
657
+ </div>
658
+
659
+ <Sheet open={sidebarOpen} onOpenChange={setSidebarOpen}>
660
+ <SheetTrigger asChild>
661
+ <Button size="icon" variant="outline" className="absolute left-4 lg:hidden dark">
662
+ <PanelLeft className="h-5 w-5 text-primary" />
663
+ <span className="sr-only">Toggle Menu</span>
664
+ </Button>
665
+ </SheetTrigger>
666
+ <SheetContent side="left" className="sm:max-w-xs overflow-y-auto">
667
+ <nav className="grid gap-6 text-lg font-medium pt-12">
668
+ {hasDashboard && (
669
+ <button
670
+ className={
671
+ window.location.pathname === "/"
672
+ ? "flex items-center gap-4 px-2.5 text-foreground"
673
+ : "flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
674
+ }
675
+ onClick={() => navigateFromSidebar("/")}
676
+ >
677
+ <ChartBar className="h-5 w-5" />
678
+ Dashboard
679
+ </button>
680
+ )}
681
+ {sidebarMenu.map((group) => {
682
+ if (group.collections.length === 1) {
709
683
  const className = "flex items-center gap-4 px-2.5 text-primary"
710
684
  return (
711
685
  <button
712
- key={collection}
686
+ key={group.collections[0]}
713
687
  className={
714
- isMatch(collection)
688
+ isMatch(group.collections[0])
715
689
  ? cn(className, "text-foreground")
716
690
  : cn(
717
691
  className,
718
692
  "text-muted-foreground hover:text-foreground",
719
693
  )
720
694
  }
721
- onClick={() => navigateFromSidebar(`/${collection.toLowerCase()}`)}
695
+ onClick={() =>
696
+ navigateFromSidebar(`/${group.collections[0].toLowerCase()}`)
697
+ }
722
698
  >
723
699
  {/* eslint-disable security/detect-object-injection */}
724
- {iconNames[collection]
725
- ? createElement(iconNames[collection], { className: "h-5 w-5" })
700
+ {iconNames[group.collections[0]]
701
+ ? createElement(iconNames[group.collections[0]], {
702
+ className: "h-5 w-5",
703
+ })
726
704
  : null}
727
- {collectionTitles[collection]}
705
+ {collectionTitles[group.collections[0]]}
728
706
  {/* eslint-enable security/detect-object-injection */}
729
707
  </button>
730
708
  )
731
- })
732
- }
733
- })}
734
- {enableMfa && ((!mfaActive && !mfaEnabled) || mfaRevoked) && (
709
+ } else {
710
+ return group.collections.map((collection) => {
711
+ const className = "flex items-center gap-4 px-2.5 text-primary"
712
+ return (
713
+ <button
714
+ key={collection}
715
+ className={
716
+ isMatch(collection)
717
+ ? cn(className, "text-foreground")
718
+ : cn(
719
+ className,
720
+ "text-muted-foreground hover:text-foreground",
721
+ )
722
+ }
723
+ onClick={() =>
724
+ navigateFromSidebar(`/${collection.toLowerCase()}`)
725
+ }
726
+ >
727
+ {/* eslint-disable security/detect-object-injection */}
728
+ {iconNames[collection]
729
+ ? createElement(iconNames[collection], {
730
+ className: "h-5 w-5",
731
+ })
732
+ : null}
733
+ {collectionTitles[collection]}
734
+ {/* eslint-enable security/detect-object-injection */}
735
+ </button>
736
+ )
737
+ })
738
+ }
739
+ })}
740
+ {enableMfa && ((!mfaActive && !mfaEnabled) || mfaRevoked) && (
741
+ <button
742
+ key="mfa-enroll-mobile"
743
+ className="flex items-center gap-4 px-2.5 bg-destructive p-4 rounded-md text-white"
744
+ onClick={() => multiFactorEnroll(user, getMultiFactorCode)}
745
+ >
746
+ Enable MFA
747
+ </button>
748
+ )}
749
+ </nav>
750
+ <div className="grid gap-6 text-lg mt-6 font-medium">
735
751
  <button
736
- key="mfa-enroll-mobile"
737
- className="flex items-center gap-4 px-2.5 bg-destructive p-4 rounded-md text-white"
738
- onClick={() => multiFactorEnroll(user, getMultiFactorCode)}
752
+ className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
753
+ onClick={signOutUser}
739
754
  >
740
- Enable MFA
755
+ Sign Out
741
756
  </button>
742
- )}
743
- </nav>
744
- <div className="grid gap-6 text-lg mt-6 font-medium">
745
- <button
746
- className="flex items-center gap-4 px-2.5 text-muted-foreground hover:text-foreground"
747
- onClick={signOutUser}
748
- >
749
- Sign Out
750
- </button>
751
- <div>
752
- <ModeToggle />
757
+ <div>
758
+ <ModeToggle />
759
+ </div>
753
760
  </div>
754
- </div>
755
- </SheetContent>
756
- </Sheet>
757
- <div className="flex justify-center items-center w-full lg:hidden">
758
- <img src={logo || defaultLogo} alt="Logo" className="h-8" />
759
- </div>
760
- <div className="absolute right-4 flex justify-center items-center gap-4 ml-auto lg:hidden">
761
- {isLoading && connectionStatus === "online" && <LoadingSpinner size={7} dark />}
762
- {connectionStatus === "offline" && <Badge variant="destructive">Offline</Badge>}
763
- </div>
764
- </header>
765
- <main className="flex w-full flex-col">
766
- <Outlet />
767
- </main>
768
- <Toaster />
769
- </>
761
+ </SheetContent>
762
+ </Sheet>
763
+ <div className="flex justify-center items-center w-full lg:hidden">
764
+ <img src={logo || defaultLogo} alt="Logo" className="h-8" />
765
+ </div>
766
+ <div className="absolute right-4 flex justify-center items-center gap-4 ml-auto lg:hidden">
767
+ {isLoading && connectionStatus === "online" && <LoadingSpinner size={7} dark />}
768
+ {connectionStatus === "offline" && <Badge variant="destructive">Offline</Badge>}
769
+ </div>
770
+ </header>
771
+ <main className="flex w-full flex-col">
772
+ <Outlet />
773
+ </main>
774
+ <Toaster />
775
+ </div>
776
+ </div>
770
777
  )
771
778
  )
772
779
  }
package/src/index.css CHANGED
@@ -88,6 +88,6 @@
88
88
  @apply border-border;
89
89
  }
90
90
  body {
91
- @apply bg-background text-foreground;
91
+ @apply text-foreground;
92
92
  }
93
93
  }