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
This commit is contained in:
parent
e38f65dea7
commit
36ce6a26e6
43
src/components/Panel.tsx
Normal file
43
src/components/Panel.tsx
Normal file
@ -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>
|
||||||
|
);
|
||||||
|
};
|
@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { ColorPicker } from "../ColorPicker";
|
import { ColorPicker } from "../ColorPicker";
|
||||||
|
import { Panel } from "../Panel";
|
||||||
|
|
||||||
interface PanelCanvasProps {
|
interface PanelCanvasProps {
|
||||||
viewBackgroundColor: string;
|
viewBackgroundColor: string;
|
||||||
@ -14,22 +15,19 @@ export const PanelCanvas: React.FC<PanelCanvasProps> = ({
|
|||||||
onClearCanvas
|
onClearCanvas
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<Panel title="Canvas">
|
||||||
<h4>Canvas</h4>
|
<h5>Canvas Background Color</h5>
|
||||||
<div className="panelColumn">
|
<ColorPicker
|
||||||
<h5>Canvas Background Color</h5>
|
color={viewBackgroundColor}
|
||||||
<ColorPicker
|
onChange={color => onViewBackgroundColorChange(color)}
|
||||||
color={viewBackgroundColor}
|
/>
|
||||||
onChange={color => onViewBackgroundColorChange(color)}
|
<button
|
||||||
/>
|
type="button"
|
||||||
<button
|
onClick={onClearCanvas}
|
||||||
type="button"
|
title="Clear the canvas & reset background color"
|
||||||
onClick={onClearCanvas}
|
>
|
||||||
title="Clear the canvas & reset background color"
|
Clear canvas
|
||||||
>
|
</button>
|
||||||
Clear canvas
|
</Panel>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { EditableText } from "../EditableText";
|
import { EditableText } from "../EditableText";
|
||||||
|
import { Panel } from "../Panel";
|
||||||
|
|
||||||
interface PanelExportProps {
|
interface PanelExportProps {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
@ -21,8 +22,7 @@ export const PanelExport: React.FC<PanelExportProps> = ({
|
|||||||
onExportAsPNG
|
onExportAsPNG
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<Panel title="Export">
|
||||||
<h4>Export</h4>
|
|
||||||
<div className="panelColumn">
|
<div className="panelColumn">
|
||||||
<h5>Name</h5>
|
<h5>Name</h5>
|
||||||
{projectName && (
|
{projectName && (
|
||||||
@ -47,6 +47,6 @@ export const PanelExport: React.FC<PanelExportProps> = ({
|
|||||||
<button onClick={onSaveScene}>Save as...</button>
|
<button onClick={onSaveScene}>Save as...</button>
|
||||||
<button onClick={onLoadScene}>Load file...</button>
|
<button onClick={onLoadScene}>Load file...</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</Panel>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -14,8 +14,7 @@ export const PanelSelection: React.FC<PanelSelectionProps> = ({
|
|||||||
onSendToBack
|
onSendToBack
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<div>
|
||||||
<h4>Selection</h4>
|
|
||||||
<div className="buttonList">
|
<div className="buttonList">
|
||||||
<button type="button" onClick={onBringForward}>
|
<button type="button" onClick={onBringForward}>
|
||||||
Bring forward
|
Bring forward
|
||||||
@ -30,6 +29,6 @@ export const PanelSelection: React.FC<PanelSelectionProps> = ({
|
|||||||
Send to back
|
Send to back
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@ import React from "react";
|
|||||||
|
|
||||||
import { SHAPES } from "../../shapes";
|
import { SHAPES } from "../../shapes";
|
||||||
import { capitalizeString } from "../../utils";
|
import { capitalizeString } from "../../utils";
|
||||||
|
import { Panel } from "../Panel";
|
||||||
|
|
||||||
interface PanelToolsProps {
|
interface PanelToolsProps {
|
||||||
activeTool: string;
|
activeTool: string;
|
||||||
@ -13,8 +14,7 @@ export const PanelTools: React.FC<PanelToolsProps> = ({
|
|||||||
onToolChange
|
onToolChange
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<Panel title="Shapes">
|
||||||
<h4>Shapes</h4>
|
|
||||||
<div className="panelTools">
|
<div className="panelTools">
|
||||||
{SHAPES.map(({ value, icon }) => (
|
{SHAPES.map(({ value, icon }) => (
|
||||||
<label
|
<label
|
||||||
@ -33,6 +33,6 @@ export const PanelTools: React.FC<PanelToolsProps> = ({
|
|||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</Panel>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
199
src/index.tsx
199
src/index.tsx
@ -46,6 +46,7 @@ import { PanelSelection } from "./components/panels/PanelSelection";
|
|||||||
import { PanelColor } from "./components/panels/PanelColor";
|
import { PanelColor } from "./components/panels/PanelColor";
|
||||||
import { PanelExport } from "./components/panels/PanelExport";
|
import { PanelExport } from "./components/panels/PanelExport";
|
||||||
import { PanelCanvas } from "./components/panels/PanelCanvas";
|
import { PanelCanvas } from "./components/panels/PanelCanvas";
|
||||||
|
import { Panel } from "./components/Panel";
|
||||||
|
|
||||||
import "./styles.scss";
|
import "./styles.scss";
|
||||||
|
|
||||||
@ -381,112 +382,110 @@ class App extends React.Component<{}, AppState> {
|
|||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{someElementIsSelected(elements) && (
|
<Panel title="Selection" hide={!someElementIsSelected(elements)}>
|
||||||
<div className="panelColumn">
|
<PanelSelection
|
||||||
<PanelSelection
|
onBringForward={this.moveOneRight}
|
||||||
onBringForward={this.moveOneRight}
|
onBringToFront={this.moveAllRight}
|
||||||
onBringToFront={this.moveAllRight}
|
onSendBackward={this.moveOneLeft}
|
||||||
onSendBackward={this.moveOneLeft}
|
onSendToBack={this.moveAllLeft}
|
||||||
onSendToBack={this.moveAllLeft}
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
<PanelColor
|
<PanelColor
|
||||||
title="Stroke Color"
|
title="Stroke Color"
|
||||||
onColorChange={this.changeStrokeColor}
|
onColorChange={this.changeStrokeColor}
|
||||||
colorValue={getSelectedAttribute(
|
colorValue={getSelectedAttribute(
|
||||||
elements,
|
elements,
|
||||||
element => element.strokeColor
|
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) && (
|
{hasBackground(elements) && (
|
||||||
<>
|
<>
|
||||||
<h5>Stroke Width</h5>
|
<PanelColor
|
||||||
<ButtonSelect
|
title="Background Color"
|
||||||
options={[
|
onColorChange={this.changeBackgroundColor}
|
||||||
{ value: 1, text: "Thin" },
|
colorValue={getSelectedAttribute(
|
||||||
{ value: 2, text: "Bold" },
|
elements,
|
||||||
{ value: 4, text: "Extra Bold" }
|
element => element.backgroundColor
|
||||||
]}
|
)}
|
||||||
value={getSelectedAttribute(
|
/>
|
||||||
elements,
|
|
||||||
element => element.strokeWidth
|
|
||||||
)}
|
|
||||||
onChange={value => {
|
|
||||||
this.changeProperty(element => {
|
|
||||||
element.strokeWidth = value;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<h5>Sloppiness</h5>
|
<h5>Fill</h5>
|
||||||
<ButtonSelect
|
<ButtonSelect
|
||||||
options={[
|
options={[
|
||||||
{ value: 0, text: "Draftsman" },
|
{ value: "solid", text: "Solid" },
|
||||||
{ value: 1, text: "Artist" },
|
{ value: "hachure", text: "Hachure" },
|
||||||
{ value: 3, text: "Cartoonist" }
|
{ value: "cross-hatch", text: "Cross-hatch" }
|
||||||
]}
|
]}
|
||||||
value={getSelectedAttribute(
|
value={getSelectedAttribute(
|
||||||
elements,
|
elements,
|
||||||
element => element.roughness
|
element => element.fillStyle
|
||||||
)}
|
)}
|
||||||
onChange={value =>
|
onChange={value => {
|
||||||
this.changeProperty(element => {
|
this.changeProperty(element => {
|
||||||
element.roughness = value;
|
element.fillStyle = value;
|
||||||
})
|
});
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<h5>Opacity</h5>
|
{hasStroke(elements) && (
|
||||||
<input
|
<>
|
||||||
type="range"
|
<h5>Stroke Width</h5>
|
||||||
min="0"
|
<ButtonSelect
|
||||||
max="100"
|
options={[
|
||||||
onChange={this.changeOpacity}
|
{ value: 1, text: "Thin" },
|
||||||
value={
|
{ value: 2, text: "Bold" },
|
||||||
getSelectedAttribute(elements, element => element.opacity) ||
|
{ value: 4, text: "Extra Bold" }
|
||||||
0 /* Put the opacity at 0 if there are two conflicting ones */
|
]}
|
||||||
}
|
value={getSelectedAttribute(
|
||||||
/>
|
elements,
|
||||||
|
element => element.strokeWidth
|
||||||
|
)}
|
||||||
|
onChange={value => {
|
||||||
|
this.changeProperty(element => {
|
||||||
|
element.strokeWidth = value;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<button onClick={this.deleteSelectedElements}>
|
<h5>Sloppiness</h5>
|
||||||
Delete selected
|
<ButtonSelect
|
||||||
</button>
|
options={[
|
||||||
</div>
|
{ 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>
|
||||||
|
</Panel>
|
||||||
<PanelCanvas
|
<PanelCanvas
|
||||||
onClearCanvas={this.clearCanvas}
|
onClearCanvas={this.clearCanvas}
|
||||||
onViewBackgroundColorChange={val =>
|
onViewBackgroundColorChange={val =>
|
||||||
|
@ -30,6 +30,27 @@ body {
|
|||||||
margin: 10px 0 10px 0;
|
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 {
|
.panelTools {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user