Repository: lyra-j/uuno
Pull request #365: [FEAT]#332 요소 속성
Author: login: K-jisu
Labels: name: ✨Feature
description: 기능 개발
name: 🫠 지수
description: 담당자 강지수
Created: 2025-04-29T11:21:51Z | Merged: 2025-04-30T02:27:46Z

## #️⃣ 연관된 이슈

- close #332 
- close #32 
- close #282 

<br>

## 📝 작업 내용

- 도형 색상 변경
- 도형 외곽선 색상 변경 
- 도형 외곽선 종류 변경

<br>

## 🖼 스크린샷

https://github.com/user-attachments/assets/b71be773-8095-4b9e-ab14-576d0dc46e49

<br>

## 💬 리뷰 요구사항

> 아마도 이거 머지 되면 템플릿 작업 가능할 듯 합니다.


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **신규 기능**
  - 그래픽 요소의 외곽선(색상, 두께, 점선 패턴)을 편집할 수 있는 UI와 사이드바가 추가되었습니다.
- **기능 개선**
  - 요소 추가 시 외곽선 스타일(색상, 두께, 점선 패턴 등)과 채우기 색상을 보다 일관되게 적용하도록 개선되었습니다.
  - 캔버스에 표시되는 요소의 외곽선 및 채우기 색상이 동적으로 반영됩니다.
- **UI 개선**
  - 외곽선 스타일 선택을 위한 전용 컨트롤과 슬라이더, 색상 선택기가 추가되었습니다.
  - 점선 패턴 선택 시 시각적 표시가 개선되고 다양한 점선 스타일이 지원됩니다.
- **기타**
  - 점선 패턴 관련 상수와 스타일 옵션이 새로 추가되고, 배열 비교 유틸리티가 도입되었습니다.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Linked issue #332: [FEAT] 도형 속성 탭
# ✨ Feature

### 1. 어떤 기능인가요?

- 도형 속성 탭

<br>

### 2. 작업 상세 내용

- [ ] 도형 속성 탭 만들기

<br>

### 3. 참고할만한 자료 (선택)


Diff:
diff --git a/public/icons/check.svg b/public/icons/check.svg
new file mode 100644
index 00000000..ca9ccf6f
--- /dev/null
+++ b/public/icons/check.svg
@@ -0,0 +1,3 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M15.7387 5.53316L7.78496 13.4884L3.01196 8.71541L4.07246 7.65491L7.78496 11.3674L14.679 4.47266L15.7387 5.53316Z" fill="#3970D5"/>
+</svg>
diff --git a/src/components/editor/editor-ui/element-style-selector/element-stroke-selector.tsx b/src/components/editor/editor-ui/element-style-selector/element-stroke-selector.tsx
new file mode 100644
index 00000000..c1b3c819
--- /dev/null
+++ b/src/components/editor/editor-ui/element-style-selector/element-stroke-selector.tsx
@@ -0,0 +1,189 @@
+import {
+  Accordion,
+  AccordionContent,
+  AccordionItem,
+  AccordionTrigger,
+} from '@/components/ui/accordion';
+import {
+  Popover,
+  PopoverContent,
+  PopoverTrigger,
+} from '@/components/ui/popover';
+import ColorPicker from '../color-picker';
+import { ElementsElement } from '@/types/editor.type';
+import {
+  BASE_LINE,
+  DASHED,
+  DASHED2,
+  DEFAULT_COLOR,
+} from '@/constants/editor.constant';
+import { Slider } from '@/components/ui/slider';
+import {
+  DropdownMenu,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import Line from '@/components/icons/editor/element/line';
+import BottomTabDownIcon from '@/components/icons/editor/bottomtab-down';
+import Image from 'next/image';
+import LineDotted from '@/components/icons/editor/element/line-dotted';
+import LineDotted2 from '@/components/icons/editor/element/line-dotted-2';
+import { isSameArray } from '@/utils/common/same-array.util';
+
+interface ElementStrokeSelectorProps {
+  selectedElement: ElementsElement;
+  handleColorChange: (field: 'fill' | 'stroke', color: string) => void;
+  handleStrokeChange: (strokeWidth: number) => void;
+  handleDashedChange: (dashed: number[]) => void;
+}
+
+const ElementStrokeSelector = ({
+  selectedElement,
+  handleColorChange,
+  handleStrokeChange,
+  handleDashedChange,
+}: ElementStrokeSelectorProps) => {
+  const showSelectedDashed = () => {
+    if (!selectedElement.dash) return <Line width={148} />;
+
+    if (isSameArray(selectedElement.dash, BASE_LINE)) {
+      return <Line width={148} />;
+    } else if (isSameArray(selectedElement.dash, DASHED)) {
+      return <LineDotted width={148} />;
+    } else if (isSameArray(selectedElement.dash, DASHED2)) {
+      return <LineDotted2 width={148} />;
+    }
+    return <Line width={148} />;
+  };
+
+  return (
+    <Accordion type='single' collapsible>
+      <AccordionItem value='stroke'>
+        <AccordionTrigger className='!text-caption-medium text-black'>
+          외곽선
+        </AccordionTrigger>
+
+        <AccordionContent className='space-y-3'>
+          {/* 외곽선 색상 */}
+          <div className='flex h-[26px] items-center justify-between'>
+            <span className='text-caption-regular text-black'>색상</span>
+            <div className='flex items-center gap-[10px] p-[3px]'>
+              <Popover>
+                <PopoverTrigger asChild>
+                  <div
+                    className='h-5 w-5 rounded border-[2px]'
+                    style={{
+                      backgroundColor: selectedElement?.stroke || DEFAULT_COLOR,
+                    }}
+                  />
+                </PopoverTrigger>
+                <PopoverContent className='z-[10] w-auto p-2'>
+                  <ColorPicker
+                    selectedColor={selectedElement?.stroke || DEFAULT_COLOR}
+                    onColorChange={(e) => handleColorChange('stroke', e)}
+                    title='외곽선 색상'
+                  />
+                </PopoverContent>
+              </Popover>
+            </div>
+          </div>
+
+          {/* 외곽선 두께 */}
+          <div className='flex flex-col items-center gap-2 self-stretch'>
+            <div className='flex w-full items-center justify-between self-stretch'>
+              <span className='text-caption-regular text-black'>두께</span>
+              <input
+                type='text'
+                min={2}
+                max={10}
+                value={Number(selectedElement.strokeWidth) || 0}
+                onChange={(e) => handleStrokeChange(Number(e.target.value))}
+                className='flex h-[26px] w-[45px] items-center rounded border border-gray-10 py-1'
+              />
+            </div>
+            <Slider
+              min={2}
+              max={10}
+              step={1}
+              onValueChange={(e) => handleStrokeChange(e[0])}
+            />
+          </div>
+
+          {/* 외곽선 종류 */}
+          <div className='flex flex-col items-center justify-center self-stretch rounded-[6px] border border-gray-10 p-[6px]'>
+            <DropdownMenu>
+              <DropdownMenuTrigger asChild>
+                <div className='flex items-center justify-between self-stretch'>
+                  {showSelectedDashed()}
+                  <BottomTabDownIcon />
+                </div>
+              </DropdownMenuTrigger>
+
+              <DropdownMenuContent>
+                <div>
+                  <DropdownMenuItem>
+                    <div
+                      className='flex h-[18px] items-center justify-between self-stretch'
+                      onClick={() => {
+                        handleDashedChange(BASE_LINE);
+                      }}
+                    >
+                      <Line width={148} />
+                      {isSameArray(selectedElement.dash, BASE_LINE) && (
+                        <Image
+                          src={'/icons/check.svg'}
+                          width={18}
+                          height={18}
+                          alt='체크 아이콘'
+                        />
+                      )}
+                    </div>
+                  </DropdownMenuItem>
+                  <DropdownMenuItem>
+                    <div
+                      className='flex h-[18px] items-center justify-between self-stretch'
+                      onClick={() => {
+                        handleDashedChange(DASHED);
+                      }}
+                    >
+                      <LineDotted width={148} />
+                      {isSameArray(selectedElement.dash, DASHED) && (
+                        <Image
+                          src={'/icons/check.svg'}
+                          width={18}
+                          height={18}
+                          alt='체크 아이콘'
+                        />
+                      )}
+                    </div>
+                  </DropdownMenuItem>
+                  <DropdownMenuItem>
+                    <div
+                      className='flex h-[18px] items-center justify-between self-stretch'
+                      onClick={() => {
+                        handleDashedChange(DASHED2);
+                      }}
+                    >
+                      <LineDotted2 width={148} />
+                      {isSameArray(selectedElement.dash, DASHED2) && (
+                        <Image
+                          src={'/icons/check.svg'}
+                          width={18}
+                          height={18}
+                          alt='체크 아이콘'
+                        />
+                      )}
+                    </div>
+                  </DropdownMenuItem>
+                </div>
+              </DropdownMenuContent>
+            </DropdownMenu>
+          </div>
+        </AccordionContent>
+      </AccordionItem>
+    </Accordion>
+  );
+};
+
+export default ElementStrokeSelector;
diff --git a/src/components/editor/editor-ui/sidebar/editor-sidebar-element.tsx b/src/components/editor/editor-ui/sidebar/editor-sidebar-element.tsx
index 1de55d75..c8fcfd9b 100644
--- a/src/components/editor/editor-ui/sidebar/editor-sidebar-element.tsx
+++ b/src/components/editor/editor-ui/sidebar/editor-sidebar-element.tsx
@@ -9,6 +9,7 @@ import ImagesSidebar from '@/components/editor/elements/images/images-sidebar';
 import QrsocialSidebar from '@/components/editor/elements/qr-social/qrsocial-sidebar';
 import TemplatesSidebar from '@/components/editor/elements/templates/templates-sidebar';
 import ElementsSidebar from '@/components/editor/elements/element/element-sidebar';
+import ElementStyleSidebar from '@/components/editor/elements/element/element-style-sidebar';
 
 const EditorSidebarElement = ({ category }: { category: string }) => {
   const selectedElementId = useEditorStore((state) => state.selectedElementId);
@@ -21,24 +22,36 @@ const EditorSidebarElement = ({ category }: { category: string }) => {
 
   const finalCategory = convertType || category;
 
+  // Todo 추후 프로젝트가 더 커지면 따로 분리(지금은 txt, ele 2개밖에 없어서 둠)
+  const switchSideBar = () => {
+    if (selectedElementId) {
+      switch (selectedElementType) {
+        case 'text':
+          return <TextStyleSidebar />;
+        case 'element':
+          return <ElementStyleSidebar />;
+      }
+    }
+
+    return (
+      <>
+        {finalCategory === CATEGORY.TEMPLATE && <TemplatesSidebar />}
+        {finalCategory === CATEGORY.IMAGE && <ImagesSidebar />}
+        {finalCategory === CATEGORY.UPLOAD && <UploadsSidebar />}
+        {finalCategory === CATEGORY.ELEMENT && <ElementsSidebar />}
+        {finalCategory === CATEGORY.TEXT && <TextSidebar />}
+        {finalCategory === CATEGORY.BACKGROUND && <BackgroundSidebar />}
+        {finalCategory === CATEGORY.SOCIAL && <QrsocialSidebar />}
+      </>
+    );
+  };
+
   return (
     <div
       className='flex w-60 flex-col items-center'
       style={{ borderRight: '1px solid var(--Gray-20, #DBDCDF)' }}
     >
-      {selectedElementId && selectedElementType === 'text' ? (
-        <TextStyleSidebar />
-      ) : (
-        <>
-          {finalCategory === CATEGORY.TEMPLATE && <TemplatesSidebar />}
-          {finalCategory === CATEGORY.IMAGE && <ImagesSidebar />}
-          {finalCategory === CATEGORY.UPLOAD && <UploadsSidebar />}
-          {finalCategory === CATEGORY.ELEMENT && <ElementsSidebar />}
-          {finalCategory === CATEGORY.TEXT && <TextSidebar />}
-          {finalCategory === CATEGORY.BACKGROUND && <BackgroundSidebar />}
-          {finalCategory === CATEGORY.SOCIAL && <QrsocialSidebar />}
-        </>
-      )}
+      {switchSideBar()}
     </div>
   );
 };
diff --git a/src/components/editor/elements/element/ElementsCanvasElement.tsx b/src/components/editor/elements/element/ElementsCanvasElement.tsx
index 926cdf73..3718b532 100644
--- a/src/components/editor/elements/element/ElementsCanvasElement.tsx
+++ b/src/components/editor/elements/element/ElementsCanvasElement.tsx
@@ -1,5 +1,5 @@
 'use client';
-import React, { forwardRef } from 'react';
+import  { forwardRef } from 'react';
 import { Circle, Group, Line, Rect, RegularPolygon, Star } from 'react-konva';
 import Konva from 'konva';
 import { ElementsElement } from '@/types/editor.type';
@@ -35,6 +35,7 @@ const ElementsCanvasElement = forwardRef<
       onTap: (e: any) => onSelect(element.id, e.target),
     };
     return (
+      // Todo : 리팩토링 필요
       <>
         {element.elementType === ElEMENT_TYPE.LINE && (
           <Group {...commonProps} ref={ref as unknown as React.Ref<KonvaGroup>}>
@@ -52,6 +53,7 @@ const ElementsCanvasElement = forwardRef<
                 height={13}
                 cornerRadius={50}
                 fill={element.stroke}
+                strokeWidth={element.strokeWidth}
               />
             )}
             {element.endDecoration === DECORATION_TYPE.CIRCLE && (
@@ -62,6 +64,7 @@ const ElementsCanvasElement = forwardRef<
                 height={13}
                 cornerRadius={50}
                 fill={element.stroke}
+                strokeWidth={element.strokeWidth}
               />
             )}
             {element.startDecoration === DECORATION_TYPE.ARROW && (
@@ -70,9 +73,9 @@ const ElementsCanvasElement = forwardRef<
                 y={element.points[1]}
                 sides={3}
                 radius={5}
-                fill='black'
-                stroke='black'
-                strokeWidth={2}
+                fill={element.fill}
+                stroke={element.stroke}
+                strokeWidth={element.strokeWidth}
                 rotation={30}
               />
             )}
@@ -82,9 +85,9 @@ const ElementsCanvasElement = forwardRef<
                 y={element.points[3]}
                 sides={3}
                 radius={5}
-                fill='black'
-                stroke='black'
-                strokeWidth={2}
+                fill={element.fill}
+                stroke={element.stroke}
+                strokeWidth={element.strokeWidth}
                 rotation={-30}
               />
             )}
@@ -97,6 +100,7 @@ const ElementsCanvasElement = forwardRef<
                 cornerRadius={0}
                 rotation={135}
                 fill={element.stroke}
+                strokeWidth={element.strokeWidth}
               />
             )}
             {element.endDecoration === DECORATION_TYPE.RECTANGLE && (
@@ -108,6 +112,7 @@ const ElementsCanvasElement = forwardRef<
                 cornerRadius={0}
                 rotation={-45}
                 fill={element.stroke}
+                strokeWidth={element.strokeWidth}
               />
             )}
           </Group>
@@ -120,8 +125,8 @@ const ElementsCanvasElement = forwardRef<
             width={element.width}
             height={element.height}
             fill={element.fill}
-            stroke='black'
-            strokeWidth={2}
+            stroke={element.stroke}
+            strokeWidth={element.strokeWidth}
             dash={element.dash}
           />
         )}
@@ -133,8 +138,8 @@ const ElementsCanvasElement = forwardRef<
             width={element.width}
             height={element.height}
             fill={element.fill}
-            stroke='black'
-            strokeWidth={2}
+            stroke={element.stroke}
+            strokeWidth={element.strokeWidth}
             dash={element.dash}
           />
         )}
@@ -148,8 +153,8 @@ const ElementsCanvasElement = forwardRef<
             fill={element.fill}
             sides={element.sides!}
             radius={element.radius!}
-            stroke='black'
-            strokeWidth={2}
+            stroke={element.stroke}
+            strokeWidth={element.strokeWidth}
             dash={element.dash}
           />
         )}
@@ -164,8 +169,9 @@ const ElementsCanvasElement = forwardRef<
             numPoints={element.numPoint!}
             innerRadius={element.innerRadius!}
             outerRadius={element.outerRadius!}
-            stroke='black'
-            strokeWidth={2}
+            stroke={element.stroke}
+            strokeWidth={element.strokeWidth}
+            dash={element.dash}
           />
         )}
       </>
diff --git a/src/components/editor/elements/element/element-sidebar.tsx b/src/components/editor/elements/element/element-sidebar.tsx
index cb5cf4b3..a11f31b0 100644
--- a/src/components/editor/elements/element/element-sidebar.tsx
+++ b/src/components/editor/elements/element/element-sidebar.tsx
@@ -14,12 +14,16 @@ import {
   starOption,
   triangleOption,
 } from '@/constants/editor-elements.constant';
+import { ElEMENT_TYPE } from '@/constants/editor.constant';
 
 const ElementsSidebar = () => {
   const addElement = useEditorStore((state) => state.addElement);
   const setSelectedElementId = useEditorStore(
     (state) => state.setSelectedElementId
   );
+  const setSelectedElementType = useEditorStore(
+    (state) => state.setSelectedElementType
+  );
   const setToolbar = useEditorStore((state) => state.setToolbar);
   const zoom = sideBarStore((state) => state.zoom);
 
@@ -38,7 +42,7 @@ const ElementsSidebar = () => {
     elementType: ElementType;
     start?: LineEndType;
     end?: LineEndType;
-    dot?: [number, number];
+    dot: number[];
     fill?: string;
     sides?: number;
     radius?: number;
@@ -65,13 +69,15 @@ const ElementsSidebar = () => {
       innerRadius,
       outerRadius,
       stroke: 'black',
-      strokeWidth: 3,
+      strokeWidth: 2,
       startDecoration: start,
       endDecoration: end,
     };
 
     addElement(newElement);
     setSelectedElementId(newElement.id);
+    setSelectedElementType(ElEMENT_TYPE.ELEMENT);
+
     setToolbar(
       calculateToolbarPosition({
         x: newElement.x,
@@ -100,7 +106,7 @@ const ElementsSidebar = () => {
         <article className='flex items-center gap-6 self-stretch'>
           <div className='flex w-[52px] flex-col items-start gap-6'>
             {/* 선 */}
-            {lineOptions.map(({ Component, start, end }, idx) => (
+            {lineOptions.map(({ Component, start, end, dashed, fill }, idx) => (
               <Component
                 key={idx}
                 className='cursor-pointer'
@@ -109,6 +115,8 @@ const ElementsSidebar = () => {
                     elementType: 'line',
                     start: start,
                     end: end,
+                    fill,
+                    dot: dashed,
                   })
                 }
               />
@@ -116,25 +124,28 @@ const ElementsSidebar = () => {
           </div>
           <div className='flex w-[52px] flex-col items-start gap-6'>
             {/* 점 선 */}
-            {lineDottedOptions.map(({ Component, start, end, dashed }, idx) => (
-              <Component
-                key={idx}
-                className='cursor-pointer'
-                onClick={() =>
-                  handleAddElement({
-                    elementType: 'line',
-                    start: start,
-                    end: end,
-                    dot: dashed,
-                  })
-                }
-              />
-            ))}
+            {lineDottedOptions.map(
+              ({ Component, start, end, dashed, fill }, idx) => (
+                <Component
+                  key={idx}
+                  className='cursor-pointer'
+                  onClick={() =>
+                    handleAddElement({
+                      elementType: 'line',
+                      start: start,
+                      end: end,
+                      dot: dashed,
+                      fill,
+                    })
+                  }
+                />
+              )
+            )}
           </div>
           <div className='flex w-[52px] flex-col items-start gap-6'>
             {/* 촘촘한 점 선 */}
             {lineDotted2Options.map(
-              ({ Component, start, end, dashed }, idx) => (
+              ({ Component, start, end, dashed, fill }, idx) => (
                 <Component
                   key={idx}
                   className='cursor-pointer'
@@ -144,6 +155,7 @@ const ElementsSidebar = () => {
                       start: start,
                       end: end,
                       dot: dashed,
+                      fill,
                     })
                   }
                 />
@@ -213,7 +225,10 @@ const ElementsSidebar = () => {
         <div className='flex w-full flex-row flex-wrap items-start gap-6'>
           {/* 별 도형 */}
           {starOption.map(
-            ({ Component, fill, numPoint, innerRadius, outerRadius }, idx) => (
+            (
+              { Component, fill, numPoint, innerRadius, outerRadius, dashed },
+              idx
+            ) => (
               <Component
                 key={idx}
                 className='cursor-pointer'
@@ -224,6 +239,7 @@ const ElementsSidebar = () => {
                     numPoint,
                     innerRadius,
                     outerRadius,
+                    dot: dashed,
                   })
                 }
               />
diff --git a/src/components/editor/elements/element/element-style-sidebar.tsx b/src/components/editor/elements/element/element-style-sidebar.tsx
new file mode 100644
index 00000000..1ecf3050
--- /dev/null
+++ b/src/components/editor/elements/element/element-style-sidebar.tsx
@@ -0,0 +1,104 @@
+import {
+  Popover,
+  PopoverContent,
+  PopoverTrigger,
+} from '@/components/ui/popover';
+import ColorPicker from '@/components/editor/editor-ui/color-picker';
+import { useMemo } from 'react';
+import { ElementsElement } from '@/types/editor.type';
+import { useEditorStore } from '@/store/editor.store';
+import { DEFAULT_COLOR, ElEMENT_TYPE } from '@/constants/editor.constant';
+import ElementStrokeSelector from '@/components/editor/editor-ui/element-style-selector/element-stroke-selector';
+
+const ElementStyleSidebar = () => {
+  const isFront = useEditorStore((state) => state.isCanvasFront);
+  const canvasElements = useEditorStore((state) =>
+    isFront ? state.canvasElements : state.canvasBackElements
+  );
+  const selectedElementId = useEditorStore((state) => state.selectedElementId);
+  const updateElement = useEditorStore((state) => state.updateElement);
+
+  /**
+   * 현재 선택된 텍스트 요소 가져오기
+   */
+  const selectedElement = useMemo((): ElementsElement | undefined => {
+    return canvasElements.find(
+      (el) => el.id === selectedElementId && el.type === ElEMENT_TYPE.ELEMENT
+    ) as ElementsElement | undefined;
+  }, [canvasElements, selectedElementId]);
+
+  if (!selectedElementId || !selectedElement) return;
+
+  /**
+   * 색상 변경
+   * @param field 변경할 필드
+   * @param color 색상
+   * @returns
+   */
+  const handleColorChange = (field: 'fill' | 'stroke', color: string) => {
+    updateElement(selectedElementId, { [field]: color });
+  };
+
+  /**
+   * 외곽선 넓이
+   * @param strokeWidth 외곽선 넓이
+   * @returns
+   */
+  const handleStrokeChange = (strokeWidth: number) => {
+    if (!selectedElementId || !selectedElement) return;
+    updateElement(selectedElementId, { strokeWidth: strokeWidth });
+  };
+
+  const handleDashedChange = (dashed: number[]) => {
+    if (!selectedElementId || !selectedElement) return;
+    updateElement(selectedElementId, { dash: dashed });
+  };
+
+  return (
+    <div className='mx-[18px] mt-[14px] flex w-[204px] flex-col items-start gap-[14px]'>
+      <div className='flex content-between items-center self-stretch'>
+        <p className='text-caption-medium text-black'>도형 속성</p>
+      </div>
+
+      <div className='flex flex-col items-start gap-3 self-stretch'>
+        <div className='flex h-[26px] w-[204px] items-center justify-between'>
+          <span className='text-caption-regular text-black'>색상</span>
+          <div className='flex items-center gap-[10px] p-[3px]'>
+            <Popover>
+              <PopoverTrigger asChild>
+                <div
+                  className='h-[20px] w-[20px] rounded border-[2px]'
+                  style={{
+                    backgroundColor: selectedElement?.fill || DEFAULT_COLOR,
+                  }}
+                />
+              </PopoverTrigger>
+              <PopoverContent className='z-[10] w-auto p-2'>
+                <ColorPicker
+                  selectedColor={selectedElement?.fill || DEFAULT_COLOR}
+                  onColorChange={(e) => {
+                    handleColorChange('fill', e);
+                    handleColorChange('stroke', e);
+                  }}
+                  title='도형 색상'
+                />
+              </PopoverContent>
+            </Popover>
+          </div>
+        </div>
+
+        <div className='w-[204px]'>
+          {/* 외곽선 속성 */}
+          <ElementStrokeSelector
+            selectedElement={selectedElement}
+            handleColorChange={handleColorChange}
+            handleStrokeChange={handleStrokeChange}
+            handleDashedChange={handleDashedChange}
+          />
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default ElementStyleSidebar;
diff --git a/src/components/icons/editor/element/line-dotted-2.tsx b/src/components/icons/editor/element/line-dotted-2.tsx
index f29079d6..4520881f 100644
--- a/src/components/icons/editor/element/line-dotted-2.tsx
+++ b/src/components/icons/editor/element/line-dotted-2.tsx
@@ -1,16 +1,17 @@
 import { SVGProps } from 'react';
 
 const LineDotted2 = (props: SVGProps<SVGSVGElement>) => {
+  const { width = 54, ...restProps } = props;
   return (
     <svg
-      width='54'
+      width={`${width}`}
       height='3'
-      viewBox='0 0 54 3'
+      viewBox={`0 0 ${width} 3`}
       xmlns='http://www.w3.org/2000/svg'
-      {...props}
+      {...restProps}
     >
       <path
-        d='M1 1L53 1'
+        d={`M1 1L${width} 1`}
         stroke='#37383C'
         strokeWidth='5'
         strokeDasharray='4,4'
diff --git a/src/components/icons/editor/element/line-dotted.tsx b/src/components/icons/editor/element/line-dotted.tsx
index b182556e..b103bca0 100644
--- a/src/components/icons/editor/element/line-dotted.tsx
+++ b/src/components/icons/editor/element/line-dotted.tsx
@@ -1,17 +1,18 @@
 import { SVGProps } from 'react';
 
 const LineDotted = (props: SVGProps<SVGSVGElement>) => {
+  const { width = 54, ...restProps } = props;
   return (
     <svg
       xmlns='http://www.w3.org/2000/svg'
-      width='54'
+      width={`${width}`}
       height='2'
-      viewBox='0 0 54 2'
+      viewBox={`0 0 ${width} 2`}
       fill='none'
-      {...props}
+      {...restProps}
     >
       <path
-        d='M1 1L53 1'
+        d={`M1 1L${width} 1`}
         stroke='#37383C'
         strokeWidth='6'
         strokeDasharray='10,10'
diff --git a/src/components/icons/editor/element/line.tsx b/src/components/icons/editor/element/line.tsx
index ef0aab9c..57edb82c 100644
--- a/src/components/icons/editor/element/line.tsx
+++ b/src/components/icons/editor/element/line.tsx
@@ -1,17 +1,18 @@
 import { SVGProps } from 'react';
 
 const Line = (props: SVGProps<SVGSVGElement>) => {
+  const { width = 54, ...restProps } = props;
   return (
     <svg
       xmlns='http://www.w3.org/2000/svg'
-      width='54'
+      width={width}
       height='2'
-      viewBox='0 0 54 2'
+      viewBox={`0 0 ${width} 2`}
       fill='none'
-      {...props}
+      {...restProps}
     >
       <path
-        d='M1 1L53 1'
+        d={`M1 1L${width} 1`}
         stroke='#37383C'
         strokeWidth='1.6745'
         strokeLinecap='round'
diff --git a/src/constants/editor-elements.constant.ts b/src/constants/editor-elements.constant.ts
index 65cfcece..cc0be8c8 100644
--- a/src/constants/editor-elements.constant.ts
+++ b/src/constants/editor-elements.constant.ts
@@ -41,6 +41,7 @@ import Triangle from '@/components/icons/editor/element/triangle';
 import TriangleDotted from '@/components/icons/editor/element/triangle-dotted';
 import TriangleWhite from '@/components/icons/editor/element/triangle-white';
 import { LineEndType } from '@/types/editor.type';
+import { BASE_LINE, DASHED, DASHED2 } from '@/constants/editor.constant';
 
 /**
  * 일반 라인
@@ -49,17 +50,79 @@ export const lineOptions: {
   Component: any;
   start: LineEndType;
   end: LineEndType;
+  fill: string;
+  dashed: number[];
 }[] = [
-  { Component: Line, start: 'none', end: 'none' },
-  { Component: RightArrow, start: 'none', end: 'arrow' },
-  { Component: LeftArrow, start: 'arrow', end: 'none' },
-  { Component: LeftRightArrow, start: 'arrow', end: 'arrow' },
-  { Component: RightCircle, start: 'none', end: 'circle' },
-  { Component: LeftCircle, start: 'circle', end: 'none' },
-  { Component: LeftRightCircle, start: 'circle', end: 'circle' },
-  { Component: RightSquare, start: 'none', end: 'rectangle' },
-  { Component: LeftSquare, start: 'rectangle', end: 'none' },
-  { Component: LeftRightSquare, start: 'rectangle', end: 'rectangle' },
+  {
+    Component: Line,
+    start: 'none',
+    end: 'none',
+    fill: 'black',
+    dashed: BASE_LINE,
+  },
+  {
+    Component: RightArrow,
+    start: 'none',
+    end: 'arrow',
+    fill: 'black',
+    dashed: BASE_LINE,
+  },
+  {
+    Component: LeftArrow,
+    start: 'arrow',
+    end: 'none',
+    fill: 'black',
+    dashed: BASE_LINE,
+  },
+  {
+    Component: LeftRightArrow,
+    start: 'arrow',
+    end: 'arrow',
+    fill: 'black',
+    dashed: BASE_LINE,
+  },
+  {
+    Component: RightCircle,
+    start: 'none',
+    end: 'circle',
+    fill: 'black',
+    dashed: BASE_LINE,
+  },
+  {
+    Component: LeftCircle,
+    start: 'circle',
+    end: 'none',
+    fill: 'black',
+    dashed: BASE_LINE,
+  },
+  {
+    Component: LeftRightCircle,
+    start: 'circle',
+    end: 'circle',
+    fill: 'black',
+    dashed: BASE_LINE,
+  },
+  {
+    Component: RightSquare,
+    start: 'none',
+    end: 'rectangle',
+    fill: 'black',
+    dashed: BASE_LINE,
+  },
+  {
+    Component: LeftSquare,
+    start: 'rectangle',
+    end: 'none',
+    fill: 'black',
+    dashed: BASE_LINE,
+  },
+  {
+    Component: LeftRightSquare,
+    start: 'rectangle',
+    end: 'rectangle',
+    fill: 'black',
+    dashed: BASE_LINE,
+  },
 ];
 
 /**
@@ -69,62 +132,78 @@ export const lineDottedOptions: {
   Component: any;
   start: LineEndType;
   end: LineEndType;
-  dashed: [number, number];
+  dashed: number[];
+  fill: string;
 }[] = [
-  { Component: LineDotted, start: 'none', end: 'none', dashed: [20, 20] },
+  {
+    Component: LineDotted,
+    start: 'none',
+    end: 'none',
+    dashed: DASHED,
+    fill: 'black',
+  },
   {
     Component: RightDottedArrow,
     start: 'none',
     end: 'arrow',
-    dashed: [20, 20],
+    dashed: DASHED,
+    fill: 'black',
   },
   {
     Component: LeftDottedArrow,
     start: 'arrow',
     end: 'none',
-    dashed: [20, 20],
+    dashed: DASHED,
+    fill: 'black',
   },
   {
     Component: LeftRightDottedArrow,
     start: 'arrow',
     end: 'arrow',
-    dashed: [20, 20],
+    dashed: DASHED,
+    fill: 'black',
   },
   {
     Component: RightDottedCircle,
     start: 'none',
     end: 'circle',
-    dashed: [20, 20],
+    dashed: DASHED,
+    fill: 'black',
   },
   {
     Component: LeftDottedCircle,
     start: 'circle',
     end: 'none',
-    dashed: [20, 20],
+    dashed: DASHED,
+    fill: 'black',
   },
   {
     Component: LeftRightDottedCircle,
     start: 'circle',
     end: 'circle',
-    dashed: [20, 20],
+    dashed: DASHED,
+    fill: 'black',
   },
   {
     Component: RightDottedSquare,
     start: 'none',
     end: 'rectangle',
-    dashed: [20, 20],
+    dashed: DASHED,
+    fill: 'black',
   },
   {
     Component: LeftDottedSquare,
     start: 'rectangle',
     end: 'none',
-    dashed: [20, 20],
+    dashed: DASHED,
+    fill: 'black',
   },
   {
     Component: LeftRightDottedSquare,
     start: 'rectangle',
     end: 'rectangle',
-    dashed: [20, 20],
+    dashed: DASHED,
+    fill: 'black',
   },
 ];
 
@@ -135,62 +214,78 @@ export const lineDotted2Options: {
   Component: any;
   start: LineEndType;
   end: LineEndType;
-  dashed: [number, number];
+  dashed: number[];
+  fill: string;
 }[] = [
-  { Component: LineDotted2, start: 'none', end: 'none', dashed: [9, 4] },
+  {
+    Component: LineDotted2,
+    start: 'none',
+    end: 'none',
+    dashed: DASHED2,
+    fill: 'black',
+  },
   {
     Component: RightDotted2Arrow,
     start: 'none',
     end: 'arrow',
-    dashed: [9, 4],
+    dashed: DASHED2,
+    fill: 'black',
   },
   {
     Component: LeftDotted2Arrow,
     start: 'arrow',
     end: 'none',
-    dashed: [9, 4],
+    dashed: DASHED2,
+    fill: 'black',
   },
   {
     Component: LeftRightDotted2Arrow,
     start: 'arrow',
     end: 'arrow',
-    dashed: [9, 4],
+    dashed: DASHED2,
+    fill: 'black',
   },
   {
     Component: RightDotted2Circle,
     start: 'none',
     end: 'circle',
-    dashed: [9, 4],
+    dashed: DASHED2,
+    fill: 'black',
   },
   {
     Component: LeftDotted2Circle,
     start: 'circle',
     end: 'none',
-    dashed: [9, 4],
+    dashed: DASHED2,
+    fill: 'black',
   },
   {
     Component: LeftRightDotted2Circle,
     start: 'circle',
     end: 'circle',
-    dashed: [9, 4],
+    dashed: DASHED2,
+    fill: 'black',
   },
   {
     Component: RightDotted2Square,
     start: 'none',
     end: 'rectangle',
-    dashed: [9, 4],
+    dashed: DASHED2,
+    fill: 'black',
   },
   {
     Component: LeftDotted2Square,
     start: 'rectangle',
     end: 'none',
-    dashed: [9, 4],
+    dashed: DASHED2,
+    fill: 'black',
   },
   {
     Component: LeftRightDotted2Square,
     start: 'rectangle',
     end: 'rectangle',
-    dashed: [9, 4],
+    dashed: DASHED2,
+    fill: 'black',
   },
 ];
 
@@ -199,64 +294,64 @@ export const lineDotted2Options: {
  */
 export const squareOption: {
   Component: any;
-  dashed: [number, number];
+  dashed: number[];
   fill: string;
 }[] = [
-  { Component: Square, dashed: [0, 0], fill: 'black' },
+  { Component: Square, dashed: BASE_LINE, fill: 'black' },
   {
     Component: SquareWhite,
-    dashed: [0, 0],
+    dashed: BASE_LINE,
     fill: 'rgba(0,0,0,0)',
   },
   {
     Component: SquareDotted,
-    dashed: [9, 5],
+    dashed: DASHED2,
     fill: 'rgba(0,0,0,0)',
   },
 ];
 
 export const circleOption: {
   Component: any;
-  dashed: [number, number];
+  dashed: number[];
   fill: string;
 }[] = [
-  { Component: Circle, dashed: [0, 0], fill: 'black' },
+  { Component: Circle, dashed: BASE_LINE, fill: 'black' },
   {
     Component: CircleWhite,
-    dashed: [0, 0],
+    dashed: BASE_LINE,
     fill: 'rgba(0,0,0,0)',
   },
   {
     Component: CircleDotted,
-    dashed: [9, 5],
+    dashed: DASHED2,
     fill: 'rgba(0,0,0,0)',
   },
 ];
 
 export const triangleOption: {
   Component: any;
-  dashed: [number, number];
+  dashed: number[];
   fill: string;
   sides: number;
   radius: number;
 }[] = [
   {
     Component: Triangle,
-    dashed: [0, 0],
+    dashed: BASE_LINE,
     fill: 'black',
     sides: 3,
     radius: 53,
   },
   {
     Component: TriangleWhite,
-    dashed: [0, 0],
+    dashed: BASE_LINE,
     fill: 'rgba(0,0,0,0)',
     sides: 3,
     radius: 53,
   },
   {
     Component: TriangleDotted,
-    dashed: [9, 5],
+    dashed: DASHED2,
     fill: 'rgba(0,0,0,0)',
     sides: 3,
     radius: 53,
@@ -269,26 +364,30 @@ export const starOption: {
   numPoint: number;
   innerRadius: number;
   outerRadius: number;
+  dashed: number[];
 }[] = [
   {
     Component: Star4,
-    fill: 'rgba(0,0,0,0)',
+    fill: 'black',
     numPoint: 4,
     innerRadius: 20,
     outerRadius: 50,
+    dashed: BASE_LINE,
   },
   {
     Component: Star5,
-    fill: 'rgba(0,0,0,0)',
+    fill: 'black',
     numPoint: 5,
     innerRadius: 20,
     outerRadius: 50,
+    dashed: BASE_LINE,
   },
   {
     Component: Star6,
-    fill: 'rgba(0,0,0,0)',
+    fill: 'black',
     numPoint: 6,
     innerRadius: 30,
     outerRadius: 50,
+    dashed: BASE_LINE,
   },
 ];
diff --git a/src/constants/editor.constant.ts b/src/constants/editor.constant.ts
index c414588b..b581d335 100644
--- a/src/constants/editor.constant.ts
+++ b/src/constants/editor.constant.ts
@@ -138,6 +138,11 @@ export const DEFAULT_FONT = 'Pretendard';
 //에디터 최대 저장 갯수
 export const MAX_CARDS_PER_USER = 3;
 
+// 에디터 요소 대쉬
+export const BASE_LINE = [0, 0];
+export const DASHED = [20, 20];
+export const DASHED2 = [9, 4];
+
 // unsplash 페이지 수
 export const PER_PAGE = 30;
 
diff --git a/src/types/editor.type.ts b/src/types/editor.type.ts
index 0166eafd..94f03d97 100644
--- a/src/types/editor.type.ts
+++ b/src/types/editor.type.ts
@@ -108,7 +108,7 @@ export interface ElementsElement extends Omit<EditorElement, 'rotation'> {
   height: number;
   fill?: string;
   elementType: ElementType;
-  dash?: [number, number];
+  dash: number[];
   sides?: number;
   radius?: number;
   numPoint?: number;
diff --git a/src/utils/common/same-array.util.ts b/src/utils/common/same-array.util.ts
new file mode 100644
index 00000000..ec6357e4
--- /dev/null
+++ b/src/utils/common/same-array.util.ts
@@ -0,0 +1,4 @@
+// 배열 비교
+export const isSameArray = (a: number[], b: number[]) => {
+  return a.length === b.length && a.every((v, i) => v === b[i]);
+};


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>src/components/icons/editor/element/line-dotted.tsx (1)</summary><blockquote>

`8-8`: **일관성 있는 코드 스타일을 위한 제안**

line.tsx 파일에서는 `width={width}`와 같이 형식 지정 없이 값을 전달하고 있지만, 여기서는 `width={`${width}`}`와 같이 템플릿 리터럴을 사용하고 있습니다. 둘 다 작동하지만 코드베이스 전체에서 일관된 스타일을 유지하는 것이 좋습니다.

```diff
-      width={`${width}`}
+      width={width}
```


Also applies to: 15-15

</blockquote></details>
<details>
<summary>src/components/editor/editor-ui/sidebar/editor-sidebar-element.tsx (1)</summary><blockquote>

`25-26`: **코드 주석에 대한 제안**

주석이 코드의 일시적인 특성을 설명하고 있는데, 프로젝트가 성장함에 따라 이 부분을 리팩토링할 계획이 있다면 TODO 주석 형식으로 표시하는 것이 추적하기 더 쉬울 수 있습니다.

```diff
-  // 추후 프로젝트가 더 커지면 따로 분리(지금은 txt, ele 2개밖에 없어서 둠)
+  // TODO: 프로젝트가 확장되면 컴포넌트 분리하기 (현재는 text와 element 타입만 처리)
```

</blockquote></details>
<details>
<summary>src/components/icons/editor/element/line.tsx (1)</summary><blockquote>

`8-8`: **일관성 있는 코드 스타일 확인**

line-dotted.tsx 파일에서는 `width={`${width}`}`와 같이 템플릿 리터럴을 사용했지만, 여기서는 `width={width}`와 같이 직접 값을 전달하고 있습니다. 두 파일 간에 일관된 스타일을 유지하는 것이 좋습니다. 개인적으로는 템플릿 리터럴 없이 직접 값을 전달하는 방식이 더 간결하고 명확합니다.



Also applies to: 15-15

</blockquote></details>
<details>
<summary>src/components/editor/elements/element/element-style-sidebar.tsx (1)</summary><blockquote>

`13-29`: **ElementStyleSidebar 컴포넌트 구현 및 요소 선택 로직**

Zustand 스토어를 사용하여 상태 관리를 효과적으로 구현했습니다. `useMemo`를 사용하여 선택된 요소를 메모이제이션한 것은 성능 최적화 측면에서 좋은 접근입니다.

그러나 `canvasElements.find` 결과를 직접 캐스팅하기보다는 조건부 타입 가드를 사용하는 것이 더 안전할 수 있습니다.

```diff
- const selectedElement = useMemo((): ElementsElement | undefined => {
-   return canvasElements.find(
-     (el) => el.id === selectedElementId && el.type === ElEMENT_TYPE.ELEMENT
-   ) as ElementsElement | undefined;
- }, [canvasElements, selectedElementId]);

+ const selectedElement = useMemo((): ElementsElement | undefined => {
+   const element = canvasElements.find(
+     (el) => el.id === selectedElementId && el.type === ElEMENT_TYPE.ELEMENT
+   );
+   return element?.type === ElEMENT_TYPE.ELEMENT ? element as ElementsElement : undefined;
+ }, [canvasElements, selectedElementId]);
```

</blockquote></details>
<details>
<summary>src/components/editor/editor-ui/element-style-selector/element-stroke-selector.tsx (1)</summary><blockquote>

`91-110`: **외곽선 두께 조절 UI**

입력 필드와 슬라이더를 함께 사용하여 외곽선 두께를 조절할 수 있게 한 것은 사용자에게 다양한 입력 방식을 제공하는 좋은 UI 패턴입니다. 최소값과 최대값 제한도 적절히 설정되어 있습니다.

그러나 입력 필드에 숫자가 아닌 값이 입력될 경우의 처리가 없습니다. 입력 유효성 검사를 추가하는 것이 좋을 것 같습니다.

```diff
 <input
   type='text'
   min={2}
   max={10}
   value={Number(selectedElement.strokeWidth) || 0}
-  onChange={(e) => handleStrokeChange(Number(e.target.value))}
+  onChange={(e) => {
+    const value = Number(e.target.value);
+    if (!isNaN(value) && value >= 2 && value <= 10) {
+      handleStrokeChange(value);
+    }
+  }}
   className='flex h-[26px] w-[45px] items-center rounded border border-gray-10 px-[6px] py-1'
 />
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between bf8c3b9cfa8336fdca285191e4d5187406648a19 and ad0ffd9c07fba14cba10fe75143abadc68dd90bc.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `public/icons/check.svg` is excluded by `!**/*.svg`

</details>

<details>
<summary>📒 Files selected for processing (12)</summary>

* `src/components/editor/editor-ui/element-style-selector/element-stroke-selector.tsx` (1 hunks)
* `src/components/editor/editor-ui/sidebar/editor-sidebar-element.tsx` (3 hunks)
* `src/components/editor/elements/element/ElementsCanvasElement.tsx` (10 hunks)
* `src/components/editor/elements/element/element-sidebar.tsx` (8 hunks)
* `src/components/editor/elements/element/element-style-sidebar.tsx` (1 hunks)
* `src/components/icons/editor/element/line-dotted-2.tsx` (1 hunks)
* `src/components/icons/editor/element/line-dotted.tsx` (1 hunks)
* `src/components/icons/editor/element/line.tsx` (1 hunks)
* `src/constants/editor-elements.constant.ts` (6 hunks)
* `src/constants/editor.constant.ts` (1 hunks)
* `src/types/editor.type.ts` (1 hunks)
* `src/utils/common/same-array.util.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (4)</summary>

<details>
<summary>src/components/editor/elements/element/element-sidebar.tsx (3)</summary><blockquote>

<details>
<summary>src/store/editor.store.ts (1)</summary>

* `useEditorStore` (80-316)

</details>
<details>
<summary>src/constants/editor.constant.ts (1)</summary>

* `ElEMENT_TYPE` (20-35)

</details>
<details>
<summary>src/constants/editor-elements.constant.ts (3)</summary>

* `lineOptions` (49-126)
* `lineDottedOptions` (131-208)
* `lineDotted2Options` (213-290)

</details>

</blockquote></details>
<details>
<summary>src/components/editor/editor-ui/sidebar/editor-sidebar-element.tsx (1)</summary><blockquote>

<details>
<summary>src/constants/editor.constant.ts (1)</summary>

* `CATEGORY` (10-18)

</details>

</blockquote></details>
<details>
<summary>src/constants/editor-elements.constant.ts (1)</summary><blockquote>

<details>
<summary>src/constants/editor.constant.ts (3)</summary>

* `BASE_LINE` (143-143)
* `DASHED` (144-144)
* `DASHED2` (145-145)

</details>

</blockquote></details>
<details>
<summary>src/components/editor/editor-ui/element-style-selector/element-stroke-selector.tsx (6)</summary><blockquote>

<details>
<summary>src/types/editor.type.ts (1)</summary>

* `ElementsElement` (104-121)

</details>
<details>
<summary>src/utils/common/same-array.util.ts (1)</summary>

* `isSameArray` (2-4)

</details>
<details>
<summary>src/constants/editor.constant.ts (4)</summary>

* `BASE_LINE` (143-143)
* `DASHED` (144-144)
* `DASHED2` (145-145)
* `DEFAULT_COLOR` (135-135)

</details>
<details>
<summary>src/components/ui/accordion.tsx (4)</summary>

* `Accordion` (53-53)
* `AccordionItem` (53-53)
* `AccordionTrigger` (53-53)
* `AccordionContent` (53-53)

</details>
<details>
<summary>src/components/ui/popover.tsx (3)</summary>

* `Popover` (33-33)
* `PopoverTrigger` (33-33)
* `PopoverContent` (33-33)

</details>
<details>
<summary>src/components/ui/slider.tsx (1)</summary>

* `Slider` (28-28)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (39)</summary><blockquote>

<details>
<summary>src/utils/common/same-array.util.ts (1)</summary>

`1-4`: **배열 비교 함수가 잘 구현되었습니다**

배열의 길이와 각 요소를 비교하는 논리가 간결하고 명확합니다. 이 유틸리티 함수는 대시 패턴 배열 비교에 유용하게 활용될 것입니다.

</details>
<details>
<summary>src/constants/editor.constant.ts (1)</summary>

`143-145`: **선 스타일 상수 추가가 적절합니다**

선 패턴을 위한 상수들(`BASE_LINE`, `DASHED`, `DASHED2`)이 명확하게 정의되어 있습니다. 이러한 상수 정의는 코드 전체에서 일관된 스타일을 유지하는 데 도움이 됩니다.

</details>
<details>
<summary>src/types/editor.type.ts (1)</summary>

`111-111`: **dash 속성 타입 변경이 적절합니다**

`dash` 속성을 선택적 튜플 타입에서 필수 배열 타입으로 변경한 것은 좋은 선택입니다. 이로써 다양한 대시 패턴을 유연하게 지원할 수 있으며, 타입 안전성도 향상됩니다.

</details>
<details>
<summary>src/components/icons/editor/element/line-dotted-2.tsx (3)</summary>

`4-4`: **props 구조 분해와 기본값 설정이 잘 되었습니다**

width 속성을 구조 분해하고 기본값을 54로 설정하여 컴포넌트의 유연성을 높였습니다.

---

`7-11`: **동적 SVG 속성 설정이 적절합니다**

SVG의 width와 viewBox 속성을 동적으로 설정하고, 나머지 props를 전개 연산자로 전달하는 방식이 좋습니다.

---

`14-14`: **패스 데이터 동적 생성이 잘 구현되었습니다**

path의 d 속성을 동적으로 계산하여 다양한 너비에 대응할 수 있게 했습니다.

</details>
<details>
<summary>src/components/icons/editor/element/line-dotted.tsx (1)</summary>

`4-4`: **동적 너비 설정 지원 추가, 훌륭해요!**

컴포넌트를 더 유연하게 만들기 위한 좋은 변경입니다. 너비를 동적으로 지정할 수 있게 하여 다양한 상황에서 활용할 수 있게 되었습니다.



Also applies to: 8-8, 10-10, 12-12, 15-15

</details>
<details>
<summary>src/components/editor/editor-ui/sidebar/editor-sidebar-element.tsx (1)</summary>

`25-47`: **사이드바 전환 로직의 적절한 추출, 좋은 개선입니다!**

사이드바 렌더링 로직을 별도의 함수로 추출하여 코드의 가독성과 유지보수성이 향상되었습니다. 선택된 요소 타입에 따라 적절한 사이드바를 렌더링하는 로직이 명확해졌습니다.

</details>
<details>
<summary>src/components/editor/elements/element/ElementsCanvasElement.tsx (3)</summary>

`55-56`: **동적 스타일링 적용, 훌륭한 개선입니다!**

하드코딩된 스타일 값(stroke, strokeWidth, fill 등)을 요소의 속성에서 동적으로 가져오도록 변경한 것은 매우 좋은 개선입니다. 이를 통해 요소의 스타일을 사용자가 더 자유롭게 커스터마이징할 수 있게 되었습니다. 모든 요소 타입에 일관되게 적용되어 있어 코드의 유지보수성도 향상되었습니다.



Also applies to: 66-67, 75-78, 87-90, 102-103, 114-115, 127-129, 140-143, 155-158, 171-174

---

`75-78`: **화살표 장식에 동적 스타일 적용 확인**

화살표 장식에 `fill`, `stroke`, `strokeWidth` 속성을 동적으로 적용한 것은 좋은 변경입니다. 이전에는 고정된 색상을 사용했지만, 이제는 사용자가 선택한 스타일이 일관되게 적용됩니다.



Also applies to: 87-90

---

`173-174`: **Star 요소에 dash 속성 추가**

Star 요소에 dash 속성을 추가하여 다른 도형 요소들과의 일관성을 유지한 것은 좋은 변경입니다. 이로써 모든 도형 요소에 일관된 선 스타일 옵션을 제공할 수 있게 되었습니다.

</details>
<details>
<summary>src/components/icons/editor/element/line.tsx (1)</summary>

`4-4`: **동적 너비 설정 지원 추가, 훌륭해요!**

Line 컴포넌트에 동적 너비 설정 기능을 추가한 것은 훌륭한 개선입니다. 이로써 다양한 상황에서 유연하게 활용할 수 있게 되었습니다. 특히 요소 스타일 선택기에서 다양한 선 스타일을 시각적으로 표현하는 데 유용할 것입니다.



Also applies to: 8-13, 15-15

</details>
<details>
<summary>src/components/editor/elements/element/element-sidebar.tsx (9)</summary>

`17-17`: **ElEMENT_TYPE 상수 추가로 타입 일관성 향상**

새로운 상수 import를 통해 element 타입을 문자열 리터럴 대신 정의된 상수로 사용하여 일관성이 향상되었습니다.

---

`24-26`: **선택된 요소 타입을 관리하기 위한 state 핸들러 추가**

`setSelectedElementType` 함수를 스토어에서 가져와 요소 선택 시 타입 정보를 업데이트하도록 구현한 것은 좋은 접근입니다.

---

`72-72`: **기본 strokeWidth 값 수정**

기본 외곽선 두께가 3에서 2로 감소되었습니다. 디자인 일관성 측면에서 좋은 변경이지만, 기존 요소들과의 시각적 통일성도 고려해야 합니다.

---

`79-80`: **요소 추가 시 선택 요소 타입 설정**

요소 생성 후 `setSelectedElementType`을 호출하여 요소 타입을 명시적으로 설정하는 것은 UI 상태 관리에 매우 중요합니다. 이를 통해 사이드바가 올바른 편집 옵션을 표시할 수 있습니다.

---

`109-120`: **라인 옵션 매핑에 fill 및 dot 속성 추가**

이제 선 요소 생성 시 `fill`과 `dot` 속성이 옵션에서 올바르게 전달됩니다. 이렇게 하면 생성된 요소가 선택된 옵션과 일관된 스타일을 가질 수 있습니다.

---

`127-143`: **점선 옵션에 fill 및 dashed 속성 추가 및 활용**

점선 옵션에서도 이제 `dashed`와 `fill` 속성을 사용하여 요소를 생성할 때 스타일 속성을 일관되게 전달합니다. 아래 속성들을 명시적으로 전달함으로써 코드의 일관성과 가독성이 향상되었습니다.

---

`148-158`: **촘촘한 점선 옵션에 fill 속성 추가**

촘촘한 점선 옵션 함수에서도 `fill` 속성을 추가하여 일관된 스타일링을 지원합니다.

---

`229-242`: **별 옵션 매핑에서 dashed 속성 활용**

별 모양 요소 생성 시 `dashed` 속성을 `dot` 매개변수로 전달하여 일관된 외곽선 스타일 적용이 가능해졌습니다.

---

`45-45`: <details>
<summary>✅ Verification successful</summary>

**요소 생성 함수의 매개변수 타입 수정**

`dot` 매개변수가 옵셔널 튜플 타입에서 필수 배열 타입으로 변경되었습니다. 이는 요소의 대시 패턴을 더 유연하게 정의할 수 있도록 해주는 좋은 개선이지만, 기존 코드와의 호환성을 확인해야 합니다.

---

🏁 Script executed:

```shell
#!/bin/bash
# 현재 dot 또는 dash 속성을 사용하는 함수 호출 검색
rg -A 2 -B 2 "dot:.*\[.*\]|dash:.*\[.*\]" --type ts
```

Length of output: 682

---



</details>

**dot/dash 매개변수 타입 변경 영향 없음 확인**

rg 검색 결과, `dot`·`dash` 속성은 타입 선언부 외 호출 지점이 없어 기존 코드와 호환성 문제가 없습니다.

- src/types/editor.type.ts: `dash: number[]`  
- src/components/editor/elements/element/element-sidebar.tsx: `dot: number[]`

</details>
<details>
<summary>src/components/editor/elements/element/element-style-sidebar.tsx (6)</summary>

`1-12`: **UI 컴포넌트 및 유틸리티 import**

필요한 UI 컴포넌트와 유틸리티를 적절히 가져왔습니다. Popover 컴포넌트를 사용하여 색상 선택기를 구현한 것은 깔끔한 UI 디자인으로 좋은 선택입니다.

---

`30-30`: **선택된 요소 없을 경우 조기 반환**

선택된 요소가 없을 경우 `return`하는 것은 불필요한 렌더링을 방지하는 좋은 패턴입니다.

---

`38-40`: **색상 변경 핸들러**

컴포넌트의 핸들러 함수가 간결하고 목적에 맞게 구현되었습니다. 객체 리터럴에서 계산된 속성 이름을 사용한 것은 코드를 더 간결하게 만듭니다.

---

`47-55`: **외곽선 스타일 및 대시 패턴 변경 핸들러**

외곽선 너비와 대시 패턴을 변경하는 핸들러가 잘 구현되었습니다. 각 핸들러는 선택된 요소가 있는지 확인하는 안전 장치를 포함하고 있습니다.

---

`67-86`: **색상 선택기 구현**

Popover 컴포넌트와 ColorPicker를 사용한 색상 선택 UI가 잘 구현되었습니다. 색상 변경 시 `fill`과 `stroke` 속성을 모두 업데이트하는 것이 의도적인 디자인인지 확인이 필요합니다. 일부 사용자는 도형의 배경색과 외곽선 색상을 다르게 설정하고 싶을 수 있습니다.

이 동작이 의도적인 디자인인지 확인해주세요. 도형 속성 탭의 "색상" 변경이 도형의 배경색과 외곽선 색상 모두를 동일하게 변경하도록 의도되었나요?

---

`90-98`: **ElementStrokeSelector 컴포넌트 통합**

외곽선 속성을 관리하기 위해 별도의 `ElementStrokeSelector` 컴포넌트를 사용한 것은 코드 모듈화 측면에서 좋은 결정입니다. 필요한 속성과 핸들러를 적절히 전달하고 있습니다.

</details>
<details>
<summary>src/constants/editor-elements.constant.ts (9)</summary>

`44-44`: **대시 패턴 상수 import**

대시 패턴을 위한 상수들(`BASE_LINE`, `DASHED`, `DASHED2`)을 import하여 매직 넘버 대신 의미 있는 상수를 사용한 것은 코드 가독성과 유지보수성을 향상시킵니다.

---

`49-55`: **라인 옵션 인터페이스에 fill과 dashed 속성 추가**

라인 옵션 인터페이스에 `fill`과 `dashed` 속성을 추가하여 타입 정의를 명확히 한 것은 좋은 접근입니다. 이를 통해 타입 안전성이 향상되고 자동 완성 기능을 더 잘 활용할 수 있습니다.

---

`56-125`: **라인 옵션 객체에 일관된 스타일 속성 추가**

모든 라인 옵션 객체에 `fill`과 `dashed` 속성을 추가하고 하드코딩된 값 대신 상수를 사용한 것은 코드 일관성과 유지보수성을 크게 향상시킵니다.

---

`131-137`: **점선 옵션에 dashed 및 fill 속성 추가**

점선 옵션에 `dashed`와 `fill` 속성을 추가하고 적절한 값을 설정한 것은 일관된 스타일링을 위한 좋은 접근입니다.



Also applies to: 141-144

---

`213-219`: **촘촘한 점선 옵션에 dashed 및 fill 속성 추가**

촘촘한 점선 옵션에도 동일하게 `dashed`와 `fill` 속성을 추가하여 일관된 코드 구조를 유지한 것이 좋습니다.



Also applies to: 223-226

---

`295-298`: **사각형 옵션에 dashed 속성 추가**

사각형 옵션에 `dashed` 속성을 추가하고 `BASE_LINE` 상수를 사용한 것은 도형 스타일의 일관성을 유지하는 데 중요합니다.



Also applies to: 300-300

---

`313-316`: **원형 옵션에 dashed 속성 추가**

원형 옵션에도 `dashed` 속성을 추가하고 적절한 상수 값을 사용한 것은 일관된 스타일링 구조를 만드는 데 도움이 됩니다.



Also applies to: 318-318

---

`331-334`: **삼각형 옵션에 dashed 속성 추가**

삼각형 옵션에 `dashed` 속성을 추가하고 `BASE_LINE` 상수를 사용한 것은 도형 스타일 속성의 통일성을 제공합니다.



Also applies to: 340-341

---

`361-368`: **별 옵션에 dashed 속성 추가**

별 옵션 객체에 `dashed` 속성을 추가하고 `BASE_LINE` 상수를 사용하여 일관된 스타일 구조를 완성한 것은 좋은 접근입니다.



Also applies to: 374-376

</details>
<details>
<summary>src/components/editor/editor-ui/element-style-selector/element-stroke-selector.tsx (3)</summary>

`1-33`: **필요한 컴포넌트와 유틸리티 import**

UI 컴포넌트, 아이콘, 상수 및 유틸리티 함수를 적절히 import했습니다. 특히 `isSameArray` 유틸리티 함수를 사용하여 배열 비교를 처리하는 것은 좋은 접근입니다.

---

`34-39`: **컴포넌트 Props 인터페이스 정의**

컴포넌트 Props 인터페이스가 명확하게 정의되어 있습니다. 선택된 요소와 각 속성 변경을 위한 핸들러 함수들이 잘 정의되어 있습니다.

---

`59-66`: **아코디언 UI 구조**

아코디언 컴포넌트를 사용하여 외곽선 속성을 접을 수 있는 섹션으로 구성한 것은 공간을 효율적으로 사용할 수 있는 좋은 UI 디자인 선택입니다.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-29T11:26:42Z
user: login: lyra-j
body: 고생이 많으십니다!!👍
이제 속성까지 줄수있다니!! 에디터 그리는 재미가 풍부해졌네요!
코멘트 확인해주시면 될것 같습니다!

state: APPROVED
author_association: OWNER
submitted_at: 2025-04-29T18:34:35Z
user: login: sum529-create
body: 요소 작업.. 어마어마 하네요
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-30T00:04:18Z

Review comments:
diff_hunk: @@ -1,6 +1,6 @@
 import TextSidebar from '@/components/editor/elements/text/text-sidebar';
 import { useEditorStore } from '@/store/editor.store';
-import { CATEGORY } from '@/constants/editor.constant';
+import { CATEGORY, ElEMENT_TYPE } from '@/constants/editor.constant';
path: src/components/editor/editor-ui/sidebar/editor-sidebar-element.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**사용하지 않는 import 및 대소문자 일관성 문제**

`ElEMENT_TYPE`는 이 파일에서 사용되고 있지 않습니다. 또한 상수를 나타내는 변수명이 일반적인 명명 규칙(대문자와 언더스코어)과 다른 대소문자 형식을 사용하고 있습니다.

```diff
-import { CATEGORY, ElEMENT_TYPE } from '@/constants/editor.constant';
+import { CATEGORY } from '@/constants/editor.constant';
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import { CATEGORY } from '@/constants/editor.constant';
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits cf0b7e5 to 99271c4
created_at: 2025-04-29T11:26:41Z
author_association: NONE
diff_hunk: @@ -0,0 +1,188 @@
+import {
+  Accordion,
+  AccordionContent,
+  AccordionItem,
+  AccordionTrigger,
+} from '@/components/ui/accordion';
+import {
+  Popover,
+  PopoverContent,
+  PopoverTrigger,
+} from '@/components/ui/popover';
+import ColorPicker from '../color-picker';
+import { ElementsElement } from '@/types/editor.type';
+import {
+  BASE_LINE,
+  DASHED,
+  DASHED2,
+  DEFAULT_COLOR,
+} from '@/constants/editor.constant';
+import { Slider } from '@/components/ui/slider';
+import {
+  DropdownMenu,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import Line from '@/components/icons/editor/element/line';
+import BottomTabDownIcon from '@/components/icons/editor/bottomtab-down';
+import Image from 'next/image';
+import LineDotted from '@/components/icons/editor/element/line-dotted';
+import LineDotted2 from '@/components/icons/editor/element/line-dotted-2';
+import { isSameArray } from '@/utils/common/same-array.util';
+
+interface ElementStrokeSelectorProps {
+  selectedElement: ElementsElement;
+  handleColorChange: (field: 'fill' | 'stroke', color: string) => void;
+  handleStrokeChange: (strokeWidth: number) => void;
+  handleDashedChange: (dashed: number[]) => void;
+}
+
+const ElementStrokeSelector = ({
+  selectedElement,
+  handleColorChange,
+  handleStrokeChange,
+  handleDashedChange,
+}: ElementStrokeSelectorProps) => {
+  const showSelectedDashed = () => {
+    if (!selectedElement.dash) return;
+
+    if (isSameArray(selectedElement.dash, BASE_LINE)) {
+      return <Line width={148} />;
+    } else if (isSameArray(selectedElement.dash, DASHED)) {
+      return <LineDotted width={148} />;
+    } else if (isSameArray(selectedElement.dash, DASHED2)) {
+      return <LineDotted2 width={148} />;
+    }
+  };
path: src/components/editor/editor-ui/element-style-selector/element-stroke-selector.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**선택된 대시 패턴 시각화 도우미 함수**

`showSelectedDashed` 함수는 현재 선택된 대시 패턴에 따라 적절한 시각적 요소를 반환합니다. `isSameArray`를 사용하여 배열을 비교하는 것은 좋은 방식입니다.

그러나, `selectedElement.dash`가 undefined인 경우 early return하지만, 이후 코드에선 `selectedElement.dash`가 항상 존재한다고 가정합니다. 더 안전한 접근 방식이 필요합니다.

```diff
 const showSelectedDashed = () => {
-  if (!selectedElement.dash) return;
+  if (!selectedElement.dash) return <Line width={148} />; // 기본값 제공

   if (isSameArray(selectedElement.dash, BASE_LINE)) {
     return <Line width={148} />;
   } else if (isSameArray(selectedElement.dash, DASHED)) {
     return <LineDotted width={148} />;
   } else if (isSameArray(selectedElement.dash, DASHED2)) {
     return <LineDotted2 width={148} />;
   }
+  return <Line width={148} />; // 일치하는 패턴이 없는 경우 기본값 반환
 };
```

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit f905589
created_at: 2025-04-29T11:26:41Z
author_association: NONE
diff_hunk: @@ -0,0 +1,188 @@
+import {
+  Accordion,
+  AccordionContent,
+  AccordionItem,
+  AccordionTrigger,
+} from '@/components/ui/accordion';
+import {
+  Popover,
+  PopoverContent,
+  PopoverTrigger,
+} from '@/components/ui/popover';
+import ColorPicker from '../color-picker';
+import { ElementsElement } from '@/types/editor.type';
+import {
+  BASE_LINE,
+  DASHED,
+  DASHED2,
+  DEFAULT_COLOR,
+} from '@/constants/editor.constant';
+import { Slider } from '@/components/ui/slider';
+import {
+  DropdownMenu,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import Line from '@/components/icons/editor/element/line';
+import BottomTabDownIcon from '@/components/icons/editor/bottomtab-down';
+import Image from 'next/image';
+import LineDotted from '@/components/icons/editor/element/line-dotted';
+import LineDotted2 from '@/components/icons/editor/element/line-dotted-2';
+import { isSameArray } from '@/utils/common/same-array.util';
+
+interface ElementStrokeSelectorProps {
+  selectedElement: ElementsElement;
+  handleColorChange: (field: 'fill' | 'stroke', color: string) => void;
+  handleStrokeChange: (strokeWidth: number) => void;
+  handleDashedChange: (dashed: number[]) => void;
+}
+
+const ElementStrokeSelector = ({
+  selectedElement,
+  handleColorChange,
+  handleStrokeChange,
+  handleDashedChange,
+}: ElementStrokeSelectorProps) => {
+  const showSelectedDashed = () => {
+    if (!selectedElement.dash) return;
+
+    if (isSameArray(selectedElement.dash, BASE_LINE)) {
+      return <Line width={148} />;
+    } else if (isSameArray(selectedElement.dash, DASHED)) {
+      return <LineDotted width={148} />;
+    } else if (isSameArray(selectedElement.dash, DASHED2)) {
+      return <LineDotted2 width={148} />;
+    }
+  };
+
+  return (
+    <Accordion type='single' collapsible>
+      <AccordionItem value='stroke'>
+        <AccordionTrigger className='!text-caption-medium text-black'>
+          외곽선
+        </AccordionTrigger>
+
+        <AccordionContent className='space-y-3'>
+          {/* 외곽선 색상 */}
+          <div className='flex h-[26px] w-[204px] items-center justify-between px-[6px]'>
+            <span className='text-caption-regular text-black'>색상</span>
+            <div className='flex items-center gap-[10px] p-[3px]'>
+              <Popover>
+                <PopoverTrigger asChild>
+                  <div
+                    className='h-[20px] w-[20px] rounded border-[2px]'
+                    style={{
+                      backgroundColor: selectedElement?.fill || DEFAULT_COLOR,
+                    }}
+                  />
+                </PopoverTrigger>
+                <PopoverContent className='z-[10] w-auto p-2'>
+                  <ColorPicker
+                    selectedColor={selectedElement?.stroke || DEFAULT_COLOR}
+                    onColorChange={(e) => handleColorChange('stroke', e)}
+                    title='외곽선 색상'
+                  />
+                </PopoverContent>
+              </Popover>
+            </div>
+          </div>
path: src/components/editor/editor-ui/element-style-selector/element-stroke-selector.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**외곽선 색상 선택기**

Popover와 ColorPicker를 사용한 색상 선택 UI가 잘 구현되었습니다. 그러나 팝오버 트리거에 표시되는 색상(`backgroundColor: selectedElement?.fill || DEFAULT_COLOR`)과 실제 선택된 색상(`selectedColor={selectedElement?.stroke || DEFAULT_COLOR}`)이 다른 속성을 참조하고 있습니다. 이것이 의도적인지 확인이 필요합니다.

```diff
 <div
   className='h-[20px] w-[20px] rounded border-[2px]'
   style={{
-    backgroundColor: selectedElement?.fill || DEFAULT_COLOR,
+    backgroundColor: selectedElement?.stroke || DEFAULT_COLOR,
   }}
 />
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
{/* 외곽선 색상 */}
<div className='flex h-[26px] w-[204px] items-center justify-between px-[6px]'>
  <span className='text-caption-regular text-black'>색상</span>
  <div className='flex items-center gap-[10px] p-[3px]'>
    <Popover>
      <PopoverTrigger asChild>
        <div
          className='h-[20px] w-[20px] rounded border-[2px]'
          style={{
            backgroundColor: selectedElement?.stroke || DEFAULT_COLOR,
          }}
        />
      </PopoverTrigger>
      <PopoverContent className='z-[10] w-auto p-2'>
        <ColorPicker
          selectedColor={selectedElement?.stroke || DEFAULT_COLOR}
          onColorChange={(e) => handleColorChange('stroke', e)}
          title='외곽선 색상'
        />
      </PopoverContent>
    </Popover>
  </div>
</div>
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit f905589
created_at: 2025-04-29T11:26:41Z
author_association: NONE
diff_hunk: @@ -0,0 +1,188 @@
+import {
+  Accordion,
+  AccordionContent,
+  AccordionItem,
+  AccordionTrigger,
+} from '@/components/ui/accordion';
+import {
+  Popover,
+  PopoverContent,
+  PopoverTrigger,
+} from '@/components/ui/popover';
+import ColorPicker from '../color-picker';
+import { ElementsElement } from '@/types/editor.type';
+import {
+  BASE_LINE,
+  DASHED,
+  DASHED2,
+  DEFAULT_COLOR,
+} from '@/constants/editor.constant';
+import { Slider } from '@/components/ui/slider';
+import {
+  DropdownMenu,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import Line from '@/components/icons/editor/element/line';
+import BottomTabDownIcon from '@/components/icons/editor/bottomtab-down';
+import Image from 'next/image';
+import LineDotted from '@/components/icons/editor/element/line-dotted';
+import LineDotted2 from '@/components/icons/editor/element/line-dotted-2';
+import { isSameArray } from '@/utils/common/same-array.util';
+
+interface ElementStrokeSelectorProps {
+  selectedElement: ElementsElement;
+  handleColorChange: (field: 'fill' | 'stroke', color: string) => void;
+  handleStrokeChange: (strokeWidth: number) => void;
+  handleDashedChange: (dashed: number[]) => void;
+}
+
+const ElementStrokeSelector = ({
+  selectedElement,
+  handleColorChange,
+  handleStrokeChange,
+  handleDashedChange,
+}: ElementStrokeSelectorProps) => {
+  const showSelectedDashed = () => {
+    if (!selectedElement.dash) return;
+
+    if (isSameArray(selectedElement.dash, BASE_LINE)) {
+      return <Line width={148} />;
+    } else if (isSameArray(selectedElement.dash, DASHED)) {
+      return <LineDotted width={148} />;
+    } else if (isSameArray(selectedElement.dash, DASHED2)) {
+      return <LineDotted2 width={148} />;
+    }
+  };
+
+  return (
+    <Accordion type='single' collapsible>
+      <AccordionItem value='stroke'>
+        <AccordionTrigger className='!text-caption-medium text-black'>
+          외곽선
+        </AccordionTrigger>
+
+        <AccordionContent className='space-y-3'>
+          {/* 외곽선 색상 */}
+          <div className='flex h-[26px] w-[204px] items-center justify-between px-[6px]'>
+            <span className='text-caption-regular text-black'>색상</span>
+            <div className='flex items-center gap-[10px] p-[3px]'>
+              <Popover>
+                <PopoverTrigger asChild>
+                  <div
+                    className='h-[20px] w-[20px] rounded border-[2px]'
+                    style={{
+                      backgroundColor: selectedElement?.fill || DEFAULT_COLOR,
+                    }}
+                  />
+                </PopoverTrigger>
+                <PopoverContent className='z-[10] w-auto p-2'>
+                  <ColorPicker
+                    selectedColor={selectedElement?.stroke || DEFAULT_COLOR}
+                    onColorChange={(e) => handleColorChange('stroke', e)}
+                    title='외곽선 색상'
+                  />
+                </PopoverContent>
+              </Popover>
+            </div>
+          </div>
+
+          {/* 외곽선 두께 */}
+          <div className='flex flex-col items-center gap-2 self-stretch px-[6px]'>
+            <div className='flex w-full items-center justify-between self-stretch'>
+              <span className='text-caption-regular text-black'>두께</span>
+              <input
+                type='text'
+                min={2}
+                max={10}
+                value={Number(selectedElement.strokeWidth) || 0}
+                onChange={(e) => handleStrokeChange(Number(e.target.value))}
+                className='flex h-[26px] w-[45px] items-center rounded border border-gray-10 px-[6px] py-1'
+              />
+            </div>
+            <Slider
+              min={2}
+              max={10}
+              step={1}
+              onValueChange={(e) => handleStrokeChange(e[0])}
+            />
+          </div>
+
+          {/* 외곽선 종류 */}
+          <div className='flex flex-col items-center justify-center self-stretch rounded-[6px] border border-gray-10 p-[6px]'>
+            <DropdownMenu>
+              <DropdownMenuTrigger asChild>
+                <div className='flex items-center justify-between self-stretch'>
+                  {showSelectedDashed()}
+                  <BottomTabDownIcon />
+                </div>
+              </DropdownMenuTrigger>
+
+              <DropdownMenuContent>
+                <div>
+                  <DropdownMenuItem>
+                    <div
+                      className='flex h-[18px] items-center justify-between self-stretch'
+                      onClick={() => {
+                        handleDashedChange([0, 0]);
+                      }}
+                    >
+                      <Line width={148} />
+                      {isSameArray(selectedElement.dash, BASE_LINE) && (
+                        <Image
+                          src={'/icons/check.svg'}
+                          width={18}
+                          height={18}
+                          alt='체크 아이콘'
+                        />
+                      )}
+                    </div>
+                  </DropdownMenuItem>
+                  <DropdownMenuItem>
+                    <div
+                      className='flex h-[18px] items-center justify-between self-stretch'
+                      onClick={() => {
+                        handleDashedChange([20, 20]);
+                      }}
+                    >
+                      <LineDotted width={148} />
+                      {isSameArray(selectedElement.dash, DASHED) && (
+                        <Image
+                          src={'/icons/check.svg'}
+                          width={18}
+                          height={18}
+                          alt='체크 아이콘'
+                        />
+                      )}
+                    </div>
+                  </DropdownMenuItem>
+                  <DropdownMenuItem>
+                    <div
+                      className='flex h-[18px] items-center justify-between self-stretch'
+                      onClick={() => {
+                        handleDashedChange([9, 4]);
+                      }}
+                    >
+                      <LineDotted2 width={148} />
+                      {isSameArray(selectedElement.dash, DASHED2) && (
+                        <Image
+                          src={'/icons/check.svg'}
+                          width={18}
+                          height={18}
+                          alt='체크 아이콘'
+                        />
+                      )}
+                    </div>
+                  </DropdownMenuItem>
+                </div>
+              </DropdownMenuContent>
+            </DropdownMenu>
+          </div>
path: src/components/editor/editor-ui/element-style-selector/element-stroke-selector.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**외곽선 종류(대시 패턴) 선택 드롭다운**

드롭다운 메뉴를 사용하여 다양한 대시 패턴을 선택할 수 있게 한 것은 좋은 UI 디자인입니다. 현재 선택된 패턴에 체크 아이콘을 표시하는 것도 사용자 경험 측면에서 좋습니다.

그러나 하드코딩된 값(`[0, 0]`, `[20, 20]`, `[9, 4]`)을 사용하기보다는 상수(`BASE_LINE`, `DASHED`, `DASHED2`)를 사용하는 것이 더 일관성 있고 유지보수하기 쉬울 것입니다.

```diff
 <div
   className='flex h-[18px] items-center justify-between self-stretch'
   onClick={() => {
-    handleDashedChange([0, 0]);
+    handleDashedChange(BASE_LINE);
   }}
 >

 <div
   className='flex h-[18px] items-center justify-between self-stretch'
   onClick={() => {
-    handleDashedChange([20, 20]);
+    handleDashedChange(DASHED);
   }}
 >

 <div
   className='flex h-[18px] items-center justify-between self-stretch'
   onClick={() => {
-    handleDashedChange([9, 4]);
+    handleDashedChange(DASHED2);
   }}
 >
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
          {/* 외곽선 종류 */}
          <div className='flex flex-col items-center justify-center self-stretch rounded-[6px] border border-gray-10 p-[6px]'>
            <DropdownMenu>
              <DropdownMenuTrigger asChild>
                <div className='flex items-center justify-between self-stretch'>
                  {showSelectedDashed()}
                  <BottomTabDownIcon />
                </div>
              </DropdownMenuTrigger>

              <DropdownMenuContent>
                <div>
                  <DropdownMenuItem>
                    <div
                      className='flex h-[18px] items-center justify-between self-stretch'
                      onClick={() => {
                        handleDashedChange(BASE_LINE);
                      }}
                    >
                      <Line width={148} />
                      {isSameArray(selectedElement.dash, BASE_LINE) && (
                        <Image
                          src={'/icons/check.svg'}
                          width={18}
                          height={18}
                          alt='체크 아이콘'
                        />
                      )}
                    </div>
                  </DropdownMenuItem>
                  <DropdownMenuItem>
                    <div
                      className='flex h-[18px] items-center justify-between self-stretch'
                      onClick={() => {
                        handleDashedChange(DASHED);
                      }}
                    >
                      <LineDotted width={148} />
                      {isSameArray(selectedElement.dash, DASHED) && (
                        <Image
                          src={'/icons/check.svg'}
                          width={18}
                          height={18}
                          alt='체크 아이콘'
                        />
                      )}
                    </div>
                  </DropdownMenuItem>
                  <DropdownMenuItem>
                    <div
                      className='flex h-[18px] items-center justify-between self-stretch'
                      onClick={() => {
                        handleDashedChange(DASHED2);
                      }}
                    >
                      <LineDotted2 width={148} />
                      {isSameArray(selectedElement.dash, DASHED2) && (
                        <Image
                          src={'/icons/check.svg'}
                          width={18}
                          height={18}
                          alt='체크 아이콘'
                        />
                      )}
                    </div>
                  </DropdownMenuItem>
                </div>
              </DropdownMenuContent>
            </DropdownMenu>
          </div>
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit f905589
created_at: 2025-04-29T11:26:41Z
author_association: NONE
diff_hunk: @@ -0,0 +1,189 @@
+import {
+  Accordion,
+  AccordionContent,
+  AccordionItem,
+  AccordionTrigger,
+} from '@/components/ui/accordion';
+import {
+  Popover,
+  PopoverContent,
+  PopoverTrigger,
+} from '@/components/ui/popover';
+import ColorPicker from '../color-picker';
+import { ElementsElement } from '@/types/editor.type';
+import {
+  BASE_LINE,
+  DASHED,
+  DASHED2,
+  DEFAULT_COLOR,
+} from '@/constants/editor.constant';
+import { Slider } from '@/components/ui/slider';
+import {
+  DropdownMenu,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import Line from '@/components/icons/editor/element/line';
+import BottomTabDownIcon from '@/components/icons/editor/bottomtab-down';
+import Image from 'next/image';
+import LineDotted from '@/components/icons/editor/element/line-dotted';
+import LineDotted2 from '@/components/icons/editor/element/line-dotted-2';
+import { isSameArray } from '@/utils/common/same-array.util';
+
+interface ElementStrokeSelectorProps {
+  selectedElement: ElementsElement;
+  handleColorChange: (field: 'fill' | 'stroke', color: string) => void;
+  handleStrokeChange: (strokeWidth: number) => void;
+  handleDashedChange: (dashed: number[]) => void;
+}
+
+const ElementStrokeSelector = ({
+  selectedElement,
+  handleColorChange,
+  handleStrokeChange,
+  handleDashedChange,
+}: ElementStrokeSelectorProps) => {
+  const showSelectedDashed = () => {
+    if (!selectedElement.dash) return <Line width={148} />;
+
+    if (isSameArray(selectedElement.dash, BASE_LINE)) {
+      return <Line width={148} />;
+    } else if (isSameArray(selectedElement.dash, DASHED)) {
+      return <LineDotted width={148} />;
+    } else if (isSameArray(selectedElement.dash, DASHED2)) {
+      return <LineDotted2 width={148} />;
+    }
+    return <Line width={148} />;
+  };
+
+  return (
+    <Accordion type='single' collapsible>
+      <AccordionItem value='stroke'>
+        <AccordionTrigger className='!text-caption-medium text-black'>
+          외곽선
+        </AccordionTrigger>
+
+        <AccordionContent className='space-y-3'>
+          {/* 외곽선 색상 */}
+          <div className='flex h-[26px] items-center justify-between'>
+            <span className='text-caption-regular text-black'>색상</span>
+            <div className='flex items-center gap-[10px] p-[3px]'>
+              <Popover>
+                <PopoverTrigger asChild>
+                  <div
+                    className='h-[20px] w-[20px] rounded border-[2px]'
path: src/components/editor/editor-ui/element-style-selector/element-stroke-selector.tsx
user: login: lyra-j
body: P3: 테일윈드에서 제공하는 값(h-5, w-5)을 사용하면 통일성이 생길것 같아요~
created_at: 2025-04-29T18:23:08Z
author_association: OWNER
diff_hunk: @@ -9,6 +9,7 @@ import ImagesSidebar from '@/components/editor/elements/images/images-sidebar';
 import QrsocialSidebar from '@/components/editor/elements/qr-social/qrsocial-sidebar';
 import TemplatesSidebar from '@/components/editor/elements/templates/templates-sidebar';
 import ElementsSidebar from '@/components/editor/elements/element/element-sidebar';
+import ElementStyleSidebar from '../../elements/element/element-style-sidebar';
path: src/components/editor/editor-ui/sidebar/editor-sidebar-element.tsx
user: login: lyra-j
body: P1: 에디터는 정말 절대경로 찾기 힘드네요.., 그래도 변경 해주세요~!
created_at: 2025-04-29T18:24:59Z
author_association: OWNER
diff_hunk: @@ -21,24 +22,36 @@ const EditorSidebarElement = ({ category }: { category: string }) => {
 
   const finalCategory = convertType || category;
 
+  // 추후 프로젝트가 더 커지면 따로 분리(지금은 txt, ele 2개밖에 없어서 둠)
path: src/components/editor/editor-ui/sidebar/editor-sidebar-element.tsx
user: login: lyra-j
body: P2:TODO로 남겨주시면 나중에 찾아볼때 좋아요!!
created_at: 2025-04-29T18:25:44Z
author_association: OWNER
diff_hunk: @@ -70,9 +72,9 @@ const ElementsCanvasElement = forwardRef<
                 y={element.points[1]}
                 sides={3}
                 radius={5}
-                fill='black'
-                stroke='black'
-                strokeWidth={2}
+                fill={element.fill}
+                stroke={element.stroke}
+                strokeWidth={element.strokeWidth}
path: src/components/editor/elements/element/ElementsCanvasElement.tsx
user: login: lyra-j
body: P2: 반복해서 사용되는 부분들이 많군요, 유지보수를 위해 리팩토링 검토 TODO 남겨두면 좋을것 같아요~
created_at: 2025-04-29T18:27:46Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,104 @@
+import {
+  Popover,
+  PopoverContent,
+  PopoverTrigger,
+} from '@/components/ui/popover';
+import ColorPicker from '../../editor-ui/color-picker';
+import { useMemo } from 'react';
+import { ElementsElement } from '@/types/editor.type';
+import { useEditorStore } from '@/store/editor.store';
+import { DEFAULT_COLOR, ElEMENT_TYPE } from '@/constants/editor.constant';
+import ElementStrokeSelector from '../../editor-ui/element-style-selector/element-stroke-selector';
path: src/components/editor/elements/element/element-style-sidebar.tsx
user: login: lyra-j
body: P1: 임포트 파일들은 경로 한번씩만 확인 해주세요~
created_at: 2025-04-29T18:29:19Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,104 @@
+import {
+  Popover,
+  PopoverContent,
+  PopoverTrigger,
+} from '@/components/ui/popover';
+import ColorPicker from '../../editor-ui/color-picker';
+import { useMemo } from 'react';
+import { ElementsElement } from '@/types/editor.type';
+import { useEditorStore } from '@/store/editor.store';
+import { DEFAULT_COLOR, ElEMENT_TYPE } from '@/constants/editor.constant';
+import ElementStrokeSelector from '../../editor-ui/element-style-selector/element-stroke-selector';
+
+const ElementStyleSidebar = () => {
+  const isFront = useEditorStore((state) => state.isCanvasFront);
+  const canvasElements = useEditorStore((state) =>
+    isFront ? state.canvasElements : state.canvasBackElements
+  );
+  const selectedElementId = useEditorStore((state) => state.selectedElementId);
+  const updateElement = useEditorStore((state) => state.updateElement);
+
+  /**
+   * 현재 선택된 텍스트 요소 가져오기
+   */
+  const selectedElement = useMemo((): ElementsElement | undefined => {
+    return canvasElements.find(
+      (el) => el.id === selectedElementId && el.type === ElEMENT_TYPE.ELEMENT
+    ) as ElementsElement | undefined;
+  }, [canvasElements, selectedElementId]);
+
+  if (!selectedElementId || !selectedElement) return;
+
+  /**
+   * 색상 변경
+   * @param field 변경할 필드
+   * @param color 색상
+   * @returns
+   */
path: src/components/editor/elements/element/element-style-sidebar.tsx
user: login: lyra-j
body: 캬~ 이런 주석 처리 죠아요!
created_at: 2025-04-29T18:30:08Z
author_association: OWNER
diff_hunk: @@ -41,6 +41,7 @@ import Triangle from '@/components/icons/editor/element/triangle';
 import TriangleDotted from '@/components/icons/editor/element/triangle-dotted';
 import TriangleWhite from '@/components/icons/editor/element/triangle-white';
 import { LineEndType } from '@/types/editor.type';
+import { BASE_LINE, DASHED, DASHED2 } from './editor.constant';
path: src/constants/editor-elements.constant.ts
user: login: lyra-j
body: P1: 경로를 찾아주세요~
created_at: 2025-04-29T18:31:50Z
author_association: OWNER
diff_hunk: @@ -269,26 +364,30 @@ export const starOption: {
   numPoint: number;
   innerRadius: number;
   outerRadius: number;
+  dashed: number[];
 }[] = [
   {
     Component: Star4,
-    fill: 'rgba(0,0,0,0)',
+    fill: 'black',
     numPoint: 4,
     innerRadius: 20,
     outerRadius: 50,
+    dashed: BASE_LINE,
   },
   {
     Component: Star5,
-    fill: 'rgba(0,0,0,0)',
+    fill: 'black',
     numPoint: 5,
     innerRadius: 20,
     outerRadius: 50,
+    dashed: BASE_LINE,
   },
   {
     Component: Star6,
-    fill: 'rgba(0,0,0,0)',
+    fill: 'black',
     numPoint: 6,
     innerRadius: 30,
     outerRadius: 50,
+    dashed: BASE_LINE,
path: src/constants/editor-elements.constant.ts
user: login: lyra-j
body: ?? 콘스탄트가 거의 400줄이라구요!???
created_at: 2025-04-29T18:33:08Z
author_association: OWNER

PR comments:
user: login: vercel[bot]
created_at: 2025-04-29T11:21:55Z
body: [vc]: #LQuyViTfi4nM/DAIvuk6ZXkzBQGNJmy4LYt77Gxr3Dk=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJ1dW5vIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6InV1bm8tZ2l0LWZlYXQtMzMyLWVsZW1lbnQtb3B0aW9uLWx5cmEtanMtcHJvamVjdHMudmVyY2VsLmFwcCJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vbHlyYS1qcy1wcm9qZWN0cy91dW5vLzk0NmV1c0RUQzZkeDFrVmQ4NDk0b0o1UEhMWjUiLCJwcmV2aWV3VXJsIjoidXVuby1naXQtZmVhdC0zMzItZWxlbWVudC1vcHRpb24tbHlyYS1qcy1wcm9qZWN0cy52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwicm9vdERpcmVjdG9yeSI6bnVsbH1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **uuno** | ✅ Ready ([Inspect](https://vercel.com/lyra-js-projects/uuno/946eusDTC6dx1kVd8494oJ5PHLZ5)) | [Visit Preview](https://uuno-git-feat-332-element-option-lyra-js-projects.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/uuno-git-feat-332-element-option-lyra-js-projects.vercel.app?via=pr-comment-feedback-link) | Apr 30, 2025 2:10am |


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-04-29T11:21:58Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

"""
## Walkthrough

이번 변경에서는 도형(요소) 속성 탭과 관련된 UI 및 데이터 구조가 대폭 확장되었습니다. 새로운 도형 속성 사이드바와 외곽선(Stroke) 선택 컴포넌트가 추가되어, 사용자는 도형의 색상, 외곽선 색상, 두께, 점선 패턴 등을 직접 조절할 수 있게 되었습니다. 도형 옵션과 타입 정의가 확장되어 dash/dashed 속성이 배열로 통일되고, SVG 아이콘 컴포넌트들은 동적으로 크기를 조절할 수 있도록 개선되었습니다. 또한, 관련 유틸 함수와 상수도 추가되었습니다.

## Changes

| 파일/경로 요약                                                                                     | 변경 요약                                                                                                      |
|---------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|
| src/components/editor/editor-ui/element-style-selector/element-stroke-selector.tsx                | 외곽선(Stroke) 속성 선택 UI 컴포넌트(ElementStrokeSelector) 추가                                               |
| src/components/editor/elements/element/element-style-sidebar.tsx                                  | 도형 스타일 속성 사이드바(ElementStyleSidebar) 추가                                                           |
| src/components/editor/elements/element/element-sidebar.tsx                                        | 요소 추가 시 타입 지정, dashed → dot, fill 등 스타일 속성 일관 적용, 기본 strokeWidth 2로 변경                |
| src/components/editor/elements/element/ElementsCanvasElement.tsx                                  | 도형 렌더링 시 stroke, strokeWidth, fill, dash 속성 element prop 기반으로 통일                                 |
| src/components/editor/editor-ui/sidebar/editor-sidebar-element.tsx                                | 사이드바 렌더링 분기 switchSideBar 함수로 리팩토링, ElementStyleSidebar import                                 |
| src/components/icons/editor/element/line.tsx<br>src/components/icons/editor/element/line-dotted.tsx<br>src/components/icons/editor/element/line-dotted-2.tsx | SVG 선 아이콘 width prop 동적 처리, viewBox 및 path 동적화                                                    |
| src/constants/editor-elements.constant.ts                                                         | 도형/선 옵션에 fill, dashed 속성 추가, dashed 타입 number[]로 통일, 상수 참조로 일원화                        |
| src/constants/editor.constant.ts                                                                  | BASE_LINE, DASHED, DASHED2 등 선 패턴 상수 추가                                                               |
| src/types/editor.type.ts                                                                         | ElementsElement 인터페이스 dash 속성 [number, number]? → number[]로 변경 및 필수화                            |
| src/utils/common/same-array.util.ts                                                              | 배열 비교 유틸 함수 isSameArray 추가                                                                           |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant ElementStyleSidebar
    participant ElementStrokeSelector
    participant EditorStore

    User->>ElementStyleSidebar: 도형 선택
    ElementStyleSidebar->>EditorStore: 선택된 도형 정보 조회
    ElementStyleSidebar->>ElementStrokeSelector: 도형 속성 전달
    User->>ElementStrokeSelector: 색상/두께/점선 패턴 변경
    ElementStrokeSelector->>ElementStyleSidebar: 변경 이벤트 콜백 호출
    ElementStyleSidebar->>EditorStore: 도형 속성 업데이트
    EditorStore-->>ElementStyleSidebar: 변경된 도형 정보 반영
```

## Assessment against linked issues

| Objective (Issue No.)                     | Addressed | Explanation                               |
|-------------------------------------------|-----------|-------------------------------------------|
| 도형 속성 탭 만들기 (#332)                | ✅        |                                           |
| 에디터 요소(아이콘, 사이드바 등) (#32)    | ✅        |                                           |
| 도형 SVG 추가 및 SVG 옵션 확장 (#282)     | ✅        |                                           |

## Suggested labels

`♻️ Refactor`, `💄 Html&css`

## Suggested reviewers

- lyra-j
- sum529-create

## Poem

> 🐇  
> 속성 탭이 생겼어요, 도형이 빛나죠!  
> 외곽선, 색상, 점선도 내 맘대로 고르죠.  
> SVG도 반짝반짝, 옵션도 풍성해~  
> 토끼는 깡총깡총, 코드를 응원해!  
>  
> ── (토끼가 그린 새 도형 세상)  
>
"""

<!-- walkthrough_end -->
<!-- This is an auto-generated comment: all tool run failures by coderabbit.ai -->

> [!WARNING]
> There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.
> 
> <details>
> <summary>🔧 ESLint</summary>
> 
> > If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.
> 
> <details>
> <summary>src/constants/editor.constant.ts</summary>
> 
> 
> Oops! Something went wrong! :(
> 
> ESLint: 8.57.1
> 
> ESLint couldn't find the plugin "eslint-plugin-prettier".
> 
> (The package "eslint-plugin-prettier" was not found when loaded as a Node module from the directory "".)
> 
> It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:
> 
>     npm install eslint-plugin-prettier@latest --save-dev
> 
> The plugin "eslint-plugin-prettier" was referenced from the config file in ".eslintrc.json".
> 
> If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
> 
> 
> </details>
> 
> <details>
> <summary>src/components/editor/elements/element/ElementsCanvasElement.tsx</summary>
> 
> 
> Oops! Something went wrong! :(
> 
> ESLint: 8.57.1
> 
> ESLint couldn't find the plugin "eslint-plugin-prettier".
> 
> (The package "eslint-plugin-prettier" was not found when loaded as a Node module from the directory "".)
> 
> It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:
> 
>     npm install eslint-plugin-prettier@latest --save-dev
> 
> The plugin "eslint-plugin-prettier" was referenced from the config file in ".eslintrc.json".
> 
> If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
> 
> 
> </details>
> 
> <details>
> <summary>src/components/editor/editor-ui/sidebar/editor-sidebar-element.tsx</summary>
> 
> 
> Oops! Something went wrong! :(
> 
> ESLint: 8.57.1
> 
> ESLint couldn't find the plugin "eslint-plugin-prettier".
> 
> (The package "eslint-plugin-prettier" was not found when loaded as a Node module from the directory "".)
> 
> It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:
> 
>     npm install eslint-plugin-prettier@latest --save-dev
> 
> The plugin "eslint-plugin-prettier" was referenced from the config file in ".eslintrc.json".
> 
> If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
> 
> 
> </details>
> 
> + 3 others
> 
> </details>

<!-- end of auto-generated comment: all tool run failures by coderabbit.ai -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between f90558946ea5feba610d3fbf0280b26005d299ac and 99271c4e6bf587f814e5131a644d3bb9f50d1c67.

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `src/components/editor/editor-ui/element-style-selector/element-stroke-selector.tsx` (1 hunks)
* `src/components/editor/editor-ui/sidebar/editor-sidebar-element.tsx` (2 hunks)
* `src/components/editor/elements/element/ElementsCanvasElement.tsx` (12 hunks)
* `src/components/editor/elements/element/element-style-sidebar.tsx` (1 hunks)
* `src/constants/editor-elements.constant.ts` (6 hunks)
* `src/constants/editor.constant.ts` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* src/constants/editor.constant.ts

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (5)</summary>

* src/components/editor/editor-ui/sidebar/editor-sidebar-element.tsx
* src/components/editor/elements/element/ElementsCanvasElement.tsx
* src/components/editor/elements/element/element-style-sidebar.tsx
* src/components/editor/editor-ui/element-style-selector/element-stroke-selector.tsx
* src/constants/editor-elements.constant.ts

</details>

</details>
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=lyra-j/uuno&utm_content=365):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE