📌 작성자 개발 환경
OS: Mac OS
Tool: Visual Studio Code

4 분 소요

til

사이드바 섹션

커스텀 페이지에서 사이드바의 요소들을 그림판에 추가하기 위한 사이드바 섹션 구현

사이드바 구성

커스텀 제과에 들어갈 사이드바의 요소들
- 크림섹션, 필링섹션, 토핑세션
고려할점
- 서버에서 받아온 이미지의 배치
- 서버에서 받아온 이미지의 크기 조정


//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_idproduct_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열로 배치되도록 설정했고 스크롤 가능한 영역을 정의하여 이미지가 너무 많을 때 스크롤할 수 있도록 구현하였다.

til

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 핸들러를 호출한다.

결과


til

댓글남기기