Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drag Mode 추가 #18

Merged
merged 9 commits into from
Jan 16, 2025
Merged

Drag Mode 추가 #18

merged 9 commits into from
Jan 16, 2025

Conversation

sumi-0011
Copy link
Owner

@sumi-0011 sumi-0011 commented Jan 16, 2025

  • tag들을 드래그해서 배치할 수 있는 drag mode를 추가하였습니다.
  • canvas 상단 toggle 버튼을 이용하여 모드 전환이 가능합니다.

Summary by CodeRabbit

  • 새로운 기능

    • 태그 목록의 드래그 앤 드롭 기능 추가
    • 썸네일 메이커에 드래그 모드 토글 스위치 도입
    • 이미지 다운로드 기능 추가
  • 의존성 업데이트

    • 드래그 앤 드롭 라이브러리 (@dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities) 추가
    • Radix UI 스위치 라이브러리 추가
  • 개선 사항

    • 태그 항목의 유연성 향상
    • 태그 순서 변경 기능 구현

@sumi-0011 sumi-0011 self-assigned this Jan 16, 2025
Copy link

vercel bot commented Jan 16, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
thumbnail-maker ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 16, 2025 3:11am

Copy link

coderabbitai bot commented Jan 16, 2025

"""

워크스루

이 풀 리퀘스트는 썸네일 메이커 컴포넌트에 드래그 앤 드롭 기능을 추가하는 주요 변경 사항을 포함하고 있습니다. 새로운 라이브러리 @dnd-kit 시리즈를 도입하여 태그 재정렬 기능을 구현했으며, 사용자 인터페이스에 드래그 모드 토글 스위치를 추가했습니다. 패키지 의존성과 컴포넌트 구조를 업데이트하여 더욱 유연하고 상호작용적인 태그 관리 시스템을 개발했습니다.

변경 사항

파일 변경 요약
package.json @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities, @radix-ui/react-switch 라이브러리 추가
src/components/thumbnail-maker/DragMode/DragModeCanvas.tsx 새 드래그 모드 캔버스 컴포넌트 생성
src/components/thumbnail-maker/DragMode/SortableTagItem.tsx 정렬 가능한 태그 아이템 컴포넌트 추가
src/components/thumbnail-maker/SubMenu/SubActionMenu.tsx 이미지 다운로드 기능 추가 및 불필요한 아이콘 제거
src/components/thumbnail-maker/Tag.context.tsx 태그 순서 업데이트 메서드 추가
src/components/thumbnail-maker/TagItem.tsx 선택적 콜백 함수로 변경된 태그 아이템 프로퍼티
src/components/thumbnail-maker/hooks/useThumbnailTagList.ts 태그 순서 업데이트 함수 추가
src/components/thumbnail-maker/index.tsx 드래그 모드 상태 및 토글 스위치 도입
src/components/ui/switch.tsx 새로운 스위치 UI 컴포넌트 생성

시퀀스 다이어그램

sequenceDiagram
    participant User
    participant UI
    participant DragModeCanvas
    participant TagContext

    User->>UI: 드래그 모드 스위치 토글
    UI->>DragModeCanvas: 드래그 모드 활성화
    User->>DragModeCanvas: 태그 드래그
    DragModeCanvas->>TagContext: 태그 순서 업데이트
    TagContext-->>DragModeCanvas: 새 태그 순서 반환
    DragModeCanvas-->>UI: 업데이트된 태그 렌더링
Loading

🐰 드래그의 춤, 태그의 노래
토글 스위치 반짝이며
자유롭게 춤추는 태그들
코드의 마법, 재미로다! 🎉
"""


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • 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 for this PR. (Beta)
  • @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 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 for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

# Conflicts:
#	package.json
#	src/components/thumbnail-maker/SubMenu/SubActionMenu.tsx
#	src/components/thumbnail-maker/index.tsx
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🔭 Outside diff range comments (1)
src/components/thumbnail-maker/SubMenu/SubActionMenu.tsx (1)

Line range hint 19-73: 비동기 작업에 대한 로딩 상태 처리가 필요합니다.

getImageFile이 비동기 함수임에도 불구하고 로딩 상태 처리가 되어있지 않습니다. 사용자 경험 향상을 위해 로딩 상태를 추가하는 것이 좋습니다.

export function SubActionMenu({
  getImageFile,
}: {
  getImageFile: () => Promise<Blob | null>;
}) {
+ const [isLoading, setIsLoading] = useState(false);
  const [isSaveTemplateSheetOpen, setIsSaveTemplateSheetOpen] = useState(false);
  // ... other state

+ const handleSaveTemplate = async () => {
+   setIsLoading(true);
+   try {
+     await getImageFile();
+     setIsSaveTemplateSheetOpen(true);
+   } finally {
+     setIsLoading(false);
+   }
+ };

  return (
    // ... existing JSX
-           <MenubarItem onClick={() => setIsSaveTemplateSheetOpen(true)}>
+           <MenubarItem 
+             onClick={handleSaveTemplate}
+             disabled={isLoading}
+           >
-             Save Template
+             {isLoading ? "저장 중..." : "템플릿 저장"}
            </MenubarItem>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c51a939 and e79ce14.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (9)
  • package.json (1 hunks)
  • src/components/thumbnail-maker/DragMode/DragModeCanvas.tsx (1 hunks)
  • src/components/thumbnail-maker/DragMode/SortableTagItem.tsx (1 hunks)
  • src/components/thumbnail-maker/SubMenu/SubActionMenu.tsx (1 hunks)
  • src/components/thumbnail-maker/Tag.context.tsx (4 hunks)
  • src/components/thumbnail-maker/TagItem.tsx (2 hunks)
  • src/components/thumbnail-maker/hooks/useThumbnailTagList.ts (2 hunks)
  • src/components/thumbnail-maker/index.tsx (2 hunks)
  • src/components/ui/switch.tsx (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/components/thumbnail-maker/TagItem.tsx

[error] 26-32: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)

🔇 Additional comments (3)
src/components/thumbnail-maker/Tag.context.tsx (1)

15-15: 컨텍스트 구현이 잘 되었습니다!

태그 순서 업데이트 기능이 컨텍스트에 잘 통합되었고, 기본값 처리도 적절히 되어 있습니다.

Also applies to: 27-27, 57-57, 79-79

package.json (2)

18-20: 드래그 앤 드롭 기능을 위한 의존성이 적절히 추가되었습니다.

@dnd-kit 라이브러리들이 태그의 드래그 기능 구현을 위해 추가되었습니다. 이는 PR의 목적에 부합합니다.


28-28: @radix-ui/react-slot 버전 다운그레이드 검증이 필요합니다.

버전이 1.1.1에서 1.0.2로 다운그레이드되었습니다. 이 변경이 의도적인지, 그리고 다른 Radix UI 컴포넌트들과의 호환성 문제는 없는지 확인이 필요합니다.

다음 스크립트를 실행하여 의존성 호환성을 확인해주세요:

✅ Verification successful

@radix-ui/react-slot 버전 다운그레이드는 안전합니다

다음과 같은 이유로 1.0.2 버전으로의 다운그레이드는 안전하다고 판단됩니다:

  • Button 컴포넌트에서만 기본적인 Slot 기능을 사용하고 있습니다
  • 해당 버전에서도 현재 구현된 기능이 모두 지원됩니다
  • 다른 Radix UI 컴포넌트들은 자체적으로 번들링된 react-slot을 사용하므로 영향이 없습니다
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Radix UI 컴포넌트들 간의 peer dependency 호환성 검증

# package-lock.json이나 yarn.lock에서 @radix-ui 관련 의존성 검색
if [ -f "package-lock.json" ]; then
  echo "Checking package-lock.json for @radix-ui dependencies..."
  cat package-lock.json | jq '.packages | to_entries[] | select(.key | contains("@radix-ui")) | {package: .key, version: .value.version, peerDependencies: .value.peerDependencies}'
elif [ -f "yarn.lock" ]; then
  echo "Checking yarn.lock for @radix-ui dependencies..."
  cat yarn.lock | grep -A 5 "@radix-ui"
fi

# npm info로 호환성 정보 확인
echo "Checking npm registry for @radix-ui/[email protected] peer dependencies..."
npm info "@radix-ui/[email protected]" peerDependencies

Length of output: 23528


Script:

#!/bin/bash
# Check if any Radix components explicitly require newer react-slot versions
echo "Checking package.json for all @radix-ui dependencies..."
cat package.json | jq '.dependencies | with_entries(select(.key | startswith("@radix-ui")))'

echo -e "\nChecking for potential breaking changes in components using react-slot..."
rg -A 5 "from '@radix-ui/react-slot'" 

Length of output: 886


Script:

#!/bin/bash
# Search for react-slot usage in TypeScript/JavaScript files
echo "Searching for react-slot usage in TS/JS files..."
fd -e ts -e tsx -e js -e jsx | xargs rg -l "@radix-ui/react-slot"

echo -e "\nChecking for Slot component usage..."
fd -e ts -e tsx -e js -e jsx | xargs rg -l "Slot[^a-zA-Z]"

Length of output: 380


Script:

#!/bin/bash
echo "Checking button.tsx implementation..."
cat src/components/ui/button.tsx

Length of output: 2031

Comment on lines +24 to +42
return (
<div
ref={setNodeRef}
{...attributes}
{...listeners}
style={{
width: "auto",
height: "auto",
transform: transform
? `translate3d(${transform.x}px, ${transform.y}px, 0)`
: undefined,
transition,
zIndex: isDragging ? 1 : undefined,
}}
className={cn("touch-none", isDragging && "opacity-50")}
>
{children}
</div>
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

접근성 개선이 필요합니다.

드래그 가능한 요소에 대한 접근성 속성이 누락되었습니다.

    <div
      ref={setNodeRef}
      {...attributes}
      {...listeners}
+     role="button"
+     aria-label="드래그 가능한 태그 항목"
+     aria-grabbed={isDragging}
+     tabIndex={0}
      style={{
📝 Committable suggestion

‼️ 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.

Suggested change
return (
<div
ref={setNodeRef}
{...attributes}
{...listeners}
style={{
width: "auto",
height: "auto",
transform: transform
? `translate3d(${transform.x}px, ${transform.y}px, 0)`
: undefined,
transition,
zIndex: isDragging ? 1 : undefined,
}}
className={cn("touch-none", isDragging && "opacity-50")}
>
{children}
</div>
);
return (
<div
ref={setNodeRef}
{...attributes}
{...listeners}
role="button"
aria-label="드래그 가능한 태그 항목"
aria-grabbed={isDragging}
tabIndex={0}
style={{
width: "auto",
height: "auto",
transform: transform
? `translate3d(${transform.x}px, ${transform.y}px, 0)`
: undefined,
transition,
zIndex: isDragging ? 1 : undefined,
}}
className={cn("touch-none", isDragging && "opacity-50")}
>
{children}
</div>
);

Comment on lines +5 to +9
interface SortableTagItemProps {
tag: {
id: string | number;
};
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

타입 정의를 더 구체적으로 개선하세요.

태그 인터페이스가 id 속성만 정의되어 있습니다. 태그와 관련된 다른 필수 속성들도 포함하는 것이 좋습니다.

interface SortableTagItemProps {
  tag: {
    id: string | number;
+   label: string;
+   position?: { x: number; y: number };
  };
}
📝 Committable suggestion

‼️ 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.

Suggested change
interface SortableTagItemProps {
tag: {
id: string | number;
};
}
interface SortableTagItemProps {
tag: {
id: string | number;
label: string;
position?: { x: number; y: number };
};
}

Comment on lines +5 to +23
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
)}
/>
</SwitchPrimitives.Root>
));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

접근성 레이블 속성을 추가하세요.

스위치 컴포넌트에 기본 aria-label이 누락되었습니다. 사용자가 레이블을 제공하지 않았을 때 사용할 기본값이 필요합니다.

const Switch = React.forwardRef<
  React.ElementRef<typeof SwitchPrimitives.Root>,
  React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
->(({ className, ...props }, ref) => (
+>(({ className, "aria-label": ariaLabel = "토글 스위치", ...props }, ref) => (
  <SwitchPrimitives.Root
    className={cn(
      "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
      className
    )}
+   aria-label={ariaLabel}
    {...props}
    ref={ref}
  >

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +40 to +42
const onUpdateTagOrder = (newTags: Tag[]) => {
setTags(newTags);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

이전 상태 백업이 누락되었습니다.

다른 상태 업데이트 함수들과 달리 onUpdateTagOrder 함수에서는 prevTags.current 업데이트가 누락되었습니다. 실행 취소 기능의 일관성을 위해 이전 상태를 저장해야 합니다.

  const onUpdateTagOrder = (newTags: Tag[]) => {
+   prevTags.current = tags;
    setTags(newTags);
  };
📝 Committable suggestion

‼️ 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.

Suggested change
const onUpdateTagOrder = (newTags: Tag[]) => {
setTags(newTags);
};
const onUpdateTagOrder = (newTags: Tag[]) => {
prevTags.current = tags;
setTags(newTags);
};

Comment on lines +82 to +86
{tags.map((tag) => (
<SortableTagItem key={tag.id} tag={tag}>
<TagItem tag={tag} />
</SortableTagItem>
))}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

태그 렌더링 최적화 제안

태그 목록이 큰 경우 성능에 영향을 줄 수 있습니다. React.memo를 사용하여 TagItem 컴포넌트를 최적화하고, 필요한 경우 가상화를 고려하세요.

Comment on lines +34 to +39
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

센서 설정의 메모이제이션 권장

useSensors 호출과 센서 설정이 매 렌더링마다 재생성됩니다. 성능 최적화를 위해 useMemo를 사용하는 것이 좋습니다.

- const sensors = useSensors(
-   useSensor(PointerSensor),
-   useSensor(KeyboardSensor, {
-     coordinateGetter: sortableKeyboardCoordinates,
-   })
- );
+ const sensors = useMemo(
+   () =>
+     useSensors(
+       useSensor(PointerSensor),
+       useSensor(KeyboardSensor, {
+         coordinateGetter: sortableKeyboardCoordinates,
+       })
+     ),
+   []
+ );
📝 Committable suggestion

‼️ 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.

Suggested change
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);
const sensors = useMemo(
() =>
useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
),
[]
);

Comment on lines +41 to +54
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;

if (over && active.id !== over.id) {
const oldIndex = tags.findIndex((tag) => tag.id === active.id);
const newIndex = tags.findIndex((tag) => tag.id === over.id);

if (oldIndex !== -1 && newIndex !== -1) {
const newTags = arrayMove(tags, oldIndex, newIndex);

onUpdateTagOrder(newTags);
}
}
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

handleDragEnd 함수의 메모이제이션 필요

handleDragEnd 함수가 매 렌더링마다 재생성되어 불필요한 리렌더링을 유발할 수 있습니다. useCallback을 사용하여 최적화하는 것이 좋습니다.

- const handleDragEnd = (event: DragEndEvent) => {
+ const handleDragEnd = useCallback((event: DragEndEvent) => {
    const { active, over } = event;

    if (over && active.id !== over.id) {
      const oldIndex = tags.findIndex((tag) => tag.id === active.id);
      const newIndex = tags.findIndex((tag) => tag.id === over.id);

      if (oldIndex !== -1 && newIndex !== -1) {
        const newTags = arrayMove(tags, oldIndex, newIndex);

        onUpdateTagOrder(newTags);
      }
    }
- };
+ }, [tags, onUpdateTagOrder]);
📝 Committable suggestion

‼️ 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.

Suggested change
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
if (over && active.id !== over.id) {
const oldIndex = tags.findIndex((tag) => tag.id === active.id);
const newIndex = tags.findIndex((tag) => tag.id === over.id);
if (oldIndex !== -1 && newIndex !== -1) {
const newTags = arrayMove(tags, oldIndex, newIndex);
onUpdateTagOrder(newTags);
}
}
};
const handleDragEnd = useCallback((event: DragEndEvent) => {
const { active, over } = event;
if (over && active.id !== over.id) {
const oldIndex = tags.findIndex((tag) => tag.id === active.id);
const newIndex = tags.findIndex((tag) => tag.id === over.id);
if (oldIndex !== -1 && newIndex !== -1) {
const newTags = arrayMove(tags, oldIndex, newIndex);
onUpdateTagOrder(newTags);
}
}
}, [tags, onUpdateTagOrder]);

Comment on lines +25 to +35
{onRemove && (
<div
className="absolute -right-1 -top-1 hidden h-6 w-6 cursor-default justify-center rounded-full bg-white/80 p-0.5 align-middle group-hover:flex"
onClick={(e) => {
e.stopPropagation();
onRemove();
}}
>
<X size={20} color="#454545" />
</div>
)}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

접근성 개선이 필요합니다

삭제 버튼에 키보드 이벤트가 없어 키보드 사용자가 접근할 수 없습니다. 다음과 같이 개선이 필요합니다:

  {onRemove && (
    <div
+     role="button"
+     tabIndex={0}
      className="absolute -right-1 -top-1  hidden h-6 w-6 cursor-default justify-center rounded-full bg-white/80 p-0.5 align-middle group-hover:flex"
      onClick={(e) => {
        e.stopPropagation();
        onRemove();
      }}
+     onKeyDown={(e) => {
+       if (e.key === 'Enter' || e.key === ' ') {
+         e.stopPropagation();
+         onRemove();
+       }
+     }}
    >
      <X size={20} color="#454545" />
    </div>
  )}
📝 Committable suggestion

‼️ 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.

Suggested change
{onRemove && (
<div
className="absolute -right-1 -top-1 hidden h-6 w-6 cursor-default justify-center rounded-full bg-white/80 p-0.5 align-middle group-hover:flex"
onClick={(e) => {
e.stopPropagation();
onRemove();
}}
>
<X size={20} color="#454545" />
</div>
)}
{onRemove && (
<div
role="button"
tabIndex={0}
className="absolute -right-1 -top-1 hidden h-6 w-6 cursor-default justify-center rounded-full bg-white/80 p-0.5 align-middle group-hover:flex"
onClick={(e) => {
e.stopPropagation();
onRemove();
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.stopPropagation();
onRemove();
}
}}
>
<X size={20} color="#454545" />
</div>
)}
🧰 Tools
🪛 Biome (1.9.4)

[error] 26-32: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)

Comment on lines +69 to +72
<div className="flex items-center justify-end gap-2">
<Switch checked={isDragMode} onCheckedChange={setIsDragMode} />
<p>{isDragMode ? "Drag Mode" : "Default Mode"}</p>
</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

사용자 경험 개선 제안

드래그 모드 토글의 UX를 개선하기 위한 제안:

  1. 토글에 설명 툴팁 추가
  2. 모드 변경 시 사용자에게 피드백 제공
  <div className="flex items-center justify-end gap-2">
+   <div className="relative">
      <Switch checked={isDragMode} onCheckedChange={setIsDragMode} />
+     <div className="absolute -bottom-8 right-0 hidden whitespace-nowrap rounded bg-gray-800 px-2 py-1 text-sm group-hover:block">
+       태그를 드래그하여 순서를 변경할 수 있습니다
+     </div>
+   </div>
    <p>{isDragMode ? "Drag Mode" : "Default Mode"}</p>
  </div>

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +73 to +85
{isDragMode ? (
<DragModeCanvas
previewRef={previewRef}
tagsContainerRef={tagsContainerRef}
/>
) : (
<CanvasContainer
previewRef={previewRef}
tagsContainerRef={tagsContainerRef}
>
<TagList setOpenTagSheet={onSelectTag} />
</CanvasContainer>
)}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

조건부 렌더링 성능 최적화

DragModeCanvasCanvasContainer 컴포넌트를 React.lazy를 사용하여 지연 로딩하면 초기 로딩 성능을 개선할 수 있습니다.

+ const DragModeCanvas = React.lazy(() => import('./DragMode/DragModeCanvas'));
+ const CanvasContainer = React.lazy(() => import('./CanvasContainer'));

  {isDragMode ? (
+   <Suspense fallback={<div>로딩 중...</div>}>
      <DragModeCanvas
        previewRef={previewRef}
        tagsContainerRef={tagsContainerRef}
      />
+   </Suspense>
  ) : (
+   <Suspense fallback={<div>로딩 중...</div>}>
      <CanvasContainer
        previewRef={previewRef}
        tagsContainerRef={tagsContainerRef}
      >
        <TagList setOpenTagSheet={onSelectTag} />
      </CanvasContainer>
+   </Suspense>
  )}

Committable suggestion skipped: line range outside the PR's diff.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e79ce14 and c68bdb1.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (3)
  • package.json (2 hunks)
  • src/components/thumbnail-maker/SubMenu/SubActionMenu.tsx (2 hunks)
  • src/components/thumbnail-maker/index.tsx (3 hunks)
🔇 Additional comments (3)
package.json (2)

30-30: Switch 컴포넌트 의존성 추가 확인

드래그 모드 토글을 위한 Switch 컴포넌트가 적절히 추가되었습니다. 다만, 다른 Radix UI 컴포넌트들과 마찬가지로 버전 관리에 주의가 필요합니다.

다음 스크립트로 Radix UI 컴포넌트들 간의 버전 호환성을 확인해보세요:


18-20: @dnd-kit 의존성 버전 관리에 대한 제안

드래그 앤 드롭 기능을 위한 @dnd-kit 라이브러리들의 버전이 캐럿(^)을 사용하고 있습니다. 이는 마이너 버전 업데이트로 인한 호환성 문제를 야기할 수 있습니다.

다음 스크립트를 실행하여 현재 버전들의 호환성을 확인해보세요:

✅ Verification successful

@dnd-kit 패키지 버전이 안정적입니다

현재 지정된 버전들이 최신 안정 버전과 일치하며 서로 호환됩니다. 하지만 더 안정적인 배포를 위해 다음과 같이 캐럿(^) 대신 정확한 버전을 사용하는 것을 권장드립니다:

"@dnd-kit/core": "6.3.1",
"@dnd-kit/sortable": "10.0.0",
"@dnd-kit/utilities": "3.2.2",
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# @dnd-kit 패키지들의 호환성 확인
curl -s https://registry.npmjs.org/@dnd-kit/core | jq '.versions | keys[]' | grep 6.3
curl -s https://registry.npmjs.org/@dnd-kit/sortable | jq '.versions | keys[]' | grep 10.0
curl -s https://registry.npmjs.org/@dnd-kit/utilities | jq '.versions | keys[]' | grep 3.2

Length of output: 1240

src/components/thumbnail-maker/index.tsx (1)

96-99: Props 네이밍 일관성 유지

downloadImage prop의 네이밍이 일관성 있게 변경되었습니다. 👍

Comment on lines +14 to +15
import { Switch } from "../ui/switch";
import { useState } from "react";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

상태 관리 최적화 제안

isDragMode 상태는 앱의 전역 상태로 관리하는 것이 좋을 것 같습니다. 여러 컴포넌트에서 드래그 모드 상태를 참조해야 할 경우를 대비하여 Context나 상태 관리 라이브러리로 이동을 고려해보세요.

- const [isDragMode, setIsDragMode] = useState(false);
+ const { isDragMode, setIsDragMode } = useDragMode();

Also applies to: 20-20

Comment on lines +73 to +85
{isDragMode ? (
<DragModeCanvas
previewRef={previewRef}
tagsContainerRef={tagsContainerRef}
/>
) : (
<CanvasContainer
previewRef={previewRef}
tagsContainerRef={tagsContainerRef}
>
<TagList setOpenTagSheet={onSelectTag} />
</CanvasContainer>
)}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

조건부 렌더링 성능 최적화

DragModeCanvasCanvasContainer 컴포넌트의 불필요한 리렌더링을 방지하기 위해 메모이제이션을 적용하는 것이 좋습니다.

+ const MemoizedDragModeCanvas = useMemo(
+   () => (
+     <DragModeCanvas
+       previewRef={previewRef}
+       tagsContainerRef={tagsContainerRef}
+     />
+   ),
+   [previewRef, tagsContainerRef]
+ );

+ const MemoizedCanvasContainer = useMemo(
+   () => (
+     <CanvasContainer
+       previewRef={previewRef}
+       tagsContainerRef={tagsContainerRef}
+     >
+       <TagList setOpenTagSheet={onSelectTag} />
+     </CanvasContainer>
+   ),
+   [previewRef, tagsContainerRef, onSelectTag]
+ );

- {isDragMode ? (
-   <DragModeCanvas
-     previewRef={previewRef}
-     tagsContainerRef={tagsContainerRef}
-   />
- ) : (
-   <CanvasContainer
-     previewRef={previewRef}
-     tagsContainerRef={tagsContainerRef}
-   >
-     <TagList setOpenTagSheet={onSelectTag} />
-   </CanvasContainer>
- )}
+ {isDragMode ? MemoizedDragModeCanvas : MemoizedCanvasContainer}

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +91 to +98
<Link to="/gallery">
<Button variant="secondary">
<LayoutTemplateIcon size={20} />
</Button>
</Link>
<Button onClick={onDownload}>
<ImageIcon size={20} className="mr-2" /> Download Image
</Button>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

접근성 개선 필요

버튼에 적절한 aria-label이 없어 스크린 리더 사용자의 접근성이 떨어집니다.

  <Link to="/gallery">
-   <Button variant="secondary">
+   <Button variant="secondary" aria-label="갤러리로 이동">
      <LayoutTemplateIcon size={20} />
    </Button>
  </Link>
- <Button onClick={onDownload}>
+ <Button onClick={onDownload} aria-label="이미지 다운로드">
    <ImageIcon size={20} className="mr-2" /> Download Image
  </Button>
📝 Committable suggestion

‼️ 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.

Suggested change
<Link to="/gallery">
<Button variant="secondary">
<LayoutTemplateIcon size={20} />
</Button>
</Link>
<Button onClick={onDownload}>
<ImageIcon size={20} className="mr-2" /> Download Image
</Button>
<Link to="/gallery">
<Button variant="secondary" aria-label="갤러리로 이동">
<LayoutTemplateIcon size={20} />
</Button>
</Link>
<Button onClick={onDownload} aria-label="이미지 다운로드">
<ImageIcon size={20} className="mr-2" /> Download Image
</Button>

Comment on lines +35 to +48
const onDownload = () => {
downloadImage();
toast.message("Downloaded Successfully", {
duration: 5000,
description:
"Share your carefully crafted image with others through the gallery! 🎨",
action: {
label: "Upload",
onClick: () => {
setIsSaveTemplateSheetOpen(true);
},
},
});
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

다운로드 에러 처리 추가 필요

다운로드 실패 시 사용자에게 적절한 피드백이 없습니다. 에러 처리를 추가하는 것이 좋겠습니다.

  const onDownload = () => {
+   try {
      downloadImage();
      toast.message("Downloaded Successfully", {
        duration: 5000,
        description:
          "Share your carefully crafted image with others through the gallery! 🎨",
        action: {
          label: "Upload",
          onClick: () => {
            setIsSaveTemplateSheetOpen(true);
          },
        },
      });
+   } catch (error) {
+     toast.error("다운로드 중 오류가 발생했습니다", {
+       description: "잠시 후 다시 시도해주세요",
+     });
+   }
  };
📝 Committable suggestion

‼️ 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.

Suggested change
const onDownload = () => {
downloadImage();
toast.message("Downloaded Successfully", {
duration: 5000,
description:
"Share your carefully crafted image with others through the gallery! 🎨",
action: {
label: "Upload",
onClick: () => {
setIsSaveTemplateSheetOpen(true);
},
},
});
};
const onDownload = () => {
try {
downloadImage();
toast.message("Downloaded Successfully", {
duration: 5000,
description:
"Share your carefully crafted image with others through the gallery! 🎨",
action: {
label: "Upload",
onClick: () => {
setIsSaveTemplateSheetOpen(true);
},
},
});
} catch (error) {
toast.error("다운로드 중 오류가 발생했습니다", {
description: "잠시 후 다시 시도해주세요",
});
}
};

@sumi-0011 sumi-0011 merged commit 1450f1c into main Jan 16, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant