index.tsx raw

   1  import ScrollToTopButton from '@/components/ScrollToTopButton'
   2  import { Titlebar } from '@/components/Titlebar'
   3  import { Button } from '@/components/ui/button'
   4  import { ScrollArea } from '@/components/ui/scroll-area'
   5  import { useSecondaryPage } from '@/PageManager'
   6  import { DeepBrowsingProvider } from '@/providers/DeepBrowsingProvider'
   7  import { useUserPreferences } from '@/providers/UserPreferencesProvider'
   8  import { ChevronLeft } from 'lucide-react'
   9  import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'
  10  import { useTranslation } from 'react-i18next'
  11  
  12  const SecondaryPageLayout = forwardRef(
  13    (
  14      {
  15        children,
  16        index,
  17        title,
  18        controls,
  19        hideBackButton = false,
  20        hideTitlebarBottomBorder = false,
  21        displayScrollToTopButton = false,
  22        titlebar
  23      }: {
  24        children?: React.ReactNode
  25        index?: number
  26        title?: React.ReactNode
  27        controls?: React.ReactNode
  28        hideBackButton?: boolean
  29        hideTitlebarBottomBorder?: boolean
  30        displayScrollToTopButton?: boolean
  31        titlebar?: React.ReactNode
  32      },
  33      ref
  34    ) => {
  35      const scrollAreaRef = useRef<HTMLDivElement>(null)
  36      const { enableSingleColumnLayout } = useUserPreferences()
  37      const { currentIndex } = useSecondaryPage()
  38  
  39      useImperativeHandle(
  40        ref,
  41        () => ({
  42          scrollToTop: (behavior: ScrollBehavior = 'smooth') => {
  43            setTimeout(() => {
  44              if (scrollAreaRef.current) {
  45                return scrollAreaRef.current.scrollTo({ top: 0, behavior })
  46              }
  47              window.scrollTo({ top: 0, behavior })
  48            }, 10)
  49          }
  50        }),
  51        []
  52      )
  53  
  54      useEffect(() => {
  55        if (enableSingleColumnLayout) {
  56          setTimeout(() => window.scrollTo({ top: 0 }), 10)
  57          return
  58        }
  59      }, [])
  60  
  61      if (enableSingleColumnLayout) {
  62        return (
  63          <DeepBrowsingProvider active={currentIndex === index}>
  64            <div
  65              style={{
  66                paddingBottom: 'env(safe-area-inset-bottom)'
  67              }}
  68            >
  69              <SecondaryPageTitlebar
  70                title={title}
  71                controls={controls}
  72                hideBackButton={hideBackButton}
  73                hideBottomBorder={hideTitlebarBottomBorder}
  74                titlebar={titlebar}
  75              />
  76              {children}
  77            </div>
  78            {displayScrollToTopButton && <ScrollToTopButton />}
  79          </DeepBrowsingProvider>
  80        )
  81      }
  82  
  83      return (
  84        <DeepBrowsingProvider active={currentIndex === index} scrollAreaRef={scrollAreaRef}>
  85          <ScrollArea
  86            className="h-full overflow-auto"
  87            scrollBarClassName="z-30 pt-12"
  88            ref={scrollAreaRef}
  89          >
  90            <SecondaryPageTitlebar
  91              title={title}
  92              controls={controls}
  93              hideBackButton={hideBackButton}
  94              hideBottomBorder={hideTitlebarBottomBorder}
  95              titlebar={titlebar}
  96            />
  97            {children}
  98            <div className="h-4" />
  99          </ScrollArea>
 100          {displayScrollToTopButton && <ScrollToTopButton scrollAreaRef={scrollAreaRef} />}
 101        </DeepBrowsingProvider>
 102      )
 103    }
 104  )
 105  SecondaryPageLayout.displayName = 'SecondaryPageLayout'
 106  export default SecondaryPageLayout
 107  
 108  export function SecondaryPageTitlebar({
 109    title,
 110    controls,
 111    hideBackButton = false,
 112    hideBottomBorder = false,
 113    titlebar
 114  }: {
 115    title?: React.ReactNode
 116    controls?: React.ReactNode
 117    hideBackButton?: boolean
 118    hideBottomBorder?: boolean
 119    titlebar?: React.ReactNode
 120  }): JSX.Element {
 121    if (titlebar) {
 122      return (
 123        <Titlebar className="p-1" hideBottomBorder={hideBottomBorder}>
 124          {titlebar}
 125        </Titlebar>
 126      )
 127    }
 128    return (
 129      <Titlebar
 130        className="flex gap-1 p-1 items-center justify-between font-semibold"
 131        hideBottomBorder={hideBottomBorder}
 132      >
 133        {hideBackButton ? (
 134          <div className="flex gap-2 items-center pl-3 w-fit truncate text-lg font-semibold">
 135            {title}
 136          </div>
 137        ) : (
 138          <div className="flex items-center flex-1 w-0">
 139            <BackButton>{title}</BackButton>
 140          </div>
 141        )}
 142        <div className="flex-shrink-0">{controls}</div>
 143      </Titlebar>
 144    )
 145  }
 146  
 147  function BackButton({ children }: { children?: React.ReactNode }) {
 148    const { t } = useTranslation()
 149    const { pop } = useSecondaryPage()
 150  
 151    return (
 152      <Button
 153        className="flex gap-1 items-center w-fit max-w-full justify-start pl-2 pr-3"
 154        variant="ghost"
 155        size="titlebar-icon"
 156        title={t('back')}
 157        onClick={() => pop()}
 158      >
 159        <ChevronLeft />
 160        <div className="truncate text-lg font-semibold">{children}</div>
 161      </Button>
 162    )
 163  }
 164