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.

tools-panel.tsx 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. import * as React from 'react'
  2. import {
  3. ArrowTopRightIcon,
  4. CircleIcon,
  5. CursorArrowIcon,
  6. LockClosedIcon,
  7. LockOpen1Icon,
  8. Pencil1Icon,
  9. SquareIcon,
  10. TextIcon,
  11. } from '@radix-ui/react-icons'
  12. import styled from '~styles'
  13. import { Data, TLDrawShapeType } from '~types'
  14. import { useTLDrawContext } from '~hooks'
  15. import { StatusBar } from './status-bar'
  16. import { FloatingContainer } from '../shared'
  17. import { PrimaryButton, SecondaryButton } from './shared'
  18. import { UndoRedo } from './undo-redo'
  19. import { Zoom } from './zoom'
  20. import { BackToContent } from './back-to-content'
  21. const activeToolSelector = (s: Data) => s.appState.activeTool
  22. const isToolLockedSelector = (s: Data) => s.appState.isToolLocked
  23. const isDebugModeSelector = (s: Data) => s.settings.isDebugMode
  24. export const ToolsPanel = React.memo((): JSX.Element => {
  25. const { tlstate, useSelector } = useTLDrawContext()
  26. const activeTool = useSelector(activeToolSelector)
  27. const isToolLocked = useSelector(isToolLockedSelector)
  28. const isDebugMode = useSelector(isDebugModeSelector)
  29. const selectSelectTool = React.useCallback(() => {
  30. tlstate.selectTool('select')
  31. }, [tlstate])
  32. const selectDrawTool = React.useCallback(() => {
  33. tlstate.selectTool(TLDrawShapeType.Draw)
  34. }, [tlstate])
  35. const selectRectangleTool = React.useCallback(() => {
  36. tlstate.selectTool(TLDrawShapeType.Rectangle)
  37. }, [tlstate])
  38. const selectEllipseTool = React.useCallback(() => {
  39. tlstate.selectTool(TLDrawShapeType.Ellipse)
  40. }, [tlstate])
  41. const selectArrowTool = React.useCallback(() => {
  42. tlstate.selectTool(TLDrawShapeType.Arrow)
  43. }, [tlstate])
  44. const selectTextTool = React.useCallback(() => {
  45. tlstate.selectTool(TLDrawShapeType.Text)
  46. }, [tlstate])
  47. return (
  48. <ToolsPanelContainer>
  49. <LeftWrap size={{ '@initial': 'mobile', '@sm': 'small' }}>
  50. <Zoom />
  51. <FloatingContainer>
  52. <SecondaryButton
  53. label={'Select'}
  54. kbd={'1'}
  55. onClick={selectSelectTool}
  56. isActive={activeTool === 'select'}
  57. >
  58. <CursorArrowIcon />
  59. </SecondaryButton>
  60. </FloatingContainer>
  61. </LeftWrap>
  62. <CenterWrap>
  63. <BackToContent />
  64. <FloatingContainer>
  65. <PrimaryButton
  66. kbd={'2'}
  67. label={TLDrawShapeType.Draw}
  68. onClick={selectDrawTool}
  69. isActive={activeTool === TLDrawShapeType.Draw}
  70. >
  71. <Pencil1Icon />
  72. </PrimaryButton>
  73. <PrimaryButton
  74. kbd={'3'}
  75. label={TLDrawShapeType.Rectangle}
  76. onClick={selectRectangleTool}
  77. isActive={activeTool === TLDrawShapeType.Rectangle}
  78. >
  79. <SquareIcon />
  80. </PrimaryButton>
  81. <PrimaryButton
  82. kbd={'4'}
  83. label={TLDrawShapeType.Draw}
  84. onClick={selectEllipseTool}
  85. isActive={activeTool === TLDrawShapeType.Ellipse}
  86. >
  87. <CircleIcon />
  88. </PrimaryButton>
  89. <PrimaryButton
  90. kbd={'5'}
  91. label={TLDrawShapeType.Arrow}
  92. onClick={selectArrowTool}
  93. isActive={activeTool === TLDrawShapeType.Arrow}
  94. >
  95. <ArrowTopRightIcon />
  96. </PrimaryButton>
  97. <PrimaryButton
  98. kbd={'6'}
  99. label={TLDrawShapeType.Text}
  100. onClick={selectTextTool}
  101. isActive={activeTool === TLDrawShapeType.Text}
  102. >
  103. <TextIcon />
  104. </PrimaryButton>
  105. </FloatingContainer>
  106. </CenterWrap>
  107. <RightWrap size={{ '@initial': 'mobile', '@sm': 'small' }}>
  108. <FloatingContainer>
  109. <SecondaryButton
  110. kbd={'7'}
  111. label={'Lock Tool'}
  112. onClick={tlstate.toggleToolLock}
  113. isActive={isToolLocked}
  114. >
  115. {isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
  116. </SecondaryButton>
  117. </FloatingContainer>
  118. <UndoRedo />
  119. </RightWrap>
  120. {isDebugMode && (
  121. <StatusWrap>
  122. <StatusBar />
  123. </StatusWrap>
  124. )}
  125. </ToolsPanelContainer>
  126. )
  127. })
  128. const ToolsPanelContainer = styled('div', {
  129. position: 'fixed',
  130. bottom: 0,
  131. left: 0,
  132. right: 0,
  133. width: '100%',
  134. minWidth: 0,
  135. maxWidth: '100%',
  136. display: 'grid',
  137. gridTemplateColumns: '1fr auto 1fr',
  138. padding: '0',
  139. alignItems: 'flex-end',
  140. zIndex: 200,
  141. gridGap: '$4',
  142. gridRowGap: '$4',
  143. pointerEvents: 'none',
  144. '& > div > *': {
  145. pointerEvents: 'all',
  146. },
  147. })
  148. const CenterWrap = styled('div', {
  149. gridRow: 1,
  150. gridColumn: 2,
  151. display: 'flex',
  152. width: 'fit-content',
  153. alignItems: 'center',
  154. justifyContent: 'center',
  155. flexDirection: 'column',
  156. gap: 12,
  157. })
  158. const LeftWrap = styled('div', {
  159. gridRow: 1,
  160. gridColumn: 1,
  161. display: 'flex',
  162. paddingLeft: '$3',
  163. variants: {
  164. size: {
  165. mobile: {
  166. flexDirection: 'column',
  167. justifyContent: 'flex-end',
  168. alignItems: 'flex-start',
  169. '& > *:nth-of-type(1)': {
  170. marginBottom: '8px',
  171. },
  172. },
  173. small: {
  174. flexDirection: 'row',
  175. alignItems: 'flex-end',
  176. justifyContent: 'space-between',
  177. '& > *:nth-of-type(1)': {
  178. marginBottom: '0px',
  179. },
  180. },
  181. },
  182. },
  183. })
  184. const RightWrap = styled('div', {
  185. gridRow: 1,
  186. gridColumn: 3,
  187. display: 'flex',
  188. paddingRight: '$3',
  189. variants: {
  190. size: {
  191. mobile: {
  192. flexDirection: 'column-reverse',
  193. justifyContent: 'flex-end',
  194. alignItems: 'flex-end',
  195. '& > *:nth-of-type(2)': {
  196. marginBottom: '8px',
  197. },
  198. },
  199. small: {
  200. flexDirection: 'row',
  201. alignItems: 'flex-end',
  202. justifyContent: 'space-between',
  203. '& > *:nth-of-type(2)': {
  204. marginBottom: '0px',
  205. },
  206. },
  207. },
  208. },
  209. })
  210. const StatusWrap = styled('div', {
  211. gridRow: 2,
  212. gridColumn: '1 / span 3',
  213. })