Browse Source

Make panels collapsible (#239)

* Make panels collapsible

- Add Panel component with collapse logic
- Use the component in all the necessary panel groups

* Remove unnecessary container from PanelCanvas

* Add "hide property" to Pane component to hide Panel contents using a prop

- Instead of doing conditional rendering, pass the condition to Panel as props

* Change collapse icon rotation for closed

- Use one icon and use CSS transforms to rotate it

* Remove unnecessary imports from PanelSelection
Gasim Gasimzada 5 năm trước cách đây
mục cha
commit
36ce6a26e6

+ 43 - 0
src/components/Panel.tsx

@@ -0,0 +1,43 @@
+import React, { useState } from "react";
+
+interface PanelProps {
+  title: string;
+  defaultCollapsed?: boolean;
+  hide?: boolean;
+}
+
+export const Panel: React.FC<PanelProps> = ({
+  title,
+  children,
+  defaultCollapsed = false,
+  hide = false
+}) => {
+  const [collapsed, setCollapsed] = useState(defaultCollapsed);
+
+  if (hide) return null;
+
+  return (
+    <div className="panel">
+      <h4>{title}</h4>
+      <button
+        className="btn-panel-collapse"
+        type="button"
+        onClick={e => {
+          e.preventDefault();
+          setCollapsed(collapsed => !collapsed);
+        }}
+      >
+        {
+          <span
+            className={`btn-panel-collapse-icon ${
+              collapsed ? "btn-panel-collapse-icon-closed" : ""
+            }`}
+          >
+            ▼
+          </span>
+        }
+      </button>
+      {!collapsed && <div className="panelColumn">{children}</div>}
+    </div>
+  );
+};

+ 15 - 17
src/components/panels/PanelCanvas.tsx

@@ -1,6 +1,7 @@
 import React from "react";
 
 import { ColorPicker } from "../ColorPicker";
+import { Panel } from "../Panel";
 
 interface PanelCanvasProps {
   viewBackgroundColor: string;
@@ -14,22 +15,19 @@ export const PanelCanvas: React.FC<PanelCanvasProps> = ({
   onClearCanvas
 }) => {
   return (
-    <>
-      <h4>Canvas</h4>
-      <div className="panelColumn">
-        <h5>Canvas Background Color</h5>
-        <ColorPicker
-          color={viewBackgroundColor}
-          onChange={color => onViewBackgroundColorChange(color)}
-        />
-        <button
-          type="button"
-          onClick={onClearCanvas}
-          title="Clear the canvas & reset background color"
-        >
-          Clear canvas
-        </button>
-      </div>
-    </>
+    <Panel title="Canvas">
+      <h5>Canvas Background Color</h5>
+      <ColorPicker
+        color={viewBackgroundColor}
+        onChange={color => onViewBackgroundColorChange(color)}
+      />
+      <button
+        type="button"
+        onClick={onClearCanvas}
+        title="Clear the canvas & reset background color"
+      >
+        Clear canvas
+      </button>
+    </Panel>
   );
 };

+ 3 - 3
src/components/panels/PanelExport.tsx

@@ -1,5 +1,6 @@
 import React from "react";
 import { EditableText } from "../EditableText";
+import { Panel } from "../Panel";
 
 interface PanelExportProps {
   projectName: string;
@@ -21,8 +22,7 @@ export const PanelExport: React.FC<PanelExportProps> = ({
   onExportAsPNG
 }) => {
   return (
-    <>
-      <h4>Export</h4>
+    <Panel title="Export">
       <div className="panelColumn">
         <h5>Name</h5>
         {projectName && (
@@ -47,6 +47,6 @@ export const PanelExport: React.FC<PanelExportProps> = ({
         <button onClick={onSaveScene}>Save as...</button>
         <button onClick={onLoadScene}>Load file...</button>
       </div>
-    </>
+    </Panel>
   );
 };

+ 2 - 3
src/components/panels/PanelSelection.tsx

@@ -14,8 +14,7 @@ export const PanelSelection: React.FC<PanelSelectionProps> = ({
   onSendToBack
 }) => {
   return (
-    <>
-      <h4>Selection</h4>
+    <div>
       <div className="buttonList">
         <button type="button" onClick={onBringForward}>
           Bring forward
@@ -30,6 +29,6 @@ export const PanelSelection: React.FC<PanelSelectionProps> = ({
           Send to back
         </button>
       </div>
-    </>
+    </div>
   );
 };

+ 3 - 3
src/components/panels/PanelTools.tsx

@@ -2,6 +2,7 @@ import React from "react";
 
 import { SHAPES } from "../../shapes";
 import { capitalizeString } from "../../utils";
+import { Panel } from "../Panel";
 
 interface PanelToolsProps {
   activeTool: string;
@@ -13,8 +14,7 @@ export const PanelTools: React.FC<PanelToolsProps> = ({
   onToolChange
 }) => {
   return (
-    <>
-      <h4>Shapes</h4>
+    <Panel title="Shapes">
       <div className="panelTools">
         {SHAPES.map(({ value, icon }) => (
           <label
@@ -33,6 +33,6 @@ export const PanelTools: React.FC<PanelToolsProps> = ({
           </label>
         ))}
       </div>
-    </>
+    </Panel>
   );
 };

+ 103 - 104
src/index.tsx

@@ -46,6 +46,7 @@ import { PanelSelection } from "./components/panels/PanelSelection";
 import { PanelColor } from "./components/panels/PanelColor";
 import { PanelExport } from "./components/panels/PanelExport";
 import { PanelCanvas } from "./components/panels/PanelCanvas";
+import { Panel } from "./components/Panel";
 
 import "./styles.scss";
 
@@ -381,112 +382,110 @@ class App extends React.Component<{}, AppState> {
               this.forceUpdate();
             }}
           />
-          {someElementIsSelected(elements) && (
-            <div className="panelColumn">
-              <PanelSelection
-                onBringForward={this.moveOneRight}
-                onBringToFront={this.moveAllRight}
-                onSendBackward={this.moveOneLeft}
-                onSendToBack={this.moveAllLeft}
-              />
-
-              <PanelColor
-                title="Stroke Color"
-                onColorChange={this.changeStrokeColor}
-                colorValue={getSelectedAttribute(
-                  elements,
-                  element => element.strokeColor
-                )}
-              />
-
-              {hasBackground(elements) && (
-                <>
-                  <PanelColor
-                    title="Background Color"
-                    onColorChange={this.changeBackgroundColor}
-                    colorValue={getSelectedAttribute(
-                      elements,
-                      element => element.backgroundColor
-                    )}
-                  />
-
-                  <h5>Fill</h5>
-                  <ButtonSelect
-                    options={[
-                      { value: "solid", text: "Solid" },
-                      { value: "hachure", text: "Hachure" },
-                      { value: "cross-hatch", text: "Cross-hatch" }
-                    ]}
-                    value={getSelectedAttribute(
-                      elements,
-                      element => element.fillStyle
-                    )}
-                    onChange={value => {
-                      this.changeProperty(element => {
-                        element.fillStyle = value;
-                      });
-                    }}
-                  />
-                </>
+          <Panel title="Selection" hide={!someElementIsSelected(elements)}>
+            <PanelSelection
+              onBringForward={this.moveOneRight}
+              onBringToFront={this.moveAllRight}
+              onSendBackward={this.moveOneLeft}
+              onSendToBack={this.moveAllLeft}
+            />
+
+            <PanelColor
+              title="Stroke Color"
+              onColorChange={this.changeStrokeColor}
+              colorValue={getSelectedAttribute(
+                elements,
+                element => element.strokeColor
               )}
+            />
+
+            {hasBackground(elements) && (
+              <>
+                <PanelColor
+                  title="Background Color"
+                  onColorChange={this.changeBackgroundColor}
+                  colorValue={getSelectedAttribute(
+                    elements,
+                    element => element.backgroundColor
+                  )}
+                />
+
+                <h5>Fill</h5>
+                <ButtonSelect
+                  options={[
+                    { value: "solid", text: "Solid" },
+                    { value: "hachure", text: "Hachure" },
+                    { value: "cross-hatch", text: "Cross-hatch" }
+                  ]}
+                  value={getSelectedAttribute(
+                    elements,
+                    element => element.fillStyle
+                  )}
+                  onChange={value => {
+                    this.changeProperty(element => {
+                      element.fillStyle = value;
+                    });
+                  }}
+                />
+              </>
+            )}
+
+            {hasStroke(elements) && (
+              <>
+                <h5>Stroke Width</h5>
+                <ButtonSelect
+                  options={[
+                    { value: 1, text: "Thin" },
+                    { value: 2, text: "Bold" },
+                    { value: 4, text: "Extra Bold" }
+                  ]}
+                  value={getSelectedAttribute(
+                    elements,
+                    element => element.strokeWidth
+                  )}
+                  onChange={value => {
+                    this.changeProperty(element => {
+                      element.strokeWidth = value;
+                    });
+                  }}
+                />
+
+                <h5>Sloppiness</h5>
+                <ButtonSelect
+                  options={[
+                    { value: 0, text: "Draftsman" },
+                    { value: 1, text: "Artist" },
+                    { value: 3, text: "Cartoonist" }
+                  ]}
+                  value={getSelectedAttribute(
+                    elements,
+                    element => element.roughness
+                  )}
+                  onChange={value =>
+                    this.changeProperty(element => {
+                      element.roughness = value;
+                    })
+                  }
+                />
+              </>
+            )}
+
+            <h5>Opacity</h5>
+            <input
+              type="range"
+              min="0"
+              max="100"
+              onChange={this.changeOpacity}
+              value={
+                getSelectedAttribute(elements, element => element.opacity) ||
+                0 /* Put the opacity at 0 if there are two conflicting ones */
+              }
+            />
 
-              {hasStroke(elements) && (
-                <>
-                  <h5>Stroke Width</h5>
-                  <ButtonSelect
-                    options={[
-                      { value: 1, text: "Thin" },
-                      { value: 2, text: "Bold" },
-                      { value: 4, text: "Extra Bold" }
-                    ]}
-                    value={getSelectedAttribute(
-                      elements,
-                      element => element.strokeWidth
-                    )}
-                    onChange={value => {
-                      this.changeProperty(element => {
-                        element.strokeWidth = value;
-                      });
-                    }}
-                  />
-
-                  <h5>Sloppiness</h5>
-                  <ButtonSelect
-                    options={[
-                      { value: 0, text: "Draftsman" },
-                      { value: 1, text: "Artist" },
-                      { value: 3, text: "Cartoonist" }
-                    ]}
-                    value={getSelectedAttribute(
-                      elements,
-                      element => element.roughness
-                    )}
-                    onChange={value =>
-                      this.changeProperty(element => {
-                        element.roughness = value;
-                      })
-                    }
-                  />
-                </>
-              )}
-
-              <h5>Opacity</h5>
-              <input
-                type="range"
-                min="0"
-                max="100"
-                onChange={this.changeOpacity}
-                value={
-                  getSelectedAttribute(elements, element => element.opacity) ||
-                  0 /* Put the opacity at 0 if there are two conflicting ones */
-                }
-              />
-
-              <button onClick={this.deleteSelectedElements}>
-                Delete selected
-              </button>
-            </div>
-          )}
+            <button onClick={this.deleteSelectedElements}>
+              Delete selected
+            </button>
+          </Panel>
           <PanelCanvas
             onClearCanvas={this.clearCanvas}
             onViewBackgroundColorChange={val =>

+ 21 - 0
src/styles.scss

@@ -30,6 +30,27 @@ body {
     margin: 10px 0 10px 0;
   }
 
+  .panel {
+    position: relative;
+    .btn-panel-collapse {
+      position: absolute;
+      top: -2px;
+      right: 5px;
+      background: none;
+      margin: 0px;
+      color: black;
+    }
+
+    .btn-panel-collapse-icon {
+      transform: none;
+      display: inline-block;
+    }
+
+    .btn-panel-collapse-icon-closed {
+      transform: rotateZ(90deg);
+    }
+  }
+
   .panelTools {
     display: flex;
     flex-wrap: wrap;