You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

page-panel.tsx 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import styled from 'styles'
  2. import * as ContextMenu from '@radix-ui/react-context-menu'
  3. import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
  4. import * as Dialog from '@radix-ui/react-dialog'
  5. import { IconWrapper, RowButton } from 'components/shared'
  6. import { CheckIcon, ChevronDownIcon, PlusIcon } from '@radix-ui/react-icons'
  7. import * as Panel from '../panel'
  8. import state, { useSelector } from 'state'
  9. import { useEffect, useRef, useState } from 'react'
  10. export default function PagePanel() {
  11. const rIsOpen = useRef(false)
  12. const [isOpen, setIsOpen] = useState(false)
  13. useEffect(() => {
  14. if (rIsOpen.current !== isOpen) {
  15. rIsOpen.current = isOpen
  16. }
  17. }, [isOpen])
  18. const documentPages = useSelector((s) => s.data.document.pages)
  19. const currentPageId = useSelector((s) => s.data.currentPageId)
  20. if (!documentPages[currentPageId]) return null
  21. const sorted = Object.values(documentPages).sort(
  22. (a, b) => a.childIndex - b.childIndex
  23. )
  24. return (
  25. <DropdownMenu.Root
  26. open={isOpen}
  27. onOpenChange={(isOpen) => {
  28. if (rIsOpen.current !== isOpen) {
  29. setIsOpen(isOpen)
  30. }
  31. }}
  32. >
  33. <PanelRoot>
  34. <DropdownMenu.Trigger
  35. as={RowButton}
  36. bp={{ '@initial': 'mobile', '@sm': 'small' }}
  37. css={{ paddingRight: 12 }}
  38. >
  39. <span>{documentPages[currentPageId].name}</span>
  40. </DropdownMenu.Trigger>
  41. <DropdownMenu.Content sideOffset={8}>
  42. <PanelRoot>
  43. <DropdownMenu.RadioGroup
  44. as={Content}
  45. value={currentPageId}
  46. onValueChange={(id) => {
  47. setIsOpen(false)
  48. state.send('CHANGED_CURRENT_PAGE', { id })
  49. }}
  50. >
  51. {sorted.map(({ id, name }) => (
  52. <ContextMenu.Root key={id}>
  53. <ContextMenu.Trigger>
  54. <StyledRadioItem
  55. key={id}
  56. value={id}
  57. bp={{ '@initial': 'mobile', '@sm': 'small' }}
  58. >
  59. <span>{name}</span>
  60. <DropdownMenu.ItemIndicator as={IconWrapper} size="small">
  61. <CheckIcon />
  62. </DropdownMenu.ItemIndicator>
  63. </StyledRadioItem>
  64. </ContextMenu.Trigger>
  65. <StyledContextMenuContent>
  66. <ContextMenu.Group>
  67. <StyledContextMenuItem
  68. onSelect={() => state.send('RENAMED_PAGE', { id })}
  69. >
  70. Rename
  71. </StyledContextMenuItem>
  72. <StyledContextMenuItem
  73. onSelect={() => {
  74. setIsOpen(false)
  75. state.send('DELETED_PAGE', { id })
  76. }}
  77. >
  78. Delete
  79. </StyledContextMenuItem>
  80. </ContextMenu.Group>
  81. </StyledContextMenuContent>
  82. </ContextMenu.Root>
  83. ))}
  84. </DropdownMenu.RadioGroup>
  85. <DropdownMenu.Separator />
  86. <RowButton
  87. bp={{ '@initial': 'mobile', '@sm': 'small' }}
  88. onClick={() => {
  89. setIsOpen(false)
  90. state.send('CREATED_PAGE')
  91. }}
  92. >
  93. <span>Create Page</span>
  94. <IconWrapper size="small">
  95. <PlusIcon />
  96. </IconWrapper>
  97. </RowButton>
  98. </PanelRoot>
  99. </DropdownMenu.Content>
  100. </PanelRoot>
  101. </DropdownMenu.Root>
  102. )
  103. }
  104. const PanelRoot = styled('div', {
  105. marginLeft: 8,
  106. zIndex: 200,
  107. overflow: 'hidden',
  108. position: 'relative',
  109. display: 'flex',
  110. flexDirection: 'column',
  111. alignItems: 'center',
  112. pointerEvents: 'all',
  113. padding: '2px',
  114. borderRadius: '4px',
  115. backgroundColor: '$panel',
  116. border: '1px solid $panel',
  117. boxShadow: '0px 2px 4px rgba(0,0,0,.2)',
  118. userSelect: 'none',
  119. })
  120. const Content = styled(Panel.Content, {
  121. width: '100%',
  122. minWidth: 128,
  123. })
  124. const StyledRadioItem = styled(DropdownMenu.RadioItem, {
  125. height: 32,
  126. width: 'auto',
  127. display: 'flex',
  128. alignItems: 'center',
  129. justifyContent: 'space-between',
  130. padding: '0 6px 0 12px',
  131. cursor: 'pointer',
  132. borderRadius: '4px',
  133. fontSize: '$1',
  134. fontFamily: '$ui',
  135. backgroundColor: 'transparent',
  136. outline: 'none',
  137. variants: {
  138. bp: {
  139. mobile: {},
  140. small: {
  141. '&:hover': {
  142. backgroundColor: '$hover',
  143. },
  144. '&:focus-within': {
  145. backgroundColor: '$hover',
  146. },
  147. },
  148. },
  149. },
  150. })
  151. const StyledContextMenuContent = styled(ContextMenu.Content, {
  152. padding: '2px',
  153. borderRadius: '4px',
  154. backgroundColor: '$panel',
  155. border: '1px solid $panel',
  156. boxShadow: '0px 2px 4px rgba(0,0,0,.2)',
  157. })
  158. const StyledContextMenuItem = styled(ContextMenu.Item, {
  159. height: 32,
  160. width: '100%',
  161. display: 'flex',
  162. alignItems: 'center',
  163. justifyContent: 'space-between',
  164. padding: '0 12px 0 12px',
  165. cursor: 'pointer',
  166. borderRadius: '4px',
  167. fontSize: '$1',
  168. fontFamily: '$ui',
  169. backgroundColor: 'transparent',
  170. outline: 'none',
  171. bp: {
  172. mobile: {},
  173. small: {
  174. '&:hover:not(:disabled)': {
  175. backgroundColor: '$hover',
  176. },
  177. },
  178. },
  179. })
  180. const StyledOverlay = styled(Dialog.Overlay, {
  181. backgroundColor: 'rgba(0, 0, 0, .15)',
  182. position: 'fixed',
  183. top: 0,
  184. right: 0,
  185. bottom: 0,
  186. left: 0,
  187. })
  188. const StyledContent = styled(Dialog.Content, {
  189. position: 'fixed',
  190. top: '50%',
  191. left: '50%',
  192. transform: 'translate(-50%, -50%)',
  193. minWidth: 200,
  194. maxWidth: 'fit-content',
  195. maxHeight: '85vh',
  196. padding: 20,
  197. marginTop: '-5vh',
  198. backgroundColor: 'white',
  199. borderRadius: 6,
  200. '&:focus': {
  201. outline: 'none',
  202. },
  203. })