Refactor control components file structure

This commit is contained in:
Mitchell McCaffrey
2021-07-20 20:41:26 +10:00
parent 24dddad66f
commit b703a08d2c
13 changed files with 47 additions and 47 deletions

View File

@@ -0,0 +1,26 @@
import { IconButton } from "theme-ui";
import BlendOnIcon from "../../../icons/BlendOnIcon";
import BlendOffIcon from "../../../icons/BlendOffIcon";
type AlphaBlendToggleProps = {
useBlending: boolean;
onBlendingChange: (useBlending: boolean) => void;
};
function AlphaBlendToggle({
useBlending,
onBlendingChange,
}: AlphaBlendToggleProps) {
return (
<IconButton
aria-label={useBlending ? "Disable Blending (O)" : "Enable Blending (O)"}
title={useBlending ? "Disable Blending (O)" : "Enable Blending (O)"}
onClick={() => onBlendingChange(!useBlending)}
>
{useBlending ? <BlendOnIcon /> : <BlendOffIcon />}
</IconButton>
);
}
export default AlphaBlendToggle;

View File

@@ -0,0 +1,125 @@
import React, { useState } from "react";
import { Box, SxProp } from "theme-ui";
import colors, { colorOptions, Color } from "../../../helpers/colors";
import MapMenu from "../../map/MapMenu";
type ColorCircleProps = {
color: Color;
selected: boolean;
onClick?: React.MouseEventHandler<HTMLDivElement>;
} & SxProp;
function ColorCircle({ color, selected, onClick, sx }: ColorCircleProps) {
return (
<Box
key={color}
sx={{
borderRadius: "50%",
transform: "scale(0.75)",
backgroundColor: colors[color],
cursor: "pointer",
...sx,
}}
onClick={onClick}
aria-label={`Brush Color ${color}`}
>
{selected && (
<Box
sx={{
width: "100%",
height: "100%",
border: "2px solid white",
position: "absolute",
top: 0,
borderRadius: "50%",
}}
/>
)}
</Box>
);
}
type ColorControlProps = {
color: Color;
onColorChange: (newColor: Color) => void;
exclude: Color[];
};
function ColorControl({ color, onColorChange, exclude }: ColorControlProps) {
const [showColorMenu, setShowColorMenu] = useState(false);
const [colorMenuOptions, setColorMenuOptions] = useState({});
function handleControlClick(event: React.MouseEvent<HTMLDivElement>) {
if (showColorMenu) {
setShowColorMenu(false);
setColorMenuOptions({});
} else {
setShowColorMenu(true);
const rect = event.currentTarget.getBoundingClientRect();
setColorMenuOptions({
// Align the right of the submenu to the left of the tool and center vertically
left: `${rect.left + rect.width / 2}px`,
top: `${rect.bottom + 16}px`,
style: { transform: "translateX(-50%)" },
// Exclude this node from the sub menus auto close
excludeNode: event.currentTarget,
});
}
}
const colorMenu = (
<MapMenu
isOpen={showColorMenu}
onRequestClose={() => {
setShowColorMenu(false);
setColorMenuOptions({});
}}
{...colorMenuOptions}
>
<Box
sx={{
width: "104px",
display: "flex",
flexWrap: "wrap",
justifyContent: "space-between",
}}
p={1}
>
{colorOptions
.filter((color) => !exclude.includes(color))
.map((c) => (
<ColorCircle
key={c}
color={c}
selected={c === color}
onClick={() => {
onColorChange(c);
setShowColorMenu(false);
setColorMenuOptions({});
}}
sx={{ width: "25%", paddingTop: "25%" }}
/>
))}
</Box>
</MapMenu>
);
return (
<>
<ColorCircle
color={color}
selected
onClick={handleControlClick}
sx={{ width: "24px", height: "24px" }}
/>
{colorMenu}
</>
);
}
ColorControl.defaultProps = {
exclude: [],
};
export default ColorControl;

View File

@@ -0,0 +1,31 @@
import { IconButton } from "theme-ui";
import CutOnIcon from "../../../icons/FogCutOnIcon";
import CutOffIcon from "../../../icons/FogCutOffIcon";
type FogCutToggleProps = {
useFogCut: boolean;
onFogCutChange: (useFogCut: boolean) => void;
disabled?: boolean;
};
function FogCutToggle({
useFogCut,
onFogCutChange,
disabled,
}: FogCutToggleProps) {
return (
<IconButton
aria-label={
useFogCut ? "Disable Fog Cutting (C)" : "Enable Fog Cutting (C)"
}
title={useFogCut ? "Disable Fog Cutting (C)" : "Enable Fog Cutting (C)"}
onClick={() => onFogCutChange(!useFogCut)}
disabled={disabled}
>
{useFogCut ? <CutOnIcon /> : <CutOffIcon />}
</IconButton>
);
}
export default FogCutToggle;

View File

@@ -0,0 +1,30 @@
import { IconButton } from "theme-ui";
import PreviewOnIcon from "../../../icons/FogPreviewOnIcon";
import PreviewOffIcon from "../../../icons/FogPreviewOffIcon";
type FogPreviewToggleProps = {
useFogPreview: boolean;
onFogPreviewChange: (useFogCut: boolean) => void;
};
function FogPreviewToggle({
useFogPreview,
onFogPreviewChange,
}: FogPreviewToggleProps) {
return (
<IconButton
aria-label={
useFogPreview ? "Disable Fog Preview (F)" : "Enable Fog Preview (F)"
}
title={
useFogPreview ? "Disable Fog Preview (F)" : "Enable Fog Preview (F)"
}
onClick={() => onFogPreviewChange(!useFogPreview)}
>
{useFogPreview ? <PreviewOnIcon /> : <PreviewOffIcon />}
</IconButton>
);
}
export default FogPreviewToggle;

View File

@@ -0,0 +1,31 @@
import { IconButton } from "theme-ui";
import MultilayerOnIcon from "../../../icons/FogMultilayerOnIcon";
import MultilayerOffIcon from "../../../icons/FogMultilayerOffIcon";
type MultilayerToggleProps = {
multilayer: boolean;
onMultilayerChange: (multilayer: boolean) => void;
disabled?: boolean;
};
function MultilayerToggle({
multilayer,
onMultilayerChange,
disabled,
}: MultilayerToggleProps) {
return (
<IconButton
aria-label={
multilayer ? "Disable Multilayer (L)" : "Enable Multilayer (L)"
}
title={multilayer ? "Disable Multilayer (L)" : "Enable Multilayer (L)"}
onClick={() => onMultilayerChange(!multilayer)}
disabled={disabled}
>
{multilayer ? <MultilayerOnIcon /> : <MultilayerOffIcon />}
</IconButton>
);
}
export default MultilayerToggle;

View File

@@ -0,0 +1,26 @@
import React from "react";
import { IconButton } from "theme-ui";
import RedoIcon from "../../../icons/RedoIcon";
import { isMacLike } from "../../../helpers/shared";
type RedoButtonProps = {
onClick?: React.MouseEventHandler<HTMLButtonElement>;
disabled?: boolean;
};
function RedoButton({ onClick, disabled }: RedoButtonProps) {
return (
<IconButton
title={`Redo (${isMacLike ? "Cmd" : "Ctrl"} + Shift + Z)`}
aria-label={`Redo (${isMacLike ? "Cmd" : "Ctrl"} + Shift + Z)`}
onClick={onClick}
disabled={disabled}
>
<RedoIcon />
</IconButton>
);
}
export default RedoButton;

View File

@@ -0,0 +1,124 @@
import React, { useState, useEffect } from "react";
import { Box, Flex } from "theme-ui";
import RadioIconButton from "../../RadioIconButton";
export type Tool = {
id: string;
title: string;
isSelected: boolean;
icon: React.ReactNode;
disabled?: boolean;
};
type ToolSectionProps = {
collapse: boolean;
tools: Tool[];
onToolClick: (tool: Tool) => void;
};
// Section of map tools with the option to collapse into a vertical list
function ToolSection({ collapse, tools, onToolClick }: ToolSectionProps) {
const [showMore, setShowMore] = useState(false);
const [collapsedTool, setCollapsedTool] = useState<Tool>();
useEffect(() => {
const selectedTool = tools.find((tool) => tool.isSelected);
if (selectedTool) {
setCollapsedTool(selectedTool);
} else {
// No selected tool, deselect if we have a tool or get the first tool if not
setCollapsedTool((prevTool) =>
prevTool ? { ...prevTool, isSelected: false } : tools[0]
);
}
}, [tools]);
function handleToolClick(tool: Tool) {
if (collapse && tool.isSelected) {
setShowMore(!showMore);
} else if (collapse && !tool.isSelected) {
setShowMore(false);
}
onToolClick(tool);
}
function renderTool(tool: Tool) {
return (
<RadioIconButton
title={tool.title}
onClick={() => handleToolClick(tool)}
key={tool.id}
isSelected={tool.isSelected}
disabled={tool.disabled}
>
{tool.icon}
</RadioIconButton>
);
}
if (collapse) {
if (!collapsedTool) {
return null;
}
return (
<Box sx={{ position: "relative" }}>
{renderTool(collapsedTool)}
{/* Render chevron when more tools is available */}
<Box
sx={{
position: "absolute",
width: 0,
height: 0,
borderTop: "4px solid",
borderTopColor: "text",
borderLeft: "4px solid transparent",
borderRight: "4px solid transparent",
transform: "translate(0, -4px) rotate(-45deg)",
bottom: 0,
right: 0,
pointerEvents: "none",
}}
/>
{showMore && (
<Flex
sx={{
position: "absolute",
top: "40px",
left: "50%",
transform: "translateX(-50%)",
flexDirection: "column",
borderRadius: "4px",
}}
bg="overlay"
p={2}
>
{tools.filter((tool) => !tool.isSelected).map(renderTool)}
</Flex>
)}
</Box>
);
} else {
return (
<>
{tools.map((tool) => (
<RadioIconButton
title={tool.title}
onClick={() => handleToolClick(tool)}
key={tool.id}
isSelected={tool.isSelected}
disabled={tool.disabled}
>
{tool.icon}
</RadioIconButton>
))}
</>
);
}
}
ToolSection.defaultProps = {
collapse: false,
};
export default ToolSection;

View File

@@ -0,0 +1,26 @@
import React from "react";
import { IconButton } from "theme-ui";
import UndoIcon from "../../../icons/UndoIcon";
import { isMacLike } from "../../../helpers/shared";
type UndoButtonProps = {
onClick?: React.MouseEventHandler<HTMLButtonElement>;
disabled?: boolean;
};
function UndoButton({ onClick, disabled }: UndoButtonProps) {
return (
<IconButton
title={`Undo (${isMacLike ? "Cmd" : "Ctrl"} + Z)`}
aria-label={`Undo (${isMacLike ? "Cmd" : "Ctrl"} + Z)`}
onClick={onClick}
disabled={disabled}
>
<UndoIcon />
</IconButton>
);
}
export default UndoButton;