참고
- https://mui.com/material-ui/react-toggle-button/
- https://casesandberg.github.io/react-color/
편집기에서 사용하는 버튼을 Mui Toggle Button으로 만들어보자.
링크의 예제를 참고하면 아래와 같이 만들 수 있다.
import React, { useState } from "react";
import { styled } from "@mui/material/styles";
import Box from "@mui/material/Box";
import FormatAlignLeftIcon from "@mui/icons-material/FormatAlignLeft";
import FormatAlignCenterIcon from "@mui/icons-material/FormatAlignCenter";
import FormatAlignRightIcon from "@mui/icons-material/FormatAlignRight";
//import FormatAlignJustifyIcon from "@mui/icons-material/FormatAlignJustify";
import VerticalAlignBottomIcon from "@mui/icons-material/VerticalAlignBottom";
import VerticalAlignCenterIcon from "@mui/icons-material/VerticalAlignCenter";
import VerticalAlignTopIcon from "@mui/icons-material/VerticalAlignTop";
import FormatBoldIcon from "@mui/icons-material/FormatBold";
import FormatItalicIcon from "@mui/icons-material/FormatItalic";
import FormatUnderlinedIcon from "@mui/icons-material/FormatUnderlined";
import FormatColorFillIcon from "@mui/icons-material/FormatColorFill";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
import Divider from "@mui/material/Divider";
import Paper from "@mui/material/Paper";
import ColorizeIcon from "@mui/icons-material/Colorize";
import ToggleButton from "@mui/material/ToggleButton";
import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
import { CompactPicker } from "react-color";
const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }) => ({
"& .MuiToggleButtonGroup-grouped": {
margin: theme.spacing(0.5),
border: 0,
"&.Mui-disabled": {
border: 0,
},
"&:not(:first-of-type)": {
borderRadius: theme.shape.borderRadius,
},
"&:first-of-type": {
borderRadius: theme.shape.borderRadius,
},
},
}));
const MyTextEditorToggleButton = () => {
const [horizontalAlignment, setHorizontalAlignment] = useState("left");
const [verticalAlignment, setVerticalAlignment] = useState("middle");
const [formats, setFormats] = useState(() => []);
const handleAlignment = (event, newAlignment, type) => {
console.log(newAlignment, type);
if (type === "horizontal") setHorizontalAlignment(newAlignment);
else if (type === "vertical") setVerticalAlignment(newAlignment);
};
const handleFormat = (event, newFormats) => {
console.log(newFormats);
setFormats(newFormats);
};
return (
<div>
<Box sx={{ m: 2 }}>
<Paper
elevation={0}
sx={{
display: "flex",
border: (theme) => `1px solid ${theme.palette.divider}`,
flexWrap: "wrap",
width: "540px",
}}
>
<StyledToggleButtonGroup
size="small"
value={horizontalAlignment}
exclusive
onChange={(e, alignment) =>
handleAlignment(e, alignment, "horizontal")
}
aria-label="text alignment"
>
<ToggleButton value="left" aria-label="left aligned">
<FormatAlignLeftIcon />
</ToggleButton>
<ToggleButton value="center" aria-label="centered">
<FormatAlignCenterIcon />
</ToggleButton>
<ToggleButton value="right" aria-label="right aligned">
<FormatAlignRightIcon />
</ToggleButton>
{/* <ToggleButton value="justify" aria-label="justified">
<FormatAlignJustifyIcon />
</ToggleButton> */}
</StyledToggleButtonGroup>
<StyledToggleButtonGroup
size="small"
value={verticalAlignment}
exclusive
onChange={(e, alignment) =>
handleAlignment(e, alignment, "vertical")
}
aria-label="text alignment"
>
<ToggleButton value="top" aria-label="top aligned">
<VerticalAlignTopIcon />
</ToggleButton>
<ToggleButton value="middle" aria-label="middle">
<VerticalAlignCenterIcon />
</ToggleButton>
<ToggleButton value="bottom" aria-label="bottom aligned">
<VerticalAlignBottomIcon />
</ToggleButton>
</StyledToggleButtonGroup>
<Divider flexItem orientation="vertical" sx={{ mx: 0.5, my: 1 }} />
<StyledToggleButtonGroup
size="small"
value={formats}
onChange={handleFormat}
aria-label="text formatting"
>
<ToggleButton value="bold" aria-label="bold">
<FormatBoldIcon />
</ToggleButton>
<ToggleButton value="italic" aria-label="italic">
<FormatItalicIcon />
</ToggleButton>
<ToggleButton value="underlined" aria-label="underlined">
<FormatUnderlinedIcon />
</ToggleButton>
<ToggleButton
value="fontColor"
aria-label="fontColor"
>
<ColorizeIcon />
<ArrowDropDownIcon />
</ToggleButton>
<ToggleButton
value="bgColor"
aria-label="bgColor"
>
<FormatColorFillIcon />
<ArrowDropDownIcon />
</ToggleButton>
</StyledToggleButtonGroup>
</Paper>
</Box>
</div>
);
};
export default MyTextEditorToggleButton;
Color Picker 적용하기
Font Color / Background Color 버튼을 클릭했을 때, 아래와 같이 Color Picker가 나오도록 해보자.
링크의 예시 중 하나인 CompactPicker를 구현해 보자.
npm install react-color --save
먼저 CompactPicker를 import한다.
import { CompactPicker } from "react-color";
color-picker를 on / off 할 변수, 그리고 위치를 나타내는 변수를 추가하고,
글자색과 배경색을 설정하기 위한 변수도 useState로 관리한다.
const [showCompactPicker, setShowCompactPicker] = useState(false);
const [pickerPosition, setPickerPosition] = useState({ top: 0, left: 0 });
const [fontColor, setFontColor] = useState("#000000");
const [bgColor, setBgColor] = useState("#FFFFFF");
Color ToggleButton에 아래의 이벤트를 추가하자.
<ToggleButton
value="fontColor"
aria-label="fontColor"
onClick={(e) => handleToggleCompactPicker(e, "fontColor")} // 추가
>
<ColorizeIcon />
<ArrowDropDownIcon />
</ToggleButton>
<ToggleButton
value="bgColor"
aria-label="bgColor"
onClick={(e) => handleToggleCompactPicker(e, "bgColor")} // 추가
>
<FormatColorFillIcon />
<ArrowDropDownIcon />
</ToggleButton>
추가한 메서드는 다음과 같다.
선택한 버튼을 기준으로 color picker의 위치를 구하고 useState를 이용하여 picker가 나타나도록 하였다.
const handleToggleCompactPicker = (event, type) => {
const iconButton = event.currentTarget;
const rect = iconButton.getBoundingClientRect();
const pickerTop = rect.bottom + window.scrollY;
const pickerLeft = rect.left + window.scrollX;
setPickerPosition({ top: pickerTop, left: pickerLeft });
setShowCompactPicker((prev) => !prev);
};
color picker는 showCompactPicker가 true인 경우만 나오게 한다.
{showCompactPicker && (
<div
className="compact-picker-container"
style={{
position: "absolute",
top: pickerPosition.top + "px",
left: pickerPosition.left + "px",
}}
>
<div
style={{
position: "fixed",
top: "0px",
right: "0px",
bottom: "0px",
left: "0px",
}}
onClick={handleClose}
/>
{getColorPicker()}
</div>
)}
handleClose를 div에 추가해서 formats에 fontColor나 bgColor가 없으면 color picker를 사라지게 한다.
const handleClose = () => {
let fms = formats.filter(
(item) => (item === "fontColor" || item === "bgColor") === false
);
setFormats(fms);
setShowCompactPicker(false);
};
getColorPicker는 fontColor / bgColor에 따라 구분되도록 한다.
const handleChangeComplete = (color, event) => {
let colorType = formats.includes("fontColor") ? "fontColor" : "bgColor";
console.log(colorType, color.hex);
if (colorType === "fontColor") setFontColor(color.hex);
else setBgColor(color.hex);
};
const getColorPicker = () => {
let colorType = formats.includes("fontColor") ? "fontColor" : "bgColor";
return (
<CompactPicker
color={colorType === "fontColor" ? fontColor : bgColor}
onChangeComplete={handleChangeComplete}
/>
);
};
이제 버튼을 눌러서 이벤트가 제대로 동작하는지 확인해 보자.
전체 코드는 다음과 같다.
import React, { useState, useEffect } from "react";
import { styled } from "@mui/material/styles";
import Box from "@mui/material/Box";
import FormatAlignLeftIcon from "@mui/icons-material/FormatAlignLeft";
import FormatAlignCenterIcon from "@mui/icons-material/FormatAlignCenter";
import FormatAlignRightIcon from "@mui/icons-material/FormatAlignRight";
//import FormatAlignJustifyIcon from "@mui/icons-material/FormatAlignJustify";
import VerticalAlignBottomIcon from "@mui/icons-material/VerticalAlignBottom";
import VerticalAlignCenterIcon from "@mui/icons-material/VerticalAlignCenter";
import VerticalAlignTopIcon from "@mui/icons-material/VerticalAlignTop";
import FormatBoldIcon from "@mui/icons-material/FormatBold";
import FormatItalicIcon from "@mui/icons-material/FormatItalic";
import FormatUnderlinedIcon from "@mui/icons-material/FormatUnderlined";
import FormatColorFillIcon from "@mui/icons-material/FormatColorFill";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
import Divider from "@mui/material/Divider";
import Paper from "@mui/material/Paper";
import ColorizeIcon from "@mui/icons-material/Colorize";
import ToggleButton from "@mui/material/ToggleButton";
import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
import { CompactPicker } from "react-color";
const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }) => ({
"& .MuiToggleButtonGroup-grouped": {
margin: theme.spacing(0.5),
border: 0,
"&.Mui-disabled": {
border: 0,
},
"&:not(:first-of-type)": {
borderRadius: theme.shape.borderRadius,
},
"&:first-of-type": {
borderRadius: theme.shape.borderRadius,
},
},
}));
const MyTextEditorToggleButton = () => {
const [horizontalAlignment, setHorizontalAlignment] = useState("left");
const [verticalAlignment, setVerticalAlignment] = useState("middle");
const [formats, setFormats] = useState(() => []);
const [showCompactPicker, setShowCompactPicker] = useState(false);
const [pickerPosition, setPickerPosition] = useState({ top: 0, left: 0 });
const [fontColor, setFontColor] = useState("#000000");
const [bgColor, setBgColor] = useState("#FFFFFF");
const handleAlignment = (event, newAlignment, type) => {
console.log(newAlignment, type);
if (type === "horizontal") setHorizontalAlignment(newAlignment);
else if (type === "vertical") setVerticalAlignment(newAlignment);
};
const handleFormat = (event, newFormats) => {
console.log(newFormats);
setFormats(newFormats);
};
const handleToggleCompactPicker = (event, type) => {
const iconButton = event.currentTarget;
const rect = iconButton.getBoundingClientRect();
const pickerTop = rect.bottom + window.scrollY;
const pickerLeft = rect.left + window.scrollX;
setPickerPosition({ top: pickerTop, left: pickerLeft });
setShowCompactPicker((prev) => !prev);
};
const handleChangeComplete = (color, event) => {
let colorType = formats.includes("fontColor") ? "fontColor" : "bgColor";
console.log(colorType, color.hex);
if (colorType === "fontColor") setFontColor(color.hex);
else setBgColor(color.hex);
};
const getColorPicker = () => {
let colorType = formats.includes("fontColor") ? "fontColor" : "bgColor";
return (
<CompactPicker
color={colorType === "fontColor" ? fontColor : bgColor}
onChangeComplete={handleChangeComplete}
/>
);
};
const handleClose = () => {
let fms = formats.filter(
(item) => (item === "fontColor" || item === "bgColor") === false
);
setFormats(fms);
setShowCompactPicker(false);
};
return (
<div>
<Box sx={{ m: 2 }}>
<Paper
elevation={0}
sx={{
display: "flex",
border: (theme) => `1px solid ${theme.palette.divider}`,
flexWrap: "wrap",
width: "540px",
}}
>
<StyledToggleButtonGroup
size="small"
value={horizontalAlignment}
exclusive
onChange={(e, alignment) =>
handleAlignment(e, alignment, "horizontal")
}
aria-label="text alignment"
>
<ToggleButton value="left" aria-label="left aligned">
<FormatAlignLeftIcon />
</ToggleButton>
<ToggleButton value="center" aria-label="centered">
<FormatAlignCenterIcon />
</ToggleButton>
<ToggleButton value="right" aria-label="right aligned">
<FormatAlignRightIcon />
</ToggleButton>
{/* <ToggleButton value="justify" aria-label="justified">
<FormatAlignJustifyIcon />
</ToggleButton> */}
</StyledToggleButtonGroup>
<StyledToggleButtonGroup
size="small"
value={verticalAlignment}
exclusive
onChange={(e, alignment) =>
handleAlignment(e, alignment, "vertical")
}
aria-label="text alignment"
>
<ToggleButton value="top" aria-label="top aligned">
<VerticalAlignTopIcon />
</ToggleButton>
<ToggleButton value="middle" aria-label="middle">
<VerticalAlignCenterIcon />
</ToggleButton>
<ToggleButton value="bottom" aria-label="bottom aligned">
<VerticalAlignBottomIcon />
</ToggleButton>
</StyledToggleButtonGroup>
<Divider flexItem orientation="vertical" sx={{ mx: 0.5, my: 1 }} />
<StyledToggleButtonGroup
size="small"
value={formats}
onChange={handleFormat}
aria-label="text formatting"
>
<ToggleButton value="bold" aria-label="bold">
<FormatBoldIcon />
</ToggleButton>
<ToggleButton value="italic" aria-label="italic">
<FormatItalicIcon />
</ToggleButton>
<ToggleButton value="underlined" aria-label="underlined">
<FormatUnderlinedIcon />
</ToggleButton>
<ToggleButton
value="fontColor"
aria-label="fontColor"
onClick={(e) => handleToggleCompactPicker(e, "fontColor")}
>
<ColorizeIcon />
<ArrowDropDownIcon />
</ToggleButton>
<ToggleButton
value="bgColor"
aria-label="bgColor"
onClick={(e) => handleToggleCompactPicker(e, "bgColor")}
>
<FormatColorFillIcon />
<ArrowDropDownIcon />
</ToggleButton>
</StyledToggleButtonGroup>
</Paper>
{showCompactPicker && (
<div
className="compact-picker-container"
style={{
position: "absolute",
top: pickerPosition.top + "px",
left: pickerPosition.left + "px",
}}
>
<div
style={{
position: "fixed",
top: "0px",
right: "0px",
bottom: "0px",
left: "0px",
}}
onClick={handleClose}
/>
{getColorPicker()}
</div>
)}
</Box>
</div>
);
};
export default MyTextEditorToggleButton;
'개발 > React' 카테고리의 다른 글
리액트 - Handsontable True / False Options (0) | 2023.09.29 |
---|---|
리액트 - Hansontable Customizing with GitHub (0) | 2023.09.29 |
리액트 - 파일 편집 후 메일 알림이 가도록 수정하기 with GitHub RESTful API (0) | 2023.09.02 |
리액트 - 로컬 스토리지 유효기간 설정하기 (Setting Local Storage Expiration Time) (0) | 2023.08.21 |
리액트 - 새 창으로 로그인해서 현재 상태 유지하기 (0) | 2023.08.19 |
댓글