@chem-po/react-web 0.0.5 → 0.0.7

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 (123) hide show
  1. package/dist/index.cjs +2 -2
  2. package/dist/index.js +2 -2
  3. package/package.json +23 -20
  4. package/src/components/auth/SignIn.tsx +43 -0
  5. package/src/components/auth/index.ts +1 -0
  6. package/src/components/box/CollapseHorizontal.tsx +18 -0
  7. package/src/components/box/ContentBox.tsx +17 -0
  8. package/src/components/box/ExpandOnMount.tsx +48 -0
  9. package/src/components/box/Expandable.tsx +96 -0
  10. package/src/components/box/FullSizeContainer.tsx +50 -0
  11. package/src/components/box/MobileFrame/index.tsx +145 -0
  12. package/src/components/box/MobileFrame/styles.css +35 -0
  13. package/src/components/box/index.ts +6 -0
  14. package/src/components/button/DeleteButton.tsx +178 -0
  15. package/src/components/button/Toggle.tsx +88 -0
  16. package/src/components/button/ViewButton.tsx +30 -0
  17. package/src/components/button/index.ts +3 -0
  18. package/src/components/feed/FeedContentPane.tsx +111 -0
  19. package/src/components/feed/MediaFeed.tsx +200 -0
  20. package/src/components/feed/MediaFeedBackground.tsx +127 -0
  21. package/src/components/feed/MediaFeedRefresh.tsx +78 -0
  22. package/src/components/feed/MediaFeedSwipeUp.tsx +34 -0
  23. package/src/components/feed/constants.ts +11 -0
  24. package/src/components/feed/context.tsx +19 -0
  25. package/src/components/feed/hooks.ts +290 -0
  26. package/src/components/feed/index.ts +2 -0
  27. package/src/components/feed/types.ts +50 -0
  28. package/src/components/form/Condition.tsx +26 -0
  29. package/src/components/form/Field.tsx +39 -0
  30. package/src/components/form/Form.tsx +425 -0
  31. package/src/components/form/FormFooter.tsx +82 -0
  32. package/src/components/form/UploadProgress/index.tsx +38 -0
  33. package/src/components/form/UploadProgress/styles.css +23 -0
  34. package/src/components/form/index.ts +4 -0
  35. package/src/components/form/input/Editable.tsx +129 -0
  36. package/src/components/form/input/InputSlider.tsx +75 -0
  37. package/src/components/form/input/OptionalTag.tsx +33 -0
  38. package/src/components/form/input/StandaloneInput.tsx +41 -0
  39. package/src/components/form/input/boolean/index.tsx +53 -0
  40. package/src/components/form/input/color/index.tsx +126 -0
  41. package/src/components/form/input/date/index.tsx +122 -0
  42. package/src/components/form/input/datetime/index.tsx +93 -0
  43. package/src/components/form/input/file.tsx +379 -0
  44. package/src/components/form/input/hooks/index.ts +2 -0
  45. package/src/components/form/input/hooks/useInputImperativeHandle.ts +16 -0
  46. package/src/components/form/input/hooks/useInputStyle.ts +39 -0
  47. package/src/components/form/input/index.ts +2 -0
  48. package/src/components/form/input/input.css +44 -0
  49. package/src/components/form/input/input.tsx +130 -0
  50. package/src/components/form/input/multipleSelect/index.tsx +55 -0
  51. package/src/components/form/input/number/index.tsx +83 -0
  52. package/src/components/form/input/number/styles.css +8 -0
  53. package/src/components/form/input/select/index.tsx +80 -0
  54. package/src/components/form/input/socialMedia/index.tsx +158 -0
  55. package/src/components/form/input/text/index.tsx +72 -0
  56. package/src/components/form/input/text/textarea.tsx +44 -0
  57. package/src/components/form/input/time/index.tsx +33 -0
  58. package/src/components/form/input/type.ts +0 -0
  59. package/src/components/form/input/types.ts +4 -0
  60. package/src/components/form/view/file.tsx +45 -0
  61. package/src/components/form/view/index.tsx +61 -0
  62. package/src/components/form/view/multipleSelect.tsx +38 -0
  63. package/src/components/form/view/select.tsx +33 -0
  64. package/src/components/index.ts +14 -0
  65. package/src/components/list/Body/InfiniteScrollGridBody.tsx +177 -0
  66. package/src/components/list/Body/InfiniteScrollListBody.tsx +114 -0
  67. package/src/components/list/Body/ListBody.tsx +23 -0
  68. package/src/components/list/Body/PagedGridBody.tsx +104 -0
  69. package/src/components/list/Body/PagedListBody.tsx +92 -0
  70. package/src/components/list/Body/hooks.ts +84 -0
  71. package/src/components/list/DataList.tsx +32 -0
  72. package/src/components/list/ListContainer.tsx +20 -0
  73. package/src/components/list/ListContent.tsx +54 -0
  74. package/src/components/list/ListCreate.tsx +57 -0
  75. package/src/components/list/ListFilters.tsx +182 -0
  76. package/src/components/list/ListFooter.tsx +458 -0
  77. package/src/components/list/ListHeader.tsx +180 -0
  78. package/src/components/list/ListItem/ListCell.tsx +48 -0
  79. package/src/components/list/ListItem/ListRow.tsx +38 -0
  80. package/src/components/list/ListItemView.tsx +53 -0
  81. package/src/components/list/ListSort.tsx +84 -0
  82. package/src/components/list/NoItems.tsx +33 -0
  83. package/src/components/list/constants.ts +1 -0
  84. package/src/components/list/index.ts +4 -0
  85. package/src/components/list/types.ts +29 -0
  86. package/src/components/list/utils.ts +62 -0
  87. package/src/components/loading/CircularProgress.tsx +11 -0
  88. package/src/components/loading/Loading.tsx +160 -0
  89. package/src/components/loading/LoadingImage.tsx +123 -0
  90. package/src/components/loading/LoadingSwitch.tsx +78 -0
  91. package/src/components/loading/index.ts +4 -0
  92. package/src/components/media/PlayButton.tsx +94 -0
  93. package/src/components/media/index.ts +1 -0
  94. package/src/components/modal/DefaultModal.tsx +18 -0
  95. package/src/components/modal/DesktopModal.tsx +11 -0
  96. package/src/components/modal/ForceMobile.tsx +7 -0
  97. package/src/components/modal/MobileModal.tsx +89 -0
  98. package/src/components/modal/index.ts +3 -0
  99. package/src/components/modal/type.ts +7 -0
  100. package/src/components/nav/NavBar.tsx +101 -0
  101. package/src/components/nav/index.ts +1 -0
  102. package/src/components/overlay/ImageViewOverlay.tsx +88 -0
  103. package/src/components/overlay/MobileOverlay.tsx +22 -0
  104. package/src/components/overlay/index.ts +2 -0
  105. package/src/components/text/GradientText/index.tsx +16 -0
  106. package/src/components/text/GradientText/styles.css +5 -0
  107. package/src/components/text/NumberTicker.tsx +28 -0
  108. package/src/components/text/index.ts +1 -0
  109. package/src/components/theme/colorMode/DarkModeToggle.tsx +40 -0
  110. package/src/components/theme/colorMode/index.ts +1 -0
  111. package/src/components/theme/index.ts +1 -0
  112. package/src/components/view/ErrorView.tsx +13 -0
  113. package/src/components/view/RedirectView.tsx +42 -0
  114. package/src/components/view/index.ts +2 -0
  115. package/src/contexts/index.ts +1 -0
  116. package/src/contexts/theme.ts +316 -0
  117. package/src/custom.d.ts +4 -0
  118. package/src/hooks/index.ts +1 -0
  119. package/src/hooks/ui/index.ts +1 -0
  120. package/src/hooks/ui/useBorderColor.ts +4 -0
  121. package/src/store/index.ts +1 -0
  122. package/src/store/usePlayer.ts +75 -0
  123. package/src/store/useScreen.ts +22 -0
@@ -0,0 +1,94 @@
1
+ import { ButtonProps, Center, IconButton, Image } from '@chakra-ui/react'
2
+ import { SpotifyTrack, WithId } from '@chem-po/core'
3
+ import { usePlaylist } from '@chem-po/react'
4
+ import { useActiveMedia, usePlayer } from '../../store/usePlayer'
5
+
6
+ export const PlayButton = ({
7
+ media,
8
+ size = 50,
9
+ opacity = 0.8,
10
+ // withThumbnail,
11
+ buttonProps,
12
+ }: {
13
+ media: WithId<SpotifyTrack> | null
14
+ size?: number
15
+ opacity?: number
16
+ withThumbnail?: boolean
17
+ buttonProps?: ButtonProps
18
+ }) => {
19
+ const { id: mediaId } = media ?? {}
20
+ const { playlist } = usePlaylist()
21
+ const api = usePlayer(s => s.api)
22
+ const playerIsPlaying = usePlayer(s => s.isPlaying)
23
+ const playerIsLoading = usePlayer(s => s.isLoading)
24
+ const currentMedia = useActiveMedia()
25
+ const isPlaying = playerIsPlaying && currentMedia?.id === mediaId
26
+ const isFetching = playerIsLoading && currentMedia?.id === mediaId
27
+
28
+ return (
29
+ <IconButton
30
+ boxShadow="md"
31
+ p={1}
32
+ height={`${size}px`}
33
+ width={`${size}px`}
34
+ minW="0"
35
+ minH="0"
36
+ borderRadius="full"
37
+ isLoading={isFetching}
38
+ aria-label="play/pause"
39
+ onClick={e => {
40
+ e.stopPropagation()
41
+ if (!media) return
42
+ if (currentMedia?.id === mediaId) {
43
+ if (playerIsPlaying) api.pause()
44
+ else api.play()
45
+ } else if (playlist) {
46
+ api.setMedia({
47
+ playlist: {
48
+ ...playlist,
49
+ currentId: mediaId ?? null,
50
+ },
51
+ })
52
+ } else {
53
+ api.setMedia({ media })
54
+ }
55
+ }}
56
+ style={{ background: '#eee' }}
57
+ {...buttonProps}
58
+ icon={
59
+ <Center borderRadius="full" height={`${size}px`} width={`${size}px`}>
60
+ {/* {
61
+ media ? (
62
+ <StorageImage
63
+ borderRadius='full'
64
+ position='absolute'
65
+ objectFit='cover'
66
+ width='100%'
67
+ height='100%'
68
+ storagePath={`${media.storageDir}/photoFile`}
69
+ alt={media.title}
70
+ />
71
+ ) : null
72
+ } */}
73
+ <Image
74
+ opacity={isPlaying ? opacity : 0}
75
+ width="100%"
76
+ transition="all 0.2s ease-in-out"
77
+ src="/svg/pause.svg"
78
+ filter="invert(100%) drop-shadow(0 0 4px black)"
79
+ transform={`scale(${isPlaying ? 0.8 : 1})`}
80
+ />
81
+ <Image
82
+ position="absolute"
83
+ width="100%"
84
+ opacity={isPlaying ? 0 : opacity}
85
+ transition="all 0.2s ease-in-out"
86
+ filter="invert(100%) drop-shadow(0 0 3px black)"
87
+ src="/svg/play.svg"
88
+ transform={`scale(${!isPlaying ? 0.8 : 1})`}
89
+ />
90
+ </Center>
91
+ }
92
+ />
93
+ )
94
+ }
@@ -0,0 +1 @@
1
+ export * from './PlayButton'
@@ -0,0 +1,18 @@
1
+ import { useScreen } from '@chem-po/react'
2
+ import { useMemo } from 'react'
3
+ import { DesktopModal } from './DesktopModal'
4
+ import { useForceMobile } from './ForceMobile'
5
+ import { MobileModal } from './MobileModal'
6
+ import { DefaultModalProps } from './type'
7
+
8
+ export const DefaultModal = (props: DefaultModalProps) => {
9
+ const isMobile = useScreen(s => s.isMobile)
10
+
11
+ const forceMobile = useForceMobile()
12
+ const Component = useMemo(
13
+ () => (isMobile || forceMobile ? MobileModal : DesktopModal),
14
+ [isMobile, forceMobile],
15
+ )
16
+
17
+ return <Component {...props} />
18
+ }
@@ -0,0 +1,11 @@
1
+ import { Box, Modal, ModalContent, ModalOverlay } from '@chakra-ui/react'
2
+ import { DefaultModalProps } from './type'
3
+
4
+ export const DesktopModal = ({ children, isOpen, contentProps, ...props }: DefaultModalProps) => (
5
+ <Modal scrollBehavior="inside" isCentered isOpen={isOpen} {...props}>
6
+ <ModalOverlay />
7
+ <ModalContent position="relative" overflowY="auto" bg="background.100" {...contentProps}>
8
+ <Box>{children}</Box>
9
+ </ModalContent>
10
+ </Modal>
11
+ )
@@ -0,0 +1,7 @@
1
+ import React, { PropsWithChildren, useContext } from 'react'
2
+
3
+ const ForceMobileContext = React.createContext(false)
4
+ export const ForceMobile = ({ children }: PropsWithChildren) => (
5
+ <ForceMobileContext.Provider value={true}>{children}</ForceMobileContext.Provider>
6
+ )
7
+ export const useForceMobile = () => useContext(ForceMobileContext)
@@ -0,0 +1,89 @@
1
+ import { Flex, useColorModeValue } from '@chakra-ui/react'
2
+ import { useMobileFrame } from '@chem-po/react'
3
+ import { useEffect, useRef, useState } from 'react'
4
+ import { MobileOverlay } from '../overlay/MobileOverlay'
5
+ import { DefaultModalProps } from './type'
6
+
7
+ export const MobileOverlayBackground = ({ onClick }: { onClick?: () => void }) => (
8
+ <Flex
9
+ onClick={onClick}
10
+ w="100%"
11
+ h="100%"
12
+ position="absolute"
13
+ bg="blackAlpha.400"
14
+ transition="opacity 300ms"
15
+ />
16
+ )
17
+
18
+ export const MobileModal = ({
19
+ isOpen,
20
+ onClose,
21
+ children,
22
+ contentProps,
23
+ onContentMounted,
24
+ closeOnOverlayClick,
25
+ }: DefaultModalProps) => {
26
+ const { height, width } = useMobileFrame()
27
+
28
+ const bg = useColorModeValue('gray.100', '#454545')
29
+
30
+ const [isMounted, setIsMounted] = useState(false)
31
+ const [contentMounted, setContentMounted] = useState(false)
32
+
33
+ const unmountTimeout = useRef<ReturnType<typeof setTimeout> | null>(null)
34
+ const contentMountTimeout = useRef<ReturnType<typeof setTimeout> | null>(null)
35
+ useEffect(() => {
36
+ if (unmountTimeout.current) {
37
+ clearTimeout(unmountTimeout.current)
38
+ unmountTimeout.current = null
39
+ }
40
+
41
+ if (contentMountTimeout.current) {
42
+ clearTimeout(contentMountTimeout.current)
43
+ contentMountTimeout.current = null
44
+ }
45
+
46
+ if (isOpen) {
47
+ setIsMounted(true)
48
+ contentMountTimeout.current = setTimeout(() => {
49
+ setContentMounted(true)
50
+ if (onContentMounted) onContentMounted(true)
51
+ }, 50)
52
+ } else {
53
+ setContentMounted(false)
54
+ if (onContentMounted) onContentMounted(false)
55
+ unmountTimeout.current = setTimeout(() => {
56
+ setIsMounted(false)
57
+ }, 300)
58
+ }
59
+ return () => {
60
+ if (unmountTimeout.current) {
61
+ clearTimeout(unmountTimeout.current)
62
+ unmountTimeout.current = null
63
+ }
64
+ if (contentMountTimeout.current) {
65
+ clearTimeout(contentMountTimeout.current)
66
+ contentMountTimeout.current = null
67
+ }
68
+ }
69
+ }, [isOpen, onContentMounted])
70
+ return isMounted ? (
71
+ <MobileOverlay
72
+ opacity={contentMounted ? 1 : 0}
73
+ pointerEvents={contentMounted ? 'auto' : 'none'}>
74
+ <MobileOverlayBackground onClick={closeOnOverlayClick !== false ? onClose : undefined} />
75
+ <Flex
76
+ width={`${width - 10}px`}
77
+ maxH={`${height - 10}px`}
78
+ overflowY="auto"
79
+ overflowX="hidden"
80
+ minH="100px"
81
+ bg={bg}
82
+ borderRadius={6}
83
+ position="relative"
84
+ {...contentProps}>
85
+ {children}
86
+ </Flex>
87
+ </MobileOverlay>
88
+ ) : null
89
+ }
@@ -0,0 +1,3 @@
1
+ export * from './DefaultModal'
2
+ export * from './DesktopModal'
3
+ export * from './MobileModal'
@@ -0,0 +1,7 @@
1
+ import { ModalContentProps, ModalProps } from '@chakra-ui/react'
2
+ import { PropsWithChildren } from 'react'
3
+
4
+ export type DefaultModalProps = PropsWithChildren<Omit<ModalProps, 'render'>> & {
5
+ contentProps?: ModalContentProps
6
+ onContentMounted?: (mounted: boolean) => void
7
+ }
@@ -0,0 +1,101 @@
1
+ import { Button, Flex, Image, useColorModeValue } from '@chakra-ui/react'
2
+ import { palette } from '@chem-po/core'
3
+ import { useViews, View } from '@chem-po/react'
4
+ import { PropsWithChildren, useMemo } from 'react'
5
+ import { Link, useLocation } from 'react-router-dom'
6
+
7
+ export const NAV_BAR_HEIGHT = 50
8
+
9
+ const NavBarLink = ({ view, absolute }: { view: View; absolute: boolean }) => {
10
+ const { path: paths, name, icon, iconScale } = view
11
+ const path = Array.isArray(paths) ? paths[0] : paths
12
+
13
+ // const hoverBg = useColorModeValue('#00000033', '#f7f7f777')
14
+ // const textColor = useColorModeValue('#777', '#cdcdcd')
15
+ const iconBrightness = useColorModeValue(1, 1.8)
16
+ const usedBrightness = useMemo(
17
+ () => (absolute ? 2.4 : iconBrightness),
18
+ [absolute, iconBrightness],
19
+ )
20
+ const { pathname } = useLocation()
21
+ const hoverBg = useColorModeValue('#ffffff33', '#00000033')
22
+ const body = (
23
+ <Button
24
+ pointerEvents={pathname === path ? 'none' : 'auto'}
25
+ opacity={pathname === path ? 1 : 0.7}
26
+ width="100%"
27
+ height="100%"
28
+ borderRadius={0}
29
+ flexFlow="column"
30
+ _hover={{
31
+ bg: hoverBg,
32
+ }}
33
+ variant="unstyled"
34
+ display="flex"
35
+ alignItems="center"
36
+ justifyContent="center">
37
+ <Image
38
+ height={`${34 * (iconScale ?? 1)}px`}
39
+ filter={`brightness(${usedBrightness})${
40
+ absolute ? ' drop-shadow(1px 1px 3px #00000099 )' : ''
41
+ }`}
42
+ src={icon}
43
+ alt={name}
44
+ />
45
+ {/* <Text color={textColor} lineHeight={1} fontWeight={400} fontSize='xs'>
46
+ {name}
47
+ </Text> */}
48
+ </Button>
49
+ )
50
+ return pathname === path ? (
51
+ <Flex key={path} flex={1} h="100%" justify="center">
52
+ {body}
53
+ </Flex>
54
+ ) : (
55
+ <Link
56
+ key={path}
57
+ style={{
58
+ flex: 1,
59
+ display: 'flex',
60
+ height: '100%',
61
+ justifyContent: 'center',
62
+ }}
63
+ to={path}>
64
+ {body}
65
+ </Link>
66
+ )
67
+ }
68
+
69
+ export const NavBar = ({
70
+ selectedView,
71
+ children,
72
+ }: PropsWithChildren<{ selectedView?: View | null }>) => {
73
+ const bg = useColorModeValue('#dedede', palette.gray.dark)
74
+ const defaultBorderColor = useColorModeValue('#00000022', '#ffffff33')
75
+ const views = useViews()
76
+ const menuViews = useMemo(() => views.filter(v => !!v.view.icon), [views])
77
+ const isAbsolute = useMemo(() => !!selectedView?.navBar?.absolute, [selectedView])
78
+ const background = useMemo(() => selectedView?.navBar?.backgroundColor ?? bg, [bg, selectedView])
79
+ const borderColor = useMemo(() => {
80
+ if (selectedView?.navBar?.borderColor) return !!selectedView?.navBar?.borderColor
81
+ return isAbsolute ? '#ffffff33' : defaultBorderColor
82
+ }, [defaultBorderColor, selectedView, isAbsolute])
83
+ return (
84
+ <Flex
85
+ position="absolute"
86
+ bottom={0}
87
+ left={0}
88
+ bg={background}
89
+ w="100%"
90
+ transition="all 500ms"
91
+ h={`${NAV_BAR_HEIGHT}px`}
92
+ borderTop={`1px solid ${borderColor}`}>
93
+ <Flex position="relative" w="100%" justify="space-around" align="center">
94
+ {menuViews.map(({ view }) => (
95
+ <NavBarLink absolute={isAbsolute} key={view.name} view={view} />
96
+ ))}
97
+ {children}
98
+ </Flex>
99
+ </Flex>
100
+ )
101
+ }
@@ -0,0 +1 @@
1
+ export * from './NavBar'
@@ -0,0 +1,88 @@
1
+ import { CloseIcon } from '@chakra-ui/icons'
2
+ import { Center, IconButton, Image, Modal, ModalContent, ModalOverlay } from '@chakra-ui/react'
3
+ import { useScreen } from '@chem-po/react'
4
+ import { SyntheticEvent, useCallback, useMemo, useState } from 'react'
5
+ import { LoadingLogo } from '../loading/Loading'
6
+
7
+ export const ImageViewOverlay = ({ src, onClose }: { src: string; onClose: () => void }) => {
8
+ const [loading, setLoading] = useState(true)
9
+ const screenWidth = useScreen(s => s.width)
10
+ const screenHeight = useScreen(s => s.height)
11
+ const [imageSize, setImageSize] = useState({ width: screenWidth / 2, height: screenHeight / 2 })
12
+
13
+ const { height, width } = useMemo(() => {
14
+ if (loading) return imageSize
15
+ const ratio = imageSize.width / imageSize.height
16
+ let h = Math.min(imageSize.height, screenHeight * 0.9)
17
+ let w = h * ratio
18
+ if (w > screenWidth * 0.9) {
19
+ w = Math.min(imageSize.width, screenWidth * 0.9)
20
+ h = w / ratio
21
+ }
22
+ return { height: h, width: w }
23
+ }, [screenHeight, screenWidth, imageSize, loading])
24
+
25
+ const onLoadStart = useCallback(() => setLoading(true), [])
26
+ const onLoad = useCallback((e: SyntheticEvent<HTMLImageElement>) => {
27
+ const { naturalWidth, naturalHeight } = e.currentTarget
28
+ setImageSize({ width: naturalWidth, height: naturalHeight })
29
+ setLoading(false)
30
+ }, [])
31
+ return (
32
+ <Modal size="full" isOpen onClose={onClose}>
33
+ <ModalOverlay bg="blackAlpha.700" />
34
+ <ModalContent
35
+ style={{ background: 'transparent' }}
36
+ pointerEvents="none"
37
+ width="100%"
38
+ height="100%">
39
+ <Center
40
+ pointerEvents="none"
41
+ position="fixed"
42
+ p={[4, 6, 8]}
43
+ top={0}
44
+ left={0}
45
+ right={0}
46
+ bottom={0}
47
+ zIndex={4}>
48
+ <Center
49
+ opacity={loading ? 0 : 1}
50
+ transition="all 500ms"
51
+ overflow="hidden"
52
+ w={`${width}px`}
53
+ height={`${height}px`}>
54
+ <Image
55
+ onLoadStart={onLoadStart}
56
+ onLoad={onLoad}
57
+ transition="opacity 300ms"
58
+ height="100%"
59
+ objectFit="contain"
60
+ borderRadius={4}
61
+ src={src}
62
+ />
63
+ </Center>
64
+ <IconButton
65
+ borderRadius="full"
66
+ position="absolute"
67
+ top={4}
68
+ right={4}
69
+ aria-label="close"
70
+ icon={<CloseIcon />}
71
+ onClick={onClose}
72
+ />
73
+ </Center>
74
+ <Center
75
+ position="absolute"
76
+ top={0}
77
+ left={0}
78
+ right={0}
79
+ bottom={0}
80
+ pointerEvents="none"
81
+ opacity={loading ? 1 : 0}
82
+ transition="opacity 300ms">
83
+ <LoadingLogo isLoading={loading} size={70} />
84
+ </Center>
85
+ </ModalContent>
86
+ </Modal>
87
+ )
88
+ }
@@ -0,0 +1,22 @@
1
+ import { Flex, FlexProps, Portal } from '@chakra-ui/react'
2
+ import { useMobileFrame } from '@chem-po/react'
3
+
4
+ export const MobileOverlay = (props: FlexProps) => {
5
+ const { overlayRef } = useMobileFrame()
6
+
7
+ return (
8
+ <Portal containerRef={overlayRef}>
9
+ <Flex
10
+ justify="center"
11
+ align="center"
12
+ transition="opacity 300ms"
13
+ position="absolute"
14
+ left={0}
15
+ top={0}
16
+ height="100%"
17
+ width="100%"
18
+ {...props}
19
+ />
20
+ </Portal>
21
+ )
22
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ImageViewOverlay'
2
+ export * from './MobileOverlay'
@@ -0,0 +1,16 @@
1
+ import { Box, BoxProps, Text, TextProps } from '@chakra-ui/react'
2
+ import './styles.css'
3
+
4
+ export const GradientText = ({
5
+ children,
6
+ color,
7
+ background,
8
+ boxProps,
9
+ ...props
10
+ }: TextProps & { boxProps?: BoxProps }) => (
11
+ <Box background={background} {...boxProps}>
12
+ <Text cursor="default" className="gradient-text" background={color} {...props}>
13
+ {children}
14
+ </Text>
15
+ </Box>
16
+ )
@@ -0,0 +1,5 @@
1
+ .gradient-text {
2
+ background-clip: text !important;
3
+ -webkit-background-clip: text !important;
4
+ -webkit-text-fill-color: transparent !important;
5
+ }
@@ -0,0 +1,28 @@
1
+ import { useEffect, useRef } from 'react'
2
+
3
+ export const NumberTicker = ({ value, duration = 30 }: { value: number; duration?: number }) => {
4
+ const spanRef = useRef<HTMLSpanElement>(null)
5
+ const helperValue = useRef(value)
6
+ const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
7
+ useEffect(() => {
8
+ intervalRef.current = setInterval(() => {
9
+ if (spanRef.current) {
10
+ if (helperValue.current === value && intervalRef.current)
11
+ return clearInterval(intervalRef.current)
12
+ if (helperValue.current < value) {
13
+ helperValue.current += 1
14
+ spanRef.current.innerText = helperValue.current.toString()
15
+ } else if (helperValue.current > value) {
16
+ helperValue.current -= 1
17
+ spanRef.current.innerText = helperValue.current.toString()
18
+ }
19
+ }
20
+ return () => {}
21
+ }, duration)
22
+ return () => {
23
+ if (intervalRef.current) clearInterval(intervalRef.current)
24
+ }
25
+ }, [value, duration])
26
+
27
+ return <span ref={spanRef}>{value}</span>
28
+ }
@@ -0,0 +1 @@
1
+ export * from './NumberTicker'
@@ -0,0 +1,40 @@
1
+ import { MoonIcon, SunIcon } from '@chakra-ui/icons'
2
+ import { Flex, FlexProps, IconButton, useColorMode, useColorModeValue } from '@chakra-ui/react'
3
+
4
+ export const DarkModeToggle = () => {
5
+ const { colorMode, toggleColorMode } = useColorMode()
6
+ const bg = useColorModeValue('gray.200', 'whiteAlpha.200')
7
+ const color = useColorModeValue('blackAlpha.700', 'whiteAlpha.800')
8
+ const hoverBg = useColorModeValue('gray.300', 'whiteAlpha.300')
9
+ const hoverColor = useColorModeValue('gray.800', 'whiteAlpha.800')
10
+ return (
11
+ <IconButton
12
+ aria-label="Toggle dark mode"
13
+ bg={bg}
14
+ color={color}
15
+ borderRadius="full"
16
+ _hover={{
17
+ bg: hoverBg,
18
+ color: hoverColor,
19
+ }}
20
+ w={8}
21
+ h={8}
22
+ minW={0}
23
+ icon={
24
+ colorMode === 'light' ? (
25
+ <SunIcon w={4} h={4} />
26
+ ) : (
27
+ <MoonIcon w={4} h={4} filter="drop-shadow(1px 1px 2px #000000aa)" />
28
+ )
29
+ }
30
+ onClick={toggleColorMode}
31
+ variant="ghost"
32
+ />
33
+ )
34
+ }
35
+
36
+ export const AbsoluteDarkModeToggle = (props: FlexProps) => (
37
+ <Flex position="absolute" bottom={3} right={3} {...props}>
38
+ <DarkModeToggle />
39
+ </Flex>
40
+ )
@@ -0,0 +1 @@
1
+ export * from './DarkModeToggle'
@@ -0,0 +1 @@
1
+ export * from './colorMode'
@@ -0,0 +1,13 @@
1
+ import { Flex, Text, useColorModeValue } from '@chakra-ui/react'
2
+ import { ContentBox } from '../box/ContentBox'
3
+
4
+ export const ErrorView = ({ message }: { message?: string }) => {
5
+ const color = useColorModeValue('gray.600', 'gray.100')
6
+ return (
7
+ <Flex pt={4}>
8
+ <ContentBox>
9
+ <Text color={color}>{message ?? 'Sorry, something went wrong.'}</Text>
10
+ </ContentBox>
11
+ </Flex>
12
+ )
13
+ }
@@ -0,0 +1,42 @@
1
+ import { Center, HStack, Text, VStack } from '@chakra-ui/react'
2
+ import { useAuth } from '@chem-po/react'
3
+ import React, { useEffect, useRef } from 'react'
4
+ import { useNavigate } from 'react-router-dom'
5
+ import { ContentBox } from '../box/ContentBox'
6
+ import { CircularProgress } from '../loading/CircularProgress'
7
+ import { Loading } from '../loading/Loading'
8
+
9
+ export const RedirectView: React.FC<{ loading?: boolean }> = ({ loading }) => {
10
+ const timer = useRef<any>()
11
+ const authLoading = useAuth(s => s.loading)
12
+
13
+ const navigate = useNavigate()
14
+ useEffect(() => {
15
+ if (!loading && !authLoading) {
16
+ timer.current = setTimeout(() => {
17
+ navigate('/')
18
+ }, 1500)
19
+ }
20
+ return () => {
21
+ if (timer.current) clearTimeout(timer.current)
22
+ }
23
+ }, [loading, navigate, authLoading])
24
+
25
+ return !loading && !authLoading ? (
26
+ <Center minH="100%" w="100%">
27
+ <ContentBox maxW="500px">
28
+ <HStack spacing={3}>
29
+ <CircularProgress size={6} />
30
+ <VStack spacing={0} align="flex-start">
31
+ <Text>404</Text>
32
+ <Text fontSize="sm" opacity={0.7}>
33
+ Page not found - redirecting to Home...
34
+ </Text>
35
+ </VStack>
36
+ </HStack>
37
+ </ContentBox>
38
+ </Center>
39
+ ) : (
40
+ <Loading />
41
+ )
42
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ErrorView'
2
+ export * from './RedirectView'
@@ -0,0 +1 @@
1
+ export * from './theme'