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.

style-panel.tsx 3.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  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, Trash, X } from "react-feather"
  7. import { deepCompare, deepCompareArrays, getSelectedShapes } from "utils/utils"
  8. import { shades, fills, strokes } from "state/data"
  9. import ColorPicker from "./color-picker"
  10. import AlignDistribute from "./align-distribute"
  11. import { ShapeStyles } from "types"
  12. const fillColors = { ...shades, ...fills }
  13. const strokeColors = { ...shades, ...strokes }
  14. export default function StylePanel() {
  15. const rContainer = useRef<HTMLDivElement>(null)
  16. const isOpen = useSelector((s) => s.data.settings.isStyleOpen)
  17. return (
  18. <StylePanelRoot ref={rContainer} isOpen={isOpen}>
  19. {isOpen ? (
  20. <SelectedShapeStyles />
  21. ) : (
  22. <IconButton onClick={() => state.send("TOGGLED_STYLE_PANEL_OPEN")}>
  23. <Circle />
  24. </IconButton>
  25. )}
  26. </StylePanelRoot>
  27. )
  28. }
  29. // This panel is going to be hard to keep cool, as we're selecting computed
  30. // information, based on the user's current selection. We might have to keep
  31. // track of this data manually within our state.
  32. function SelectedShapeStyles({}: {}) {
  33. const selectedIds = useSelector(
  34. (s) => Array.from(s.data.selectedIds.values()),
  35. deepCompareArrays
  36. )
  37. const shapesStyle = useSelector((s) => {
  38. const { currentStyle } = s.data
  39. const shapes = getSelectedShapes(s.data)
  40. if (shapes.length === 0) {
  41. return currentStyle
  42. }
  43. const style: Partial<ShapeStyles> = {}
  44. const overrides = new Set<string>([])
  45. for (const shape of shapes) {
  46. for (let key in currentStyle) {
  47. if (overrides.has(key)) continue
  48. if (style[key] === undefined) {
  49. style[key] = shape.style[key]
  50. } else {
  51. if (style[key] === shape.style[key]) continue
  52. style[key] = currentStyle[key]
  53. overrides.add(key)
  54. }
  55. }
  56. }
  57. return style
  58. }, deepCompare)
  59. const hasSelection = selectedIds.length > 0
  60. return (
  61. <Panel.Layout>
  62. <Panel.Header>
  63. <h3>Style</h3>
  64. <Panel.ButtonsGroup>
  65. <IconButton
  66. disabled={!hasSelection}
  67. onClick={() => state.send("DELETED")}
  68. >
  69. <Trash />
  70. </IconButton>
  71. </Panel.ButtonsGroup>
  72. <IconButton onClick={() => state.send("TOGGLED_STYLE_PANEL_OPEN")}>
  73. <X />
  74. </IconButton>
  75. </Panel.Header>
  76. <Content>
  77. <ColorPicker
  78. label="Fill"
  79. color={shapesStyle.fill}
  80. colors={fillColors}
  81. onChange={(color) => state.send("CHANGED_STYLE", { fill: color })}
  82. />
  83. <ColorPicker
  84. label="Stroke"
  85. color={shapesStyle.stroke}
  86. colors={strokeColors}
  87. onChange={(color) => state.send("CHANGED_STYLE", { stroke: color })}
  88. />
  89. <AlignDistribute
  90. hasTwoOrMore={selectedIds.length > 1}
  91. hasThreeOrMore={selectedIds.length > 2}
  92. />
  93. </Content>
  94. </Panel.Layout>
  95. )
  96. }
  97. const StylePanelRoot = styled(Panel.Root, {
  98. minWidth: 1,
  99. width: 184,
  100. maxWidth: 184,
  101. overflow: "hidden",
  102. position: "relative",
  103. variants: {
  104. isOpen: {
  105. true: {},
  106. false: {
  107. height: 34,
  108. width: 34,
  109. },
  110. },
  111. },
  112. })
  113. const Content = styled(Panel.Content, {
  114. padding: 8,
  115. })