Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
03d1357
chore: 패키지 dnd-kit/core 추가
Grit03 Jan 7, 2026
b24e4ce
fix: CursorWithName 컴포넌트의 대문자 수정
Grit03 Jan 7, 2026
c16f5a2
feat: ReactPortal 컴포넌트 추가
Grit03 Jan 7, 2026
6f10d85
fix: ReactPortalProps의 parent 속성을 선택적으로 변경
Grit03 Jan 7, 2026
298b814
fix: react portal 컴포넌트 기본 부모 설정 변경
Grit03 Jan 7, 2026
f74c97b
fix: ModalHeader 컴포넌트 아이콘 및 인터페이스, prop 수정
Grit03 Jan 8, 2026
8dc6770
feat: TechStackWidget에 TechStackModal 연동 및 드래그 기능 추가
Grit03 Jan 8, 2026
91e7971
style: SearchBar 컴포넌트의 마진 수정
Grit03 Jan 8, 2026
de6814c
feat: 컴포넌트 TechStackItem 및 DraggableTechStackItem 추가
Grit03 Jan 8, 2026
c20dc3b
feat: 모달과 TechStackWidget 컴포넌트와 연동
Grit03 Jan 8, 2026
d0aae3c
fix: TechStackWidget 경로 수정 및 파일 삭제
Grit03 Jan 8, 2026
0e618a7
style: dark 모드 클래스 추가 및 수정
Grit03 Jan 8, 2026
864b88f
refactor: techWidget 관련 컴포넌트의 Props 인터페이스 추가 후 적용
Grit03 Jan 8, 2026
9721269
refactor: CursorProps를 CursorWithNameProps로 이름 변경
Grit03 Jan 8, 2026
763cb77
refactor: 대소문자 오류로 인한 CursorWithName 컴포넌트 파일명 변경
Grit03 Jan 8, 2026
3b87079
refactor: TechStack에 id 추가 및 타입 분리
Grit03 Jan 8, 2026
7747e35
feat: id를 기반으로 기술 스택 이름을 추출하는 로직 구현
Grit03 Jan 8, 2026
d852b6c
feat: DraggableTechStackItem 및 TechStackList 수정
Grit03 Jan 8, 2026
69374fb
feat: SelectedTechStackBox 및 TechStackWidget 수정
Grit03 Jan 8, 2026
6c10635
feat: 캔버스 기능 merge
Grit03 Jan 8, 2026
908a984
refactor: 상대 경로 모두 절대 경로 방식으로 변경
Grit03 Jan 8, 2026
be20375
fix: 드래그 overlay 좌표 버그 수정
Grit03 Jan 8, 2026
6db23f2
fix: 모달에서 휠 이벤트 전파 방지 추가
Grit03 Jan 8, 2026
b43749c
Merge branch 'main' into feat/5-techstack-dnd
davidpro08 Jan 8, 2026
046b887
Merge branch 'main' into feat/5-techstack-dnd
davidpro08 Jan 8, 2026
a47ef7b
fix: 누락된 useCallback import 경로 추가
Grit03 Jan 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<title>frontend</title>
</head>
<body>
<div id="root"></div>
<div id="root" class="dark"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"preview": "vite preview"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.4",
"@tailwindcss/vite": "^4.1.17",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { LuMousePointer2 } from 'react-icons/lu';

interface CursorProps {
interface CursorWithNameProps {
nickname: string;
color: string;
backgroundColor: string;
Expand All @@ -14,7 +14,7 @@ function CursorWithName({
backgroundColor,
x,
y,
}: CursorProps) {
}: CursorWithNameProps) {
const renderColor = color ? color : '#000000';

return (
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/common/components/ReactPortal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { PropsWithChildren } from 'react';
import { createPortal } from 'react-dom';

type ReactPortalProps = {
parent?: HTMLElement;
} & PropsWithChildren;

const defaultRoot = document.getElementById('root') as HTMLElement;

export default function ReactPortal({
parent = defaultRoot,
children,
}: ReactPortalProps) {
return parent && createPortal(children, parent);
}
4 changes: 2 additions & 2 deletions frontend/src/features/canvas/CanvasContent.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import CursorWithName from '@/common/components/CursorWithName';
import type { Cursor } from '@/common/types/cursor';
import TechStackWidget from '@/features/widgets/techStack/components/TechStackWidget';
import TechStackWidget from '@/features/widgets/techStack/components/techStackWidget/TechStackWidget';
import { useState } from 'react';
import type { Camera } from '@/common/types/camera';
import CursorWithName from '@/common/components/cursorWithName';

interface CanvasContainerProps {
camera: Camera;
Expand All @@ -23,7 +23,7 @@
isPanning,
remoteCursor,
}: CanvasContainerProps) {
const [techStackPosition, setTechStackPosition] = useState({

Check warning on line 26 in frontend/src/features/canvas/CanvasContent.tsx

View workflow job for this annotation

GitHub Actions / frontend-ci

'setTechStackPosition' is assigned a value but never used
x: 500,
y: 500,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { memo, useState } from 'react';
import { getTechIconUrl } from '@/features/widgets/techStack/utils/getTechIconUrl';
import { Badge } from '@/common/components/shadcn/badge';

interface TechIconProps {
name: string;
}

function TechIcon({ name }: TechIconProps) {
const [error, setError] = useState(false);
const iconUrl = getTechIconUrl(name);

if (error) {
return (
<Badge className="h-5 w-5 rounded-full bg-gray-200 font-bold text-gray-600">
{name.substring(0, 1)}
</Badge>
);
}

return (
<img
src={iconUrl}
alt={name}
className="h-5 w-5 object-contain"
onError={() => setError(true)}
/>
);
}

export function TechStackItem({ techName }: { techName: string }) {
return (
<Badge
variant="outline"
className="hover:border-primary flex h-25 w-25 flex-col items-center justify-center gap-2 rounded-lg border-gray-700 px-2 py-1 transition-colors select-none hover:bg-gray-700"
>
<TechIcon name={techName} />
<div className="text-center text-xs font-medium text-wrap text-gray-300">
{techName}
</div>
</Badge>
);
}

export default memo(TechStackItem);

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { memo } from 'react';
import TechStackItem from '@/features/widgets/techStack/components/TechStackItem';
import { useDraggable } from '@dnd-kit/core';
import type { TechStack } from '@/features/widgets/techStack/types/techStack';

function DraggableTechStackItem({ id, name, category }: TechStack) {
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
id: `tech-stack-${id}`,
data: {
support: ['techStackWidget'] as const,
content: { id, name, category },
},
});

const style = {
// DragOverlay를 사용하므로 원본은 transform 없이 제자리에 유지
opacity: isDragging ? 0.8 : 1,
cursor: isDragging ? 'grabbing' : 'grab',
};

return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
<TechStackItem techName={name} />
</div>
);
}

export default memo(DraggableTechStackItem);

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
import { Button } from '@/common/components/shadcn/button';
import { LuTrash2 } from 'react-icons/lu';
import { LuX } from 'react-icons/lu';
import type { ComponentProps } from 'react';

interface HeaderProps {
title: string;
icon: React.ReactNode;
onRemove: () => void;
onClose: () => void;
}

export default function ModalHeader({ title, icon, onRemove }: HeaderProps) {
export default function ModalHeader({
title,
icon,
onClose,
...props
}: HeaderProps & ComponentProps<'header'>) {
return (
<div className="mb-4 flex items-center justify-between border-b border-gray-700 pb-2 select-none">
<h4 className="flex items-center gap-2 font-bold text-white">
<header
className="flex cursor-move items-center justify-between border-b border-gray-700 px-4 py-1 select-none"
{...props}
>
<div className="flex items-center gap-2 text-sm font-bold text-white">
{icon} {title}
</h4>
</div>
<Button
variant="ghost"
onMouseDown={(e) => e.stopPropagation()}
onClick={onRemove}
className="hover:text-main text-gray-500 transition-colors hover:cursor-pointer hover:bg-transparent dark:hover:bg-transparent"
onClick={onClose}
className="hover:text-main shrink-0 text-gray-500 transition-colors hover:cursor-pointer hover:bg-transparent dark:hover:bg-transparent"
>
<LuTrash2 size={16} />
<LuX size={16} />
</Button>
</div>
</header>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import { Button } from '@/common/components/shadcn/button';
import { Input } from '@/common/components/shadcn/input';
import { LuSearch } from 'react-icons/lu';

export default function SearchBar({
search,
setSearch,
}: {
interface SearchBarProps {
search: string;
setSearch: (search: string) => void;
}) {
}

export default function SearchBar({ search, setSearch }: SearchBarProps) {
return (
<div className="relative mb-4 flex items-center justify-between pb-2 select-none">
<div className="relative mb-6 flex items-center justify-between select-none">
<Input
type="text"
placeholder="기술 스택을 검색하세요"
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { memo, useMemo } from 'react';
import NoContents from './NoContents';
import { TECH_STACKS } from '@/features/widgets/techStack/constant/techStackInfo';
import DraggableTechStackItem from './DraggableTechStackItem';

interface TechStackListProps {
keyword: string;
}

function TechStackList({ keyword }: TechStackListProps) {
const filteredStacks = useMemo(() => {
const lower = keyword.toLowerCase();
return TECH_STACKS.filter((tech) =>
tech.name.toLowerCase().includes(lower),
);
}, [keyword]);

if (filteredStacks.length === 0) {
return <NoContents />;
}

return (
<div className="flex flex-wrap gap-2 overflow-y-auto">
{filteredStacks.map((tech) => (
<DraggableTechStackItem key={tech.id} {...tech} />
))}
</div>
);
}

export default memo(TechStackList);
Loading
Loading