diff --git a/frontend/techpick/src/components/PickListViewer/DraggablePickListViewer.tsx b/frontend/techpick/src/components/PickListViewer/DraggablePickListViewer.tsx
index e850c965e..81c2b66fb 100644
--- a/frontend/techpick/src/components/PickListViewer/DraggablePickListViewer.tsx
+++ b/frontend/techpick/src/components/PickListViewer/DraggablePickListViewer.tsx
@@ -1,22 +1,24 @@
import type { ReactNode } from 'react';
import { PickDnDCard } from './PickDnDCard';
import { PickDnDCardListLayout } from './PickDnDCardListLayout';
+import { PickDndRecord } from './PickDndRecord';
+import { PickDndRecordListLayout } from './PickDndRecordListLayout';
import type {
PickViewItemComponentProps,
PickViewItemListLayoutComponentProps,
} from './PickListViewer';
-import type { PickInfoType } from '@/types';
+import type { PickInfoType, PickRenderModeType } from '@/types';
export function DraggablePickListViewer({
pickList,
- viewType = 'card',
+ viewType = 'list',
folderId,
}: PickListViewerProps) {
const { PickViewItemComponent, PickViewItemListLayoutComponent } =
DND_PICK_LIST_VIEW_TEMPLATES[viewType];
return (
-
+
{pickList.map((pickInfo) => (
))}
@@ -27,24 +29,23 @@ export function DraggablePickListViewer({
interface PickListViewerProps {
pickList: PickInfoType[];
folderId: number;
- viewType?: DnDViewTemplateType;
+ viewType?: PickRenderModeType;
}
const DND_PICK_LIST_VIEW_TEMPLATES: Record<
- DnDViewTemplateType,
+ PickRenderModeType,
DnDViewTemplateValueType
> = {
card: {
PickViewItemComponent: PickDnDCard,
PickViewItemListLayoutComponent: PickDnDCardListLayout,
},
+ list: {
+ PickViewItemComponent: PickDndRecord,
+ PickViewItemListLayoutComponent: PickDndRecordListLayout,
+ },
};
-/**
- * @description DnDViewTemplateType은 Drag&Drop이 가능한 UI 중 무엇을 보여줄지 나타냅니다. ex) card, list
- */
-type DnDViewTemplateType = 'card';
-
interface DnDViewTemplateValueType {
PickViewItemListLayoutComponent: (
props: PickViewDnDItemListLayoutComponentProps
@@ -53,6 +54,9 @@ interface DnDViewTemplateValueType {
}
export type PickViewDnDItemListLayoutComponentProps =
- PickViewItemListLayoutComponentProps<{ folderId: number }>;
+ PickViewItemListLayoutComponentProps<{
+ folderId: number;
+ viewType: PickRenderModeType;
+ }>;
export type PickViewDnDItemComponentProps = PickViewItemComponentProps;
diff --git a/frontend/techpick/src/components/PickListViewer/PickDnDCardListLayout.tsx b/frontend/techpick/src/components/PickListViewer/PickDnDCardListLayout.tsx
index 49d77d37a..a1cf7b8e3 100644
--- a/frontend/techpick/src/components/PickListViewer/PickDnDCardListLayout.tsx
+++ b/frontend/techpick/src/components/PickListViewer/PickDnDCardListLayout.tsx
@@ -6,11 +6,12 @@ import { PickListSortableContext } from './PickListSortableContext';
export function PickDnDCardListLayout({
children,
+ viewType,
folderId,
}: PickViewDnDItemListLayoutComponentProps) {
return (
diff --git a/frontend/techpick/src/components/PickListViewer/PickDndRecord.tsx b/frontend/techpick/src/components/PickListViewer/PickDndRecord.tsx
new file mode 100644
index 000000000..59f85f174
--- /dev/null
+++ b/frontend/techpick/src/components/PickListViewer/PickDndRecord.tsx
@@ -0,0 +1,103 @@
+'use client';
+
+import { MouseEvent, useCallback, type CSSProperties } from 'react';
+import { useSortable } from '@dnd-kit/sortable';
+import { CSS } from '@dnd-kit/utilities';
+import { usePickStore } from '@/stores';
+import { isSelectionActive } from '@/utils';
+import {
+ isActiveDraggingItemStyle,
+ selectedDragItemStyle,
+} from './pickDnDCard.css';
+import { getSelectedPickRange } from './pickDnDCard.util';
+import { PickRecord } from './PickRecord';
+import type { PickViewDnDItemComponentProps } from './PickListViewer';
+
+export function PickDndRecord({ pickInfo }: PickViewDnDItemComponentProps) {
+ const {
+ selectedPickIdList,
+ selectSinglePick,
+ getOrderedPickIdListByFolderId,
+ focusPickId,
+ setSelectedPickIdList,
+ isDragging,
+ } = usePickStore();
+ const { linkInfo, id: pickId, parentFolderId } = pickInfo;
+ const { url } = linkInfo;
+ const isSelected = selectedPickIdList.includes(pickId);
+ const {
+ attributes,
+ listeners,
+ setNodeRef,
+ transform,
+ transition,
+ isDragging: isActiveDragging,
+ } = useSortable({
+ id: pickId,
+ data: {
+ id: pickId,
+ type: 'pick',
+ parentFolderId: parentFolderId,
+ },
+ });
+ const pickElementId = `pickId-${pickId}`;
+
+ const style: CSSProperties = {
+ transform: CSS.Transform.toString(transform),
+ transition,
+ opacity: 1,
+ };
+
+ const openUrl = useCallback(() => {
+ window.open(url, '_blank');
+ }, [url]);
+
+ const handleShiftSelect = (parentFolderId: number, pickId: number) => {
+ if (!focusPickId) {
+ return;
+ }
+
+ // 새로운 선택된 배열 만들기.
+ const orderedPickIdList = getOrderedPickIdListByFolderId(parentFolderId);
+
+ const newSelectedPickIdList = getSelectedPickRange({
+ orderedPickIdList,
+ startPickId: focusPickId,
+ endPickId: pickId,
+ });
+
+ setSelectedPickIdList(newSelectedPickIdList);
+ };
+
+ const handleClick = (
+ pickId: number,
+ event: MouseEvent
+ ) => {
+ if (event.shiftKey && isSelectionActive(selectedPickIdList.length)) {
+ event.preventDefault();
+ handleShiftSelect(parentFolderId, pickId);
+ return;
+ }
+
+ selectSinglePick(pickId);
+ };
+
+ if (isDragging && isSelected && !isActiveDragging) {
+ return null;
+ }
+
+ return (
+ <>
+
+
handleClick(pickId, event)}
+ id={pickElementId}
+ >
+
+
+
+ >
+ );
+}
diff --git a/frontend/techpick/src/components/PickListViewer/PickDndRecordListLayout.tsx b/frontend/techpick/src/components/PickListViewer/PickDndRecordListLayout.tsx
new file mode 100644
index 000000000..c0f814f8d
--- /dev/null
+++ b/frontend/techpick/src/components/PickListViewer/PickDndRecordListLayout.tsx
@@ -0,0 +1,17 @@
+import { PickViewDnDItemListLayoutComponentProps } from './DraggablePickListViewer';
+import { PickListSortableContext } from './PickListSortableContext';
+import { PickRecordListLayout } from './PickRecordListLayout';
+
+export function PickDndRecordListLayout({
+ children,
+ folderId,
+ viewType,
+}: PickViewDnDItemListLayoutComponentProps) {
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/frontend/techpick/src/components/PickListViewer/PickListSortableContext.tsx b/frontend/techpick/src/components/PickListViewer/PickListSortableContext.tsx
index 4330a7171..de8ca13a1 100644
--- a/frontend/techpick/src/components/PickListViewer/PickListSortableContext.tsx
+++ b/frontend/techpick/src/components/PickListViewer/PickListSortableContext.tsx
@@ -1,12 +1,17 @@
'use client';
-import { SortableContext, rectSortingStrategy } from '@dnd-kit/sortable';
-import { usePickStore } from '@/stores/pickStore/pickStore';
+import {
+ SortableContext,
+ rectSortingStrategy,
+ verticalListSortingStrategy,
+} from '@dnd-kit/sortable';
+import { usePickStore } from '@/stores';
import { PickViewDnDItemListLayoutComponentProps } from './DraggablePickListViewer';
export function PickListSortableContext({
folderId,
children,
+ viewType,
}: PickViewDnDItemListLayoutComponentProps) {
const {
getOrderedPickIdListByFolderId,
@@ -23,11 +28,17 @@ export function PickListSortableContext({
)
: orderedPickIdList;
+ /**
+ * @description card일때와 vertical일 때 렌더링이 다릅니다.
+ */
+ const strategy =
+ viewType === 'card' ? rectSortingStrategy : verticalListSortingStrategy;
+
return (
{children}
diff --git a/frontend/techpick/src/components/PickListViewer/PickListViewerInfiniteScroll.tsx b/frontend/techpick/src/components/PickListViewer/PickListViewerInfiniteScroll.tsx
index ee28b3ac7..88fb7cff2 100644
--- a/frontend/techpick/src/components/PickListViewer/PickListViewerInfiniteScroll.tsx
+++ b/frontend/techpick/src/components/PickListViewer/PickListViewerInfiniteScroll.tsx
@@ -6,7 +6,7 @@ import InfiniteLoader from 'react-window-infinite-loader';
import { colorVars } from 'techpick-shared';
import { PickRecord } from '@/components/PickListViewer/PickRecord';
import {
- pickRecordListLayoutStyle,
+ pickRecordListLayoutInlineStyle,
RECORD_HEIGHT,
} from '@/components/PickListViewer/pickRecordListLayout.css';
import { usePickStore } from '@/stores';
@@ -62,7 +62,7 @@ export function PickListViewerInfiniteScroll(
>
{({ onItemsRendered, ref }) => (
{children}
;
+}
diff --git a/frontend/techpick/src/components/PickListViewer/pickRecordListLayout.css.ts b/frontend/techpick/src/components/PickListViewer/pickRecordListLayout.css.ts
index 8c6b262e6..68681eccf 100644
--- a/frontend/techpick/src/components/PickListViewer/pickRecordListLayout.css.ts
+++ b/frontend/techpick/src/components/PickListViewer/pickRecordListLayout.css.ts
@@ -1,5 +1,16 @@
-import { CSSProperties } from 'react';
+import type { CSSProperties } from 'react';
+import { style } from '@vanilla-extract/css';
+import { sizes, space } from 'techpick-shared';
export const RECORD_HEIGHT = 100;
-export const pickRecordListLayoutStyle: CSSProperties = {};
+export const pickRecordListLayoutInlineStyle: CSSProperties = {};
+
+export const pickRecordListLayoutStyle = style({
+ display: 'flex',
+ flexDirection: 'column',
+ gap: space['12'],
+ width: sizes['full'],
+ height: sizes['full'],
+ overflowY: 'scroll',
+});
diff --git a/frontend/techpick/src/stores/index.ts b/frontend/techpick/src/stores/index.ts
index 3b6f750dc..18c84e6a8 100644
--- a/frontend/techpick/src/stores/index.ts
+++ b/frontend/techpick/src/stores/index.ts
@@ -1,3 +1,4 @@
export { useTagStore } from './tagStore';
export { usePickStore } from './pickStore/pickStore';
export { useTreeStore } from './dndTreeStore/dndTreeStore';
+export { usePickRenderModeStore } from './pickRenderModeStore';
diff --git a/frontend/techpick/src/stores/pickRenderModeStore.ts b/frontend/techpick/src/stores/pickRenderModeStore.ts
new file mode 100644
index 000000000..7d2bff2c0
--- /dev/null
+++ b/frontend/techpick/src/stores/pickRenderModeStore.ts
@@ -0,0 +1,24 @@
+import { create } from 'zustand';
+import { immer } from 'zustand/middleware/immer';
+import type {
+ PickRenderModeAction,
+ PickRenderModeState,
+} from './pickRenderModeStore.type';
+
+const initialState: PickRenderModeState = {
+ pickRenderMode: 'list',
+};
+
+export const usePickRenderModeStore = create<
+ PickRenderModeState & PickRenderModeAction
+>()(
+ immer((set) => ({
+ ...initialState,
+
+ setPickRenderMode: (newPickRenderMode) => {
+ set((state) => {
+ state.pickRenderMode = newPickRenderMode;
+ });
+ },
+ }))
+);
diff --git a/frontend/techpick/src/stores/pickRenderModeStore.type.ts b/frontend/techpick/src/stores/pickRenderModeStore.type.ts
new file mode 100644
index 000000000..32d55bdcd
--- /dev/null
+++ b/frontend/techpick/src/stores/pickRenderModeStore.type.ts
@@ -0,0 +1,9 @@
+import type { PickRenderModeType } from '@/types';
+
+export type PickRenderModeState = {
+ pickRenderMode: PickRenderModeType;
+};
+
+export type PickRenderModeAction = {
+ setPickRenderMode: (newPickRenderMode: PickRenderModeType) => void;
+};
diff --git a/frontend/techpick/src/types/PickRenderModeType.ts b/frontend/techpick/src/types/PickRenderModeType.ts
new file mode 100644
index 000000000..a50072175
--- /dev/null
+++ b/frontend/techpick/src/types/PickRenderModeType.ts
@@ -0,0 +1,4 @@
+/**
+ * @description PickRenderModeType은 Pick의 렌더링 UI 중 무엇을 보여줄지 나타냅니다. ex) card, list
+ */
+export type PickRenderModeType = 'card' | 'list';
diff --git a/frontend/techpick/src/types/index.ts b/frontend/techpick/src/types/index.ts
index 099c111bd..5dac9653f 100644
--- a/frontend/techpick/src/types/index.ts
+++ b/frontend/techpick/src/types/index.ts
@@ -6,3 +6,4 @@ export type * from './dnd.type';
export type { PickDraggableObjectType } from './PickDraggableObjectType';
export type { FolderDraggableObjectType } from './FolderDraggableObjectType';
export type { PickToFolderDroppableObjectType } from './PickToFolderDroppableObjectType';
+export type { PickRenderModeType } from './PickRenderModeType';