您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

style-panel.tsx 4.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import styled from 'styles'
  2. import state, { useSelector } from 'state'
  3. import * as Panel from 'components/panel'
  4. import { useRef } from 'react'
  5. import { IconButton } from 'components/shared'
  6. import { Circle, Copy, Lock, Trash, Unlock, X } from 'react-feather'
  7. import { deepCompare, deepCompareArrays, getSelectedShapes } from 'utils/utils'
  8. import { shades, fills, strokes } from 'lib/colors'
  9. import ColorPicker from './color-picker'
  10. import AlignDistribute from './align-distribute'
  11. import { ShapeStyles } from 'types'
  12. import WidthPicker from './width-picker'
  13. import { CopyIcon } from '@radix-ui/react-icons'
  14. const fillColors = { ...shades, ...fills }
  15. const strokeColors = { ...shades, ...strokes }
  16. export default function StylePanel() {
  17. const rContainer = useRef<HTMLDivElement>(null)
  18. const isOpen = useSelector((s) => s.data.settings.isStyleOpen)
  19. return (
  20. <StylePanelRoot ref={rContainer} isOpen={isOpen}>
  21. {isOpen ? (
  22. <SelectedShapeStyles />
  23. ) : (
  24. <IconButton onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}>
  25. <Circle />
  26. </IconButton>
  27. )}
  28. </StylePanelRoot>
  29. )
  30. }
  31. // This panel is going to be hard to keep cool, as we're selecting computed
  32. // information, based on the user's current selection. We might have to keep
  33. // track of this data manually within our state.
  34. function SelectedShapeStyles({}: {}) {
  35. const selectedIds = useSelector(
  36. (s) => Array.from(s.data.selectedIds.values()),
  37. deepCompareArrays
  38. )
  39. const shapesStyle = useSelector((s) => {
  40. const { currentStyle } = s.data
  41. const shapes = getSelectedShapes(s.data)
  42. if (shapes.length === 0) {
  43. return currentStyle
  44. }
  45. const style: Partial<ShapeStyles> = {}
  46. const overrides = new Set<string>([])
  47. for (const shape of shapes) {
  48. for (let key in currentStyle) {
  49. if (overrides.has(key)) continue
  50. if (style[key] === undefined) {
  51. style[key] = shape.style[key]
  52. } else {
  53. if (style[key] === shape.style[key]) continue
  54. style[key] = currentStyle[key]
  55. overrides.add(key)
  56. }
  57. }
  58. }
  59. return style
  60. }, deepCompare)
  61. const hasSelection = selectedIds.length > 0
  62. return (
  63. <Panel.Layout>
  64. <Panel.Header side="right">
  65. <h3>Style</h3>
  66. <IconButton onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}>
  67. <X />
  68. </IconButton>
  69. </Panel.Header>
  70. <Content>
  71. <ColorPicker
  72. label="Fill"
  73. color={shapesStyle.fill}
  74. colors={fillColors}
  75. onChange={(color) => state.send('CHANGED_STYLE', { fill: color })}
  76. />
  77. <ColorPicker
  78. label="Stroke"
  79. color={shapesStyle.stroke}
  80. colors={strokeColors}
  81. onChange={(color) => state.send('CHANGED_STYLE', { stroke: color })}
  82. />
  83. <Row>
  84. <label htmlFor="width">Width</label>
  85. <WidthPicker strokeWidth={Number(shapesStyle.strokeWidth)} />
  86. </Row>
  87. <AlignDistribute
  88. hasTwoOrMore={selectedIds.length > 1}
  89. hasThreeOrMore={selectedIds.length > 2}
  90. />
  91. <ButtonsRow>
  92. <IconButton
  93. disabled={!hasSelection}
  94. onClick={() => state.send('DELETED')}
  95. >
  96. <Trash />
  97. </IconButton>
  98. <IconButton
  99. disabled={!hasSelection}
  100. onClick={() => state.send('DUPLICATED')}
  101. >
  102. <Copy />
  103. </IconButton>
  104. <IconButton>
  105. <Unlock />
  106. </IconButton>
  107. </ButtonsRow>
  108. </Content>
  109. </Panel.Layout>
  110. )
  111. }
  112. const StylePanelRoot = styled(Panel.Root, {
  113. minWidth: 1,
  114. width: 184,
  115. maxWidth: 184,
  116. overflow: 'hidden',
  117. position: 'relative',
  118. variants: {
  119. isOpen: {
  120. true: {},
  121. false: {
  122. height: 34,
  123. width: 34,
  124. },
  125. },
  126. },
  127. })
  128. const Content = styled(Panel.Content, {
  129. padding: 8,
  130. })
  131. const Row = styled('div', {
  132. position: 'relative',
  133. display: 'flex',
  134. width: '100%',
  135. background: 'none',
  136. border: 'none',
  137. cursor: 'pointer',
  138. outline: 'none',
  139. alignItems: 'center',
  140. justifyContent: 'space-between',
  141. padding: '4px 2px 4px 12px',
  142. '& label': {
  143. fontFamily: '$ui',
  144. fontSize: '$2',
  145. fontWeight: '$1',
  146. margin: 0,
  147. padding: 0,
  148. },
  149. '& > svg': {
  150. position: 'relative',
  151. },
  152. })
  153. const ButtonsRow = styled('div', {
  154. position: 'relative',
  155. display: 'flex',
  156. width: '100%',
  157. background: 'none',
  158. border: 'none',
  159. cursor: 'pointer',
  160. outline: 'none',
  161. alignItems: 'center',
  162. justifyContent: 'flex-start',
  163. padding: 4,
  164. })