Skip to content

Commit 155d5df

Browse files
committed
feat: tier list app, tier forge
1 parent 1ce2052 commit 155d5df

File tree

11 files changed

+986
-28
lines changed

11 files changed

+986
-28
lines changed

public/apps/apps.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,14 @@
164164
"icon": "SparkleIcon",
165165
"order": 2,
166166
"apps": [
167+
{
168+
"slug": "tier-forge",
169+
"to": "/apps/tier-forge",
170+
"title": "Tier Forge",
171+
"description": "Construct and visualize ranked data sets with drag-and-drop precision.",
172+
"icon": "ListBulletsIcon",
173+
"created_at": "2026-01-10T12:00:00+03:00"
174+
},
167175
{
168176
"slug": "aether",
169177
"to": "/apps/aether",
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
When building **Tier Forge**, I needed a flexible way to move items between the "pool" and various "tiers". While libraries like `react-beautiful-dnd` or `dnd-kit` are excellent, sometimes you just want full control without the overhead.
2+
3+
Here is how I implemented a robust drag-and-drop system using only the native HTML5 API and React state.
4+
5+
## The State Architecture
6+
7+
The key to a good DnD system is centralized state. In `TierForge`, the state is held in the parent component:
8+
9+
```javascript
10+
const [tiers, setTiers] = useState(DEFAULT_TIERS); // The board
11+
const [poolItems, setPoolItems] = useState([]); // The unranked items
12+
const [dragData, setDragData] = useState(null); // What are we dragging?
13+
```
14+
15+
We track `dragData` to know *what* is moving (`itemId`) and *where* it came from (`sourceId`).
16+
17+
## The Handlers
18+
19+
We need three main handlers: `onDragStart`, `onDragOver`, and `onDrop`.
20+
21+
### 1. Starting the Drag
22+
23+
When a user grabs an item, we store its ID and source container ID. We also set `dataTransfer` for compatibility.
24+
25+
```javascript
26+
const handleDragStart = (e, itemId, sourceId) => {
27+
setDragData({ itemId, sourceId });
28+
e.dataTransfer.effectAllowed = 'move';
29+
// Fallback for some browsers
30+
e.dataTransfer.setData('text/plain', JSON.stringify({ itemId, sourceId }));
31+
};
32+
```
33+
34+
### 2. Allowing the Drop
35+
36+
By default, HTML elements don't accept drops. We must prevent the default behavior.
37+
38+
```javascript
39+
const handleDragOver = (e) => {
40+
e.preventDefault();
41+
e.dataTransfer.dropEffect = 'move';
42+
};
43+
```
44+
45+
### 3. Handling the Drop
46+
47+
This is where the magic happens. When an item is dropped, we:
48+
1. Identify the **Source** (where it came from) and **Target** (where it landed).
49+
2. If Source === Target, do nothing (or reorder).
50+
3. Find the item in the Source array.
51+
4. Remove it from the Source.
52+
5. Add it to the Target.
53+
54+
```javascript
55+
const handleDrop = (e, targetId) => {
56+
e.preventDefault();
57+
const data = dragData || JSON.parse(e.dataTransfer.getData('text/plain'));
58+
if (!data) return;
59+
60+
const { itemId, sourceId } = data;
61+
if (sourceId === targetId) return;
62+
63+
// ... Logic to find item, remove from source, add to target ...
64+
// This involves setTiers() and setPoolItems() updates.
65+
};
66+
```
67+
68+
## The Components
69+
70+
### Draggable Item
71+
The item itself needs the `draggable` attribute and the start handler.
72+
73+
```jsx
74+
<div
75+
draggable
76+
onDragStart={(e) => onDragStart(e, item.id, sourceId)}
77+
className="cursor-grab active:cursor-grabbing ..."
78+
>
79+
{/* Content */}
80+
</div>
81+
```
82+
83+
### Drop Zone
84+
The container (Tier or Pool) listens for drag-over and drop events.
85+
86+
```jsx
87+
<div
88+
onDragOver={handleDragOver}
89+
onDrop={(e) => handleDrop(e, containerId)}
90+
className="..."
91+
>
92+
{/* Render Items */}
93+
</div>
94+
```
95+
96+
## Why Native API?
97+
98+
1. **Zero Dependencies:** Keeps the bundle size small.
99+
2. **Full Control:** I can define exactly how state updates happen.
100+
3. **Performance:** Direct DOM events are highly performant.
101+
102+
This pattern powers the entire Tier Forge experience, allowing smooth transitions of assets between the chaotic pool and the structured tiers.

public/posts/posts.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
11
[
2+
{
3+
"slug": "implementing-drag-and-drop-in-react",
4+
"title": "Implementing Drag and Drop in React without Libraries",
5+
"date": "2026-01-10",
6+
"updated": "2026-01-10",
7+
"description": "A deep dive into building a robust drag-and-drop system for Tier Forge using the native HTML5 Drag and Drop API.",
8+
"tags": ["react", "drag-and-drop", "javascript", "tutorial", "frontend"],
9+
"category": "dev",
10+
"filename": "implementing-drag-and-drop-in-react.txt",
11+
"authors": ["fezcode"]
12+
},
213
{
314
"slug": "gh-pages-enametoolong-fix",
415
"title": "Fixing gh-pages: Resolving spawn ENAMETOOLONG",

0 commit comments

Comments
 (0)