[MainProject] 커스텀 사이드바 드래그
사이드바 섹션
커스텀 페이지에서 사이드바의 요소들을 그림판에 추가하기 위한 사이드바 섹션
구현
사이드바 구성
커스텀 제과에 들어갈 사이드바의 요소들
- 크림섹션, 필링섹션, 토핑세션
고려할점
- 서버에서 받아온 이미지의 배치
- 서버에서 받아온 이미지의 크기 조정
//CustomSidebar.tsx
import styled from 'styled-components';
import React, { useEffect, useState } from 'react';
import custom_icon from '../../../assets/images/img_modal/custom_icon.png';
import { getCustomBoard } from '../../../api/customApis';
import ToppingSection from './ToppingSection';
import FillingSection from './FillingSection';
import CreamSection from './CreamSection';
// Sidebar 스타일드 컴포넌트
const Sidebar = styled.div`
position: fixed;
top: 0;
left: 0;
width: 21%;
height: 100%;
background-color: #ffec9e;
backdrop-filter: blur(5px);
overflow: hidden;
border-right: 14px solid #cdc4aa;
`;
// SidebarContent 스타일드 컴포넌트
const SidebarContent = styled.div`
position: relative;
height: 100%;
display: flex;
flex-direction: column;
padding-top: 25px;
overflow: hidden;
`;
// CustomIcon 스타일드 컴포넌트
const CustomIcon = styled.img`
width: 40px;
height: 10px;
margin-bottom: 20px;
margin-left: 15px;
`;
// CustomSidebar 컴포넌트
interface CustomSidebarProps {
store_id: number;
product_id: number;
}
const CustomSidebar: React.FC<CustomSidebarProps> = ({ store_id, product_id }) => {
// 각 섹션별 재료 리스트 상태
const [toppingIngredientList, setToppingIngredientList] = useState<any[]>([]);
const [fillingIngredientList, setFillingIngredientList] = useState<any[]>([]);
const [creamIngredientList, setCreamIngredientList] = useState<any[]>([]);
// 이미지 드래그 시작 시 호출되는 함수
const handleImageDragStart = (event: React.DragEvent<HTMLImageElement>, imageUrl: string) => {
event.dataTransfer.setData('text/plain', imageUrl);
event.dataTransfer.setData('application/my-app-type', 'image');
};
// 컴포넌트 마운트 시 및 store_id, product_id 변경 시 데이터 로드
useEffect(() => {
const fetchData = async () => {
const data = await getCustomBoard(store_id, product_id);
if (data) {
setToppingIngredientList(data.toppingIngredientList || []);
setFillingIngredientList(data.fillingIngredientList || []);
setCreamIngredientList(data.creamIngredientList || []);
}
};
fetchData();
}, [store_id, product_id]);
return (
<Sidebar>
<SidebarContent>
<CustomIcon src={custom_icon} alt="Custom Icon" />
{/* 크림 섹션 */}
<CreamSection
onImageDragStart={handleImageDragStart}
creamIngredientList={creamIngredientList}
/>
{/* 토핑 섹션 */}
<ToppingSection
onImageDragStart={handleImageDragStart}
toppingIngredientList={toppingIngredientList}
/>
{/* 필링 섹션 */}
<FillingSection
onImageDragStart={handleImageDragStart}
fillingIngredientList={fillingIngredientList}
/>
</SidebarContent>
</Sidebar>
);
};
export default CustomSidebar;
CustomSidebar 컴포넌트
interface CustomSidebarProps {
store_id: number;
product_id: number;
}
const CustomSidebar: React.FC<CustomSidebarProps> = ({ store_id, product_id }) => {
const [toppingIngredientList, setToppingIngredientList] = useState<any[]>([]);
const [fillingIngredientList, setFillingIngredientList] = useState<any[]>([]);
const [creamIngredientList, setCreamIngredientList] = useState<any[]>([]);
- CustomSidebarProps 인터페이스로
store_id
와product_id
를 받아옴- 매장 별로 다른 사이드바 요소들을 받아오기 위해서
- toppingIngredientList, fillingIngredientList, creamIngredientList에 대한 상태를 정의
useEffect(() => {
const fetchData = async () => {
const data = await getCustomBoard(store_id, product_id);
if (data) {
setToppingIngredientList(data.toppingIngredientList || []);
setFillingIngredientList(data.fillingIngredientList || []);
setCreamIngredientList(data.creamIngredientList || []);
}
};
fetchData();
}, [store_id, product_id]);
useEffect
훅을 사용하여 컴포넌트가 마운트되거나store_id
또는product_id
가 변경될 때마다 실행되는 로직을 정의getCustomBoard
함수를 통해 API를 호출하고 결과 데이터를 상태로 설정
반환
return (
<Sidebar>
<SidebarContent>
<CustomIcon src={custom_icon} alt="Custom Icon" />
<CreamSection
onImageDragStart={handleImageDragStart}
creamIngredientList={creamIngredientList}
/>
<ToppingSection
onImageDragStart={handleImageDragStart}
toppingIngredientList={toppingIngredientList}
/>
<FillingSection
onImageDragStart={handleImageDragStart}
fillingIngredientList={fillingIngredientList}
/>
</SidebarContent>
</Sidebar>
);
- CustomIcon, CreamSection, ToppingSection, FillingSection 컴포넌트를 렌더링
- 해당 섹션들은 이미지 드래그와 관련된 데이터를 전달하며 렌더링
3가지 섹션(크림,토핑,필링)
3가지 섹션의 로직은 유사하므로 크림섹션의 구현을 설명
//CreamSection.tsx
import React from "react";
import styled from "styled-components";
// 섹션 컨테이너 스타일드 컴포넌트
const SectionContainer = styled.div`
position: relative;
display: flex;
flex-direction: column;
margin-bottom: 30px;
margin-left: 11px;
max-height: 500px;
`;
// 콘텐츠 아이템 스타일드 컴포넌트
const ContentItem = styled.div`
margin-left: 1.5%;
width: 88%;
background-color: #fafcff;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
border-radius: 10px;
padding-left: 8px;
padding-bottom: 10px;
`;
// 콘텐츠 이미지 스타일드 컴포넌트
const ContentImage = styled.img`
width: 57px;
height: 57px;
margin-right: 5px;
border: 0.9px solid var(--light-gray);
border-radius: 8px;
transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
cursor: grab;
&:hover {
background-color: var(--gray);
}
&:active {
background-color: var(--normal-gray);
}
`;
// 콘텐츠 이미지 컨테이너 스타일드 컴포넌트
const ContentImageContainer = styled.div`
display: grid;
grid-template-columns: repeat(3, minmax(50px, 1fr)); // 3열로 고정
gap: 8px;
max-width: 100%;
overflow-y: auto;
max-height: 180px;
margin-top: 8px;
`;
// 크림 정보를 나타내는 인터페이스
interface Cream {
ingredientName: string;
ingredientImage: string;
ingredientPrice: number;
}
// CreamSectionProps 인터페이스
interface CreamSectionProps {
onImageDragStart: (
event: React.DragEvent<HTMLImageElement>,
imageUrl: string
) => void;
creamIngredientList: Cream[];
}
// CreamSection 컴포넌트
function CreamSection({
onImageDragStart,
creamIngredientList,
}: CreamSectionProps) {
// 이미지 드래그 시작 시 호출되는 함수
const handleImageDragStart = (event: React.DragEvent<HTMLImageElement>) => {
event.dataTransfer.setData("text/plain", "");
const imageUrl = event.currentTarget.getAttribute("src");
if (imageUrl) {
onImageDragStart(event, imageUrl);
}
};
return (
<SectionContainer>
{/* 섹션 제목 */}
<div
style=
>
<p
style=
>
CREAM.
</p>
</div>
<ContentItem>
{/* 크림 이미지 리스트 */}
<ContentImageContainer>
{creamIngredientList.map((cream, index) => (
<ContentImage
key={index}
src={cream.ingredientImage}
alt={cream.ingredientName}
draggable
onDragStart={handleImageDragStart}
/>
))}
</ContentImageContainer>
</ContentItem>
</SectionContainer>
);
}
export default CreamSection;
사이드바 컨테이너
const ContentImageContainer = styled.div`
display: grid;
grid-template-columns: repeat(3, minmax(50px, 1fr)); // 3열로 고정
gap: 8px;
max-width: 100%;
overflow-y: auto;
max-height: 180px;
margin-top: 8px;
`;
ContentImageContainer 스타일드 컴포넌트: 그리드 컨테이너, 이미지들이 3열로 배치되도록 설정했고 스크롤 가능한 영역을 정의하여 이미지가 너무 많을 때 스크롤할 수 있도록 구현하였다.
interface Cream {
ingredientName: string;
ingredientImage: string;
ingredientPrice: number;
}
interface CreamSectionProps {
onImageDragStart: (
event: React.DragEvent<HTMLImageElement>,
imageUrl: string
) => void;
creamIngredientList: Cream[];
}
- Cream 인터페이스: 크림에 대한 정보를 나타내는 인터페이스로, 이름, 이미지 경로 및 가격을 포함한다.
- CreamSectionProps 인터페이스: CreamSection 컴포넌트의 프로퍼티 타입을 정의한 인터페이스로, 이미지 드래그 이벤트 핸들러와 크림 정보 리스트를 받는다.
function CreamSection({ onImageDragStart, creamIngredientList }: CreamSectionProps) {
// 이미지 드래그 시작 시 호출되는 함수
const handleImageDragStart = (event: React.DragEvent<HTMLImageElement>) => {
event.dataTransfer.setData('text/plain', '');
const imageUrl = event.currentTarget.getAttribute('src');
if (imageUrl) {
onImageDragStart(event, imageUrl);
}
};
handleImageDragStart 함수: 이미지를 드래그할 때 호출되는 함수로, 드래그한 이미지의 URL을 추출하여 onImageDragStart 핸들러를 호출한다.
댓글남기기