Feat: fully working dnd
This commit is contained in:
47
src/App.tsx
47
src/App.tsx
@@ -1,36 +1,22 @@
|
|||||||
|
import { DndContext, DragEndEvent } from '@dnd-kit/core';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { useShallow } from 'zustand/shallow';
|
import { useShallow } from 'zustand/shallow';
|
||||||
import './App.scss';
|
import './App.scss';
|
||||||
import { TierImage } from './components/Image';
|
import { TierImage } from './components/Image';
|
||||||
import { Tier, TierProps } from './components/Tier';
|
import { Tier } from './components/Tier';
|
||||||
|
import { TierModal } from './components/TierModal';
|
||||||
import useStore from './store';
|
import useStore from './store';
|
||||||
|
|
||||||
const default_tier_levels: TierProps[] = [
|
|
||||||
{
|
|
||||||
name: 'S',
|
|
||||||
color: 'green',
|
|
||||||
textColor: 'black',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'A',
|
|
||||||
color: 'yellow',
|
|
||||||
textColor: 'black',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'B',
|
|
||||||
color: 'red',
|
|
||||||
textColor: 'black',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
interface tierImage {
|
interface tierImage {
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
category: string;
|
category: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function App() {
|
const App = () => {
|
||||||
const [images, addTierImage] = useStore(useShallow(state => [state.images, state.addTierImage]));
|
const [images, addTierImage, tierLevels, editTierImage] = useStore(
|
||||||
|
useShallow(state => [state.images, state.addTierImage, state.tierLevels, state.editTierImage]),
|
||||||
|
);
|
||||||
|
|
||||||
const uploadBtn = useRef<HTMLInputElement>(null);
|
const uploadBtn = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
@@ -39,7 +25,7 @@ function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getRandomCategoryName = () => {
|
const getRandomCategoryName = () => {
|
||||||
const categoryNames = default_tier_levels.flatMap(category => category.name);
|
const categoryNames = tierLevels.flatMap(category => category.name);
|
||||||
return categoryNames[Math.floor(Math.random() * categoryNames.length)];
|
return categoryNames[Math.floor(Math.random() * categoryNames.length)];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -49,10 +35,18 @@ function App() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDragEnd = (event: DragEndEvent) => {
|
||||||
|
const { active, over } = event;
|
||||||
|
if (active.data.current && over && over.data.current) {
|
||||||
|
const image: tierImage = active.data.current.image;
|
||||||
|
editTierImage(image, over.data.current.name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<DndContext onDragEnd={handleDragEnd}>
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
{default_tier_levels.map(tier_level => (
|
{tierLevels.map(tier_level => (
|
||||||
<Tier
|
<Tier
|
||||||
color={tier_level.color}
|
color={tier_level.color}
|
||||||
textColor={tier_level.textColor}
|
textColor={tier_level.textColor}
|
||||||
@@ -94,8 +88,9 @@ function App() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
<TierModal />
|
||||||
|
</DndContext>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
@@ -1,20 +1,27 @@
|
|||||||
import { useDraggable } from '@dnd-kit/core';
|
import { useDraggable } from '@dnd-kit/core';
|
||||||
import { tierImage } from '../store';
|
import { useEffect } from 'react';
|
||||||
|
import { tierImage } from '../dto/tier';
|
||||||
|
import useStore from '../store';
|
||||||
import './Image.scss';
|
import './Image.scss';
|
||||||
interface ImageProps {
|
interface ImageProps {
|
||||||
image: tierImage;
|
image: tierImage;
|
||||||
onDragStart?: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TierImage = ({ image }: ImageProps) => {
|
export const TierImage = ({ image }: ImageProps) => {
|
||||||
const { attributes, listeners, setNodeRef, transform } = useDraggable({
|
const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
|
||||||
id: `draggable:${image.name}`,
|
id: `draggable:${image.name}`,
|
||||||
|
data: { image },
|
||||||
});
|
});
|
||||||
|
const setModalOpen = useStore(state => state.setTierLevelModalOpen);
|
||||||
const style = transform
|
const style = transform
|
||||||
? {
|
? {
|
||||||
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
|
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
|
||||||
|
zIndex: 99999,
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
useEffect(() => {
|
||||||
|
setModalOpen(isDragging);
|
||||||
|
}, [isDragging, setModalOpen]);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='flex flex-wrap justify-center gap-4 mr-1 anime-container'
|
className='flex flex-wrap justify-center gap-4 mr-1 anime-container'
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import useStore from '../store';
|
|||||||
import { TierImage } from './Image';
|
import { TierImage } from './Image';
|
||||||
|
|
||||||
export interface TierProps {
|
export interface TierProps {
|
||||||
color: 'red' | 'yellow' | 'green';
|
color: string;
|
||||||
name: string;
|
name: string;
|
||||||
textColor: 'white' | 'black';
|
textColor: string;
|
||||||
|
id?: string;
|
||||||
}
|
}
|
||||||
export const Tier = ({ color, name, textColor }: TierProps) => {
|
export const Tier = ({ color, name, textColor }: TierProps) => {
|
||||||
let color_code = '';
|
let color_code = '';
|
||||||
@@ -21,14 +22,17 @@ export const Tier = ({ color, name, textColor }: TierProps) => {
|
|||||||
color_code = '#ffeaa7';
|
color_code = '#ffeaa7';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
color_code = '#6c5ce7';
|
color_code = color;
|
||||||
}
|
}
|
||||||
switch (textColor) {
|
switch (textColor) {
|
||||||
case 'white':
|
case 'white':
|
||||||
text_color_code = '#dfe6e9';
|
text_color_code = '#dfe6e9';
|
||||||
break;
|
break;
|
||||||
default:
|
case 'black':
|
||||||
text_color_code = '#2d3436';
|
text_color_code = '#2d3436';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
text_color_code = textColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
19
src/components/TierModal.tsx
Normal file
19
src/components/TierModal.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { useShallow } from 'zustand/shallow';
|
||||||
|
import useStore from '../store';
|
||||||
|
import { TierModalCategory } from './TierModalCategory';
|
||||||
|
|
||||||
|
export const TierModal = () => {
|
||||||
|
const [modalOpen, tierLevels] = useStore(useShallow(state => [state.tierLevelsModalOpen, state.tierLevels]));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] ${
|
||||||
|
modalOpen ? 'flex' : 'hidden'
|
||||||
|
} w-2/5 h-1/4 bg-[#2d3436] ps-8 pe-8 pt-8 pb-8 z-50 rounded-3xl flex-row gap-4 justify-center items-center text-white border-white border-2`}
|
||||||
|
>
|
||||||
|
{tierLevels.map(tier_level => (
|
||||||
|
<TierModalCategory name={tier_level.name} color={tier_level.color} textColor={tier_level.textColor} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
67
src/components/TierModalCategory.tsx
Normal file
67
src/components/TierModalCategory.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { useDroppable } from '@dnd-kit/core';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
interface TierModalCategoryProps {
|
||||||
|
color: string;
|
||||||
|
textColor: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TierModalCategory = ({ color, textColor, name }: TierModalCategoryProps) => {
|
||||||
|
const { setNodeRef, isOver } = useDroppable({
|
||||||
|
id: 'droppable:' + name,
|
||||||
|
data: { name },
|
||||||
|
});
|
||||||
|
const getColorCode = (color: string) => {
|
||||||
|
let color_code = '';
|
||||||
|
// TODO: color on hover
|
||||||
|
switch (color) {
|
||||||
|
case 'green':
|
||||||
|
color_code = isOver ? '#55efc4' : '#00b894';
|
||||||
|
break;
|
||||||
|
case 'red':
|
||||||
|
color_code = isOver ? '#d63031' : '#ff7675';
|
||||||
|
break;
|
||||||
|
case 'yellow':
|
||||||
|
color_code = isOver ? '#fdcb6e' : '#ffeaa7';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
color_code = color;
|
||||||
|
}
|
||||||
|
return color_code;
|
||||||
|
};
|
||||||
|
const getTextColorCode = (textColor: string) => {
|
||||||
|
let text_color_code = '';
|
||||||
|
switch (textColor) {
|
||||||
|
case 'white':
|
||||||
|
text_color_code = '#dfe6e9';
|
||||||
|
break;
|
||||||
|
case 'black':
|
||||||
|
text_color_code = '#2d3436';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
text_color_code = textColor;
|
||||||
|
}
|
||||||
|
return text_color_code;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(isOver);
|
||||||
|
}, [isOver]);
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
backgroundColor: getColorCode(color),
|
||||||
|
color: getTextColorCode(textColor),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`w-32 h-32 flex justify-center items-center font-bold text-2xl rounded-lg`}
|
||||||
|
style={style}
|
||||||
|
key={name}
|
||||||
|
ref={setNodeRef}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
5
src/dto/tier.ts
Normal file
5
src/dto/tier.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export interface tierImage {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
category: string;
|
||||||
|
}
|
||||||
32
src/store.ts
32
src/store.ts
@@ -1,17 +1,19 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { devtools } from 'zustand/middleware';
|
import { devtools } from 'zustand/middleware';
|
||||||
|
import { TierProps } from './components/Tier';
|
||||||
export interface tierImage {
|
import { tierImage } from './dto/tier';
|
||||||
name: string;
|
import { generateTierLevel } from './tools/genarators';
|
||||||
url: string;
|
|
||||||
category: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface tierStore {
|
interface tierStore {
|
||||||
images: tierImage[];
|
images: tierImage[];
|
||||||
addTierImage: (image: tierImage) => void;
|
addTierImage: (image: tierImage) => void;
|
||||||
editTierImage: (image: tierImage, tier: string) => void;
|
editTierImage: (image: tierImage, tier: string) => void;
|
||||||
removeTierImage: (image: tierImage) => void;
|
removeTierImage: (image: tierImage) => void;
|
||||||
|
tierLevelsModalOpen: boolean;
|
||||||
|
setTierLevelModalOpen: (open: boolean) => void;
|
||||||
|
tierLevels: TierProps[];
|
||||||
|
addTierLevel: (name: string, color: string, textColor: string) => void;
|
||||||
|
editTierLevelName: (id: string, name: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStore = create(
|
const useStore = create(
|
||||||
@@ -20,11 +22,27 @@ const useStore = create(
|
|||||||
images: [] as tierImage[],
|
images: [] as tierImage[],
|
||||||
addTierImage: (image: tierImage) => set(state => ({ images: [...state.images, image] })),
|
addTierImage: (image: tierImage) => set(state => ({ images: [...state.images, image] })),
|
||||||
editTierImage: (image: tierImage, tier: string) =>
|
editTierImage: (image: tierImage, tier: string) =>
|
||||||
set(state => ({ images: state.images.map(i => (i.name === image.name ? { ...i, category: tier } : i)) })),
|
set(state => ({
|
||||||
|
images:
|
||||||
|
image.category === tier
|
||||||
|
? state.images
|
||||||
|
: state.images.map(i => (i.name === image.name ? { ...i, category: tier } : i)),
|
||||||
|
})),
|
||||||
removeTierImage: (image: tierImage) => {
|
removeTierImage: (image: tierImage) => {
|
||||||
URL.revokeObjectURL(image.url);
|
URL.revokeObjectURL(image.url);
|
||||||
set(state => ({ images: state.images.filter(i => i.name !== image.name) }));
|
set(state => ({ images: state.images.filter(i => i.name !== image.name) }));
|
||||||
},
|
},
|
||||||
|
tierLevelsModalOpen: false,
|
||||||
|
setTierLevelModalOpen: (open: boolean) => set({ tierLevelsModalOpen: open }),
|
||||||
|
tierLevels: [
|
||||||
|
generateTierLevel('S', 'green', 'black'),
|
||||||
|
generateTierLevel('A', 'yellow', 'black'),
|
||||||
|
generateTierLevel('B', 'red', 'black'),
|
||||||
|
] as TierProps[],
|
||||||
|
addTierLevel: (name: string, color: string, textColor: string) =>
|
||||||
|
set(state => ({ tierLevels: [...state.tierLevels, generateTierLevel(name, color, textColor)] })),
|
||||||
|
editTierLevelName: (id: string, name: string) =>
|
||||||
|
set(state => ({ tierLevels: state.tierLevels.map(t => (t.id === id ? { ...t, name } : t)) })),
|
||||||
}),
|
}),
|
||||||
{ name: 'tierStore' },
|
{ name: 'tierStore' },
|
||||||
),
|
),
|
||||||
|
|||||||
10
src/tools/genarators.ts
Normal file
10
src/tools/genarators.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { TierProps } from '../components/Tier';
|
||||||
|
|
||||||
|
export const generateTierLevel = (name: string, color: string, textColor: string): TierProps => {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
color,
|
||||||
|
textColor,
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user