@prose-reader/react-reader 1.117.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 (37) hide show
  1. package/README.md +50 -0
  2. package/package.json +36 -0
  3. package/src/common/useFullscreen.ts +44 -0
  4. package/src/components/ui/avatar.tsx +74 -0
  5. package/src/components/ui/checkbox.tsx +25 -0
  6. package/src/components/ui/close-button.tsx +17 -0
  7. package/src/components/ui/color-mode.tsx +75 -0
  8. package/src/components/ui/dialog.tsx +62 -0
  9. package/src/components/ui/drawer.tsx +52 -0
  10. package/src/components/ui/field.tsx +33 -0
  11. package/src/components/ui/input-group.tsx +53 -0
  12. package/src/components/ui/popover.tsx +59 -0
  13. package/src/components/ui/progress.tsx +34 -0
  14. package/src/components/ui/provider.tsx +12 -0
  15. package/src/components/ui/radio.tsx +24 -0
  16. package/src/components/ui/slider.tsx +82 -0
  17. package/src/components/ui/toggle-tip.tsx +70 -0
  18. package/src/components/ui/tooltip.tsx +46 -0
  19. package/src/context/ReactReaderProvider.tsx +14 -0
  20. package/src/context/context.ts +6 -0
  21. package/src/context/useReader.ts +9 -0
  22. package/src/index.ts +2 -0
  23. package/src/navigation/QuickMenu/BottomBar.tsx +65 -0
  24. package/src/navigation/QuickMenu/PaginationInfoSection.tsx +62 -0
  25. package/src/navigation/QuickMenu/QuickBar.tsx +40 -0
  26. package/src/navigation/QuickMenu/QuickMenu.tsx +22 -0
  27. package/src/navigation/QuickMenu/Scrubber.tsx +138 -0
  28. package/src/navigation/QuickMenu/TimeIndicator.tsx +29 -0
  29. package/src/navigation/QuickMenu/TopBar.tsx +72 -0
  30. package/src/navigation/useNavigationContext.ts +46 -0
  31. package/src/pagination/usePagination.ts +29 -0
  32. package/src/settings/useSettings.ts +9 -0
  33. package/src/vite-env.d.ts +1 -0
  34. package/tsconfig.app.json +26 -0
  35. package/tsconfig.json +7 -0
  36. package/tsconfig.node.json +22 -0
  37. package/vite.config.ts +32 -0
@@ -0,0 +1,82 @@
1
+ import { Slider as ChakraSlider, For, HStack } from "@chakra-ui/react"
2
+ import * as React from "react"
3
+
4
+ export interface SliderProps extends ChakraSlider.RootProps {
5
+ marks?: Array<number | { value: number; label: React.ReactNode }>
6
+ label?: React.ReactNode
7
+ showValue?: boolean
8
+ }
9
+
10
+ export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
11
+ function Slider(props, ref) {
12
+ const { marks: marksProp, label, showValue, ...rest } = props
13
+ const value = props.defaultValue ?? props.value
14
+
15
+ const marks = marksProp?.map((mark) => {
16
+ if (typeof mark === "number") return { value: mark, label: undefined }
17
+ return mark
18
+ })
19
+
20
+ const hasMarkLabel = !!marks?.some((mark) => mark.label)
21
+
22
+ return (
23
+ <ChakraSlider.Root ref={ref} thumbAlignment="center" {...rest}>
24
+ {label && !showValue && (
25
+ <ChakraSlider.Label>{label}</ChakraSlider.Label>
26
+ )}
27
+ {label && showValue && (
28
+ <HStack justify="space-between">
29
+ <ChakraSlider.Label>{label}</ChakraSlider.Label>
30
+ <ChakraSlider.ValueText />
31
+ </HStack>
32
+ )}
33
+ <ChakraSlider.Control data-has-mark-label={hasMarkLabel || undefined}>
34
+ <ChakraSlider.Track>
35
+ <ChakraSlider.Range />
36
+ </ChakraSlider.Track>
37
+ <SliderThumbs value={value} />
38
+ <SliderMarks marks={marks} />
39
+ </ChakraSlider.Control>
40
+ </ChakraSlider.Root>
41
+ )
42
+ },
43
+ )
44
+
45
+ function SliderThumbs(props: { value?: number[] }) {
46
+ const { value } = props
47
+ return (
48
+ <For each={value}>
49
+ {(_, index) => (
50
+ <ChakraSlider.Thumb key={index} index={index}>
51
+ <ChakraSlider.HiddenInput />
52
+ </ChakraSlider.Thumb>
53
+ )}
54
+ </For>
55
+ )
56
+ }
57
+
58
+ interface SliderMarksProps {
59
+ marks?: Array<number | { value: number; label: React.ReactNode }>
60
+ }
61
+
62
+ const SliderMarks = React.forwardRef<HTMLDivElement, SliderMarksProps>(
63
+ function SliderMarks(props, ref) {
64
+ const { marks } = props
65
+ if (!marks?.length) return null
66
+
67
+ return (
68
+ <ChakraSlider.MarkerGroup ref={ref}>
69
+ {marks.map((mark, index) => {
70
+ const value = typeof mark === "number" ? mark : mark.value
71
+ const label = typeof mark === "number" ? undefined : mark.label
72
+ return (
73
+ <ChakraSlider.Marker key={index} value={value}>
74
+ <ChakraSlider.MarkerIndicator />
75
+ {label}
76
+ </ChakraSlider.Marker>
77
+ )
78
+ })}
79
+ </ChakraSlider.MarkerGroup>
80
+ )
81
+ },
82
+ )
@@ -0,0 +1,70 @@
1
+ import { Popover as ChakraPopover, IconButton, Portal } from "@chakra-ui/react"
2
+ import * as React from "react"
3
+ import { HiOutlineInformationCircle } from "react-icons/hi"
4
+
5
+ export interface ToggleTipProps extends ChakraPopover.RootProps {
6
+ showArrow?: boolean
7
+ portalled?: boolean
8
+ portalRef?: React.RefObject<HTMLElement>
9
+ content?: React.ReactNode
10
+ }
11
+
12
+ export const ToggleTip = React.forwardRef<HTMLDivElement, ToggleTipProps>(
13
+ function ToggleTip(props, ref) {
14
+ const {
15
+ showArrow,
16
+ children,
17
+ portalled = true,
18
+ content,
19
+ portalRef,
20
+ ...rest
21
+ } = props
22
+
23
+ return (
24
+ <ChakraPopover.Root
25
+ {...rest}
26
+ positioning={{ ...rest.positioning, gutter: 4 }}
27
+ >
28
+ <ChakraPopover.Trigger asChild>{children}</ChakraPopover.Trigger>
29
+ <Portal disabled={!portalled} container={portalRef}>
30
+ <ChakraPopover.Positioner>
31
+ <ChakraPopover.Content
32
+ width="auto"
33
+ px="2"
34
+ py="1"
35
+ textStyle="xs"
36
+ rounded="sm"
37
+ ref={ref}
38
+ >
39
+ {showArrow && (
40
+ <ChakraPopover.Arrow>
41
+ <ChakraPopover.ArrowTip />
42
+ </ChakraPopover.Arrow>
43
+ )}
44
+ {content}
45
+ </ChakraPopover.Content>
46
+ </ChakraPopover.Positioner>
47
+ </Portal>
48
+ </ChakraPopover.Root>
49
+ )
50
+ },
51
+ )
52
+
53
+ export const InfoTip = React.forwardRef<
54
+ HTMLDivElement,
55
+ Partial<ToggleTipProps>
56
+ >(function InfoTip(props, ref) {
57
+ const { children, ...rest } = props
58
+ return (
59
+ <ToggleTip content={children} {...rest} ref={ref}>
60
+ <IconButton
61
+ variant="ghost"
62
+ aria-label="info"
63
+ size="2xs"
64
+ colorPalette="gray"
65
+ >
66
+ <HiOutlineInformationCircle />
67
+ </IconButton>
68
+ </ToggleTip>
69
+ )
70
+ })
@@ -0,0 +1,46 @@
1
+ import { Tooltip as ChakraTooltip, Portal } from "@chakra-ui/react"
2
+ import * as React from "react"
3
+
4
+ export interface TooltipProps extends ChakraTooltip.RootProps {
5
+ showArrow?: boolean
6
+ portalled?: boolean
7
+ portalRef?: React.RefObject<HTMLElement>
8
+ content: React.ReactNode
9
+ contentProps?: ChakraTooltip.ContentProps
10
+ disabled?: boolean
11
+ }
12
+
13
+ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
14
+ function Tooltip(props, ref) {
15
+ const {
16
+ showArrow,
17
+ children,
18
+ disabled,
19
+ portalled = true,
20
+ content,
21
+ contentProps,
22
+ portalRef,
23
+ ...rest
24
+ } = props
25
+
26
+ if (disabled) return children
27
+
28
+ return (
29
+ <ChakraTooltip.Root {...rest}>
30
+ <ChakraTooltip.Trigger asChild>{children}</ChakraTooltip.Trigger>
31
+ <Portal disabled={!portalled} container={portalRef}>
32
+ <ChakraTooltip.Positioner>
33
+ <ChakraTooltip.Content ref={ref} {...contentProps}>
34
+ {showArrow && (
35
+ <ChakraTooltip.Arrow>
36
+ <ChakraTooltip.ArrowTip />
37
+ </ChakraTooltip.Arrow>
38
+ )}
39
+ {content}
40
+ </ChakraTooltip.Content>
41
+ </ChakraTooltip.Positioner>
42
+ </Portal>
43
+ </ChakraTooltip.Root>
44
+ )
45
+ },
46
+ )
@@ -0,0 +1,14 @@
1
+ import type { Reader } from "@prose-reader/core"
2
+ import { memo } from "react"
3
+ import { ReaderContext } from "./context"
4
+
5
+ export const ReactReaderProvider = memo(
6
+ ({
7
+ children,
8
+ reader,
9
+ }: { children?: React.ReactNode; reader: Reader | undefined }) => {
10
+ return (
11
+ <ReaderContext.Provider value={reader}>{children}</ReaderContext.Provider>
12
+ )
13
+ },
14
+ )
@@ -0,0 +1,6 @@
1
+ import type { Reader } from "@prose-reader/core"
2
+ import { type Context, createContext } from "react"
3
+
4
+ export const ReaderContext: Context<Reader | undefined> = createContext<
5
+ Reader | undefined
6
+ >(undefined)
@@ -0,0 +1,9 @@
1
+ import type { Reader } from "@prose-reader/core"
2
+ import { useContext } from "react"
3
+ import { ReaderContext } from "./context"
4
+
5
+ export const useReader = (): Reader | undefined => {
6
+ const context = useContext(ReaderContext)
7
+
8
+ return context
9
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./navigation/QuickMenu/QuickMenu"
2
+ export * from "./context/ReactReaderProvider"
@@ -0,0 +1,65 @@
1
+ import { Box, IconButton, Stack } from "@chakra-ui/react"
2
+ import {
3
+ RxDoubleArrowDown,
4
+ RxDoubleArrowLeft,
5
+ RxDoubleArrowRight,
6
+ RxDoubleArrowUp,
7
+ } from "react-icons/rx"
8
+ import { useObserve } from "reactjrx"
9
+ import { useReader } from "../../context/useReader"
10
+ import { PaginationInfoSection } from "./PaginationInfoSection"
11
+ import { QuickBar } from "./QuickBar"
12
+ import { Scrubber } from "./Scrubber"
13
+ import { TimeIndicator } from "./TimeIndicator"
14
+
15
+ export const BottomBar = ({ open }: { open: boolean }) => {
16
+ const reader = useReader()
17
+ const navigation = useObserve(() => reader?.navigation.state$, [reader])
18
+ const settings = useObserve(() => reader?.settings.values$, [reader])
19
+ const isVerticalDirection = settings?.computedPageTurnDirection === "vertical"
20
+
21
+ return (
22
+ <QuickBar present={open} position="bottom" height={130}>
23
+ <IconButton
24
+ aria-label="left"
25
+ size="lg"
26
+ variant="ghost"
27
+ flexShrink={0}
28
+ onClick={() => reader?.navigation.goToLeftOrTopSpineItem()}
29
+ disabled={
30
+ !navigation?.canGoLeftSpineItem && !navigation?.canGoTopSpineItem
31
+ }
32
+ >
33
+ {isVerticalDirection ? <RxDoubleArrowUp /> : <RxDoubleArrowLeft />}
34
+ </IconButton>
35
+ <Stack
36
+ flex={1}
37
+ maxW={400}
38
+ gap={2}
39
+ alignItems="center"
40
+ overflow="auto"
41
+ px={4}
42
+ >
43
+ <PaginationInfoSection />
44
+ <Box height={5} maxW={300} width="100%" overflow="visible">
45
+ <Scrubber />
46
+ </Box>
47
+ </Stack>
48
+ <IconButton
49
+ aria-label="right"
50
+ size="lg"
51
+ flexShrink={0}
52
+ variant="ghost"
53
+ disabled={
54
+ !navigation?.canGoRightSpineItem && !navigation?.canGoBottomSpineItem
55
+ }
56
+ onClick={() => {
57
+ reader?.navigation.goToRightOrBottomSpineItem()
58
+ }}
59
+ >
60
+ {isVerticalDirection ? <RxDoubleArrowDown /> : <RxDoubleArrowRight />}
61
+ </IconButton>
62
+ <TimeIndicator position="absolute" bottom={0} left={0} p={2} />
63
+ </QuickBar>
64
+ )
65
+ }
@@ -0,0 +1,62 @@
1
+ import { HStack, Stack, Text } from "@chakra-ui/react"
2
+ import {
3
+ ProgressBar,
4
+ ProgressRoot,
5
+ ProgressValueText,
6
+ } from "../../components/ui/progress"
7
+ import { usePagination } from "../../pagination/usePagination"
8
+ import { useNavigationContext } from "../useNavigationContext"
9
+
10
+ export const PaginationInfoSection = () => {
11
+ const pagination = usePagination()
12
+ const {
13
+ hasOnlyOnePage,
14
+ leftPageIndex,
15
+ rightPageIndex,
16
+ totalApproximatePages,
17
+ beginAndEndAreDifferent,
18
+ } = useNavigationContext()
19
+ const progress = Math.round((pagination?.percentageEstimateOfBook ?? 0) * 100)
20
+
21
+ const buildTitleChain = (
22
+ chapterInfo: NonNullable<typeof pagination>["beginChapterInfo"],
23
+ ): string => {
24
+ if (chapterInfo?.subChapter) {
25
+ return `${chapterInfo.title} / ${buildTitleChain(chapterInfo.subChapter)}`
26
+ }
27
+ return chapterInfo?.title || ""
28
+ }
29
+
30
+ const chapterTitle = buildTitleChain(pagination?.beginChapterInfo)
31
+
32
+ return (
33
+ <Stack alignItems="center" gap={1} maxW="100%" overflow="auto">
34
+ <ProgressRoot value={progress} size="xs" width={150}>
35
+ <HStack justifyContent="space-between">
36
+ <ProgressBar width={110} />
37
+ <ProgressValueText>{`${progress}%`}</ProgressValueText>
38
+ </HStack>
39
+ </ProgressRoot>
40
+ <Text truncate maxWidth="100%" fontSize="sm" mt={1}>
41
+ {chapterTitle ? `Chapter: ${chapterTitle}` : `\u00A0`}
42
+ </Text>
43
+ {!hasOnlyOnePage && (
44
+ <HStack>
45
+ <Text fontSize="xs">
46
+ {beginAndEndAreDifferent
47
+ ? `${leftPageIndex + 1} - ${rightPageIndex + 1} of ${totalApproximatePages}`
48
+ : `${leftPageIndex + 1} of ${totalApproximatePages}`}
49
+ </Text>
50
+ {!!pagination?.hasChapters && (
51
+ <>
52
+ <Text>-</Text>
53
+ <Text fontSize="xs">
54
+ ({(pagination?.beginAbsolutePageIndex ?? 0) + 1})
55
+ </Text>
56
+ </>
57
+ )}
58
+ </HStack>
59
+ )}
60
+ </Stack>
61
+ )
62
+ }
@@ -0,0 +1,40 @@
1
+ import { Presence, type PresenceProps } from "@chakra-ui/react"
2
+ import { memo } from "react"
3
+
4
+ export const QuickBar = memo(
5
+ ({
6
+ children,
7
+ position,
8
+ ...rest
9
+ }: { position: "top" | "bottom" } & PresenceProps) => {
10
+ return (
11
+ <Presence
12
+ display="flex"
13
+ flexDirection="row"
14
+ width="100%"
15
+ position="absolute"
16
+ {...(position === "bottom" ? { bottom: 0 } : { top: 0 })}
17
+ animationName={
18
+ position === "bottom"
19
+ ? {
20
+ _open: "slide-from-bottom, fade-in",
21
+ _closed: "slide-to-bottom, fade-out",
22
+ }
23
+ : {
24
+ _open: "slide-from-top, fade-in",
25
+ _closed: "slide-to-top, fade-out",
26
+ }
27
+ }
28
+ animationDuration="moderate"
29
+ bgColor="bg.panel"
30
+ alignItems="center"
31
+ justifyContent="center"
32
+ shadow="md"
33
+ px={4}
34
+ {...rest}
35
+ >
36
+ {children}
37
+ </Presence>
38
+ )
39
+ },
40
+ )
@@ -0,0 +1,22 @@
1
+ import { memo } from "react"
2
+ import { BottomBar } from "./BottomBar"
3
+ import { TopBar } from "./TopBar"
4
+
5
+ export const QuickMenu = memo(
6
+ ({
7
+ open,
8
+ onBackClick,
9
+ onMoreClick,
10
+ }: { open: boolean; onBackClick: () => void; onMoreClick: () => void }) => {
11
+ return (
12
+ <>
13
+ <TopBar
14
+ open={open}
15
+ onBackClick={onBackClick}
16
+ onMoreClick={onMoreClick}
17
+ />
18
+ <BottomBar open={open} />
19
+ </>
20
+ )
21
+ },
22
+ )
@@ -0,0 +1,138 @@
1
+ import RcSlider from "rc-slider"
2
+ import { type ComponentProps, useCallback, useEffect } from "react"
3
+ import { useObserve, useSignal, useSubscribe } from "reactjrx"
4
+ import { useReader } from "../../context/useReader"
5
+ import { usePagination } from "../../pagination/usePagination"
6
+ import "rc-slider/assets/index.css"
7
+ import { useNavigationContext } from "../useNavigationContext"
8
+
9
+ const useSliderValues = () => {
10
+ const pagination = usePagination()
11
+ const isUsingSpread = pagination?.isUsingSpread
12
+ const { beginPageIndex: currentRealPage, totalApproximatePages = 0 } =
13
+ useNavigationContext()
14
+ const currentPage = isUsingSpread
15
+ ? Math.floor((currentRealPage || 0) / 2)
16
+ : currentRealPage
17
+ const [value, valueSignal] = useSignal({
18
+ default: currentPage || 0,
19
+ })
20
+ const min = 0
21
+ const max = Math.max(
22
+ 0,
23
+ isUsingSpread
24
+ ? Math.floor((totalApproximatePages - 1) / 2)
25
+ : totalApproximatePages - 1,
26
+ )
27
+
28
+ useEffect(() => {
29
+ valueSignal.setValue(currentPage || 0)
30
+ }, [currentPage, valueSignal])
31
+
32
+ return {
33
+ value,
34
+ valueSignal,
35
+ min,
36
+ max,
37
+ }
38
+ }
39
+
40
+ export const Scrubber = () => {
41
+ const reader = useReader()
42
+ const pagination = usePagination()
43
+ const { manifest } = useObserve(() => reader?.context.state$, []) ?? {}
44
+ const reverse = manifest?.readingDirection === "rtl"
45
+ const isUsingSpread = pagination?.isUsingSpread
46
+ const { totalApproximatePages = 0, isBeginWithinChapter } =
47
+ useNavigationContext()
48
+ const step = 1
49
+ const isScrubberWithinChapter = isBeginWithinChapter
50
+ const { value, valueSignal, min, max } = useSliderValues()
51
+
52
+ const onChange: NonNullable<ComponentProps<typeof RcSlider>["onChange"]> =
53
+ useCallback(
54
+ (values) => {
55
+ const [value = 0] = Array.isArray(values) ? values : [values]
56
+
57
+ valueSignal.setValue(value)
58
+
59
+ const pageIndex = isUsingSpread
60
+ ? Math.floor(value) * 2
61
+ : Math.floor(value)
62
+
63
+ if (!isScrubberWithinChapter) {
64
+ reader?.navigation.goToAbsolutePageIndex({
65
+ absolutePageIndex: pageIndex,
66
+ animation: false,
67
+ })
68
+ } else {
69
+ reader?.navigation.goToPageOfSpineItem({
70
+ pageIndex,
71
+ spineItemId: reader.pagination.getState().beginSpineItemIndex ?? 0,
72
+ animation: false,
73
+ })
74
+ }
75
+ },
76
+ [reader, isUsingSpread, valueSignal, isScrubberWithinChapter],
77
+ )
78
+
79
+ /**
80
+ * @note
81
+ * Scrubber can navigate fast and without lock we may end up with
82
+ * slowness due to the reader
83
+ * paginating and loading items in between.
84
+ * This is good practice (but not required) to throttle it.
85
+ */
86
+ useSubscribe(
87
+ () =>
88
+ reader?.navigation.throttleLock({
89
+ duration: 100,
90
+ trigger: valueSignal.subject,
91
+ }),
92
+ [reader, valueSignal],
93
+ )
94
+
95
+ // const marks =
96
+ // max > 1
97
+ // ? Array.from({ length: max + 1 }, (_, i) => i).reduce(
98
+ // (acc: number[], val) => [...acc, val],
99
+ // [],
100
+ // )
101
+ // : []
102
+
103
+ if (
104
+ totalApproximatePages === 1 ||
105
+ (isUsingSpread && totalApproximatePages === 2)
106
+ ) {
107
+ return null
108
+ }
109
+
110
+ // @tmp not available yet in chakra
111
+ // if (reverse) return null
112
+
113
+ return (
114
+ <RcSlider
115
+ value={[value]}
116
+ max={max}
117
+ min={min}
118
+ reverse={reverse}
119
+ step={step}
120
+ onChange={onChange}
121
+ />
122
+ )
123
+ // return (
124
+ // <Slider
125
+ // value={[value]}
126
+ // max={max}
127
+ // min={min}
128
+ // marks={marks}
129
+ // onChange={e => {
130
+ // debugger
131
+ // }}
132
+ // onValueChange={onChange}
133
+ // // reverse={reverse}
134
+ // orientation="horizontal"
135
+ // step={step}
136
+ // />
137
+ // )
138
+ }
@@ -0,0 +1,29 @@
1
+ import { Text, type TextProps } from "@chakra-ui/react"
2
+ import { useEffect, useState } from "react"
3
+
4
+ export const useTime = () => {
5
+ const [time, setTime] = useState(new Date())
6
+
7
+ useEffect(() => {
8
+ const interval = setInterval(() => {
9
+ setTime(new Date())
10
+ }, 1000 * 60)
11
+
12
+ return () => clearInterval(interval)
13
+ }, [])
14
+
15
+ return time
16
+ }
17
+
18
+ export const TimeIndicator = (props: TextProps) => {
19
+ const time = useTime()
20
+
21
+ return (
22
+ <Text fontSize="xs" {...props}>
23
+ {time.toLocaleTimeString(navigator.language, {
24
+ hour: "2-digit",
25
+ minute: "2-digit",
26
+ })}
27
+ </Text>
28
+ )
29
+ }
@@ -0,0 +1,72 @@
1
+ import { HStack, IconButton, Stack, Text } from "@chakra-ui/react"
2
+ import { IoIosArrowBack, IoMdMore } from "react-icons/io"
3
+ import { MdFullscreen, MdFullscreenExit } from "react-icons/md"
4
+ import { useObserve } from "reactjrx"
5
+ import { useFullscreen } from "../../common/useFullscreen"
6
+ import { useReader } from "../../context/useReader"
7
+ import { QuickBar } from "./QuickBar"
8
+
9
+ export const TopBar = ({
10
+ open,
11
+ onBackClick,
12
+ onMoreClick,
13
+ }: {
14
+ open: boolean
15
+ onBackClick: () => void
16
+ onMoreClick: () => void
17
+ }) => {
18
+ const reader = useReader()
19
+ const manifest = useObserve(() => reader?.context.manifest$, [reader])
20
+ const { isFullscreen, onToggleFullscreenClick } = useFullscreen()
21
+
22
+ return (
23
+ <QuickBar
24
+ present={open}
25
+ position="top"
26
+ height="80px"
27
+ justifyContent="space-between"
28
+ >
29
+ <IconButton
30
+ aria-label="left"
31
+ size="lg"
32
+ variant="ghost"
33
+ flexShrink={0}
34
+ onClick={onBackClick}
35
+ >
36
+ <IoIosArrowBack />
37
+ </IconButton>
38
+ <Stack
39
+ flex={1}
40
+ maxW={600}
41
+ gap={1}
42
+ alignItems="center"
43
+ overflow="auto"
44
+ px={4}
45
+ >
46
+ <Text truncate maxWidth="100%">
47
+ {manifest?.title}
48
+ </Text>
49
+ </Stack>
50
+ <HStack>
51
+ <IconButton
52
+ aria-label="right"
53
+ size="lg"
54
+ flexShrink={0}
55
+ variant="ghost"
56
+ onClick={onMoreClick}
57
+ >
58
+ <IoMdMore />
59
+ </IconButton>
60
+ <IconButton
61
+ aria-label="right"
62
+ size="lg"
63
+ flexShrink={0}
64
+ variant="ghost"
65
+ onClick={onToggleFullscreenClick}
66
+ >
67
+ {isFullscreen ? <MdFullscreenExit /> : <MdFullscreen />}
68
+ </IconButton>
69
+ </HStack>
70
+ </QuickBar>
71
+ )
72
+ }