Drag tasks into the right quadrant. Everything persists in your browser — no backend, no sign-up, no network required.
- 4-quadrant matrix — Do First, Schedule, Delegate, Eliminate
- Drag & drop — move tasks between quadrants with pointer or keyboard
- Persistent state — all data lives in
localStorage, survives page refresh - Cross-tab sync — changes in one tab reflect instantly in others
- Keyboard accessible — full arrow-key DnD, focus rings, ARIA labels throughout
- Responsive — 2-column grid on desktop, single column on mobile
- Zero runtime dependencies — no backend, no auth, no network calls
| Layer | Technology |
|---|---|
| Framework | React 18 |
| Language | TypeScript |
| Styling | Tailwind CSS |
| Drag & Drop | dnd-kit (core + sortable + utilities) |
| Build | Vite 5 |
| Deployment | Vercel |
ProjectScope/
├── src/
│ ├── components/
│ │ ├── Matrix.tsx # DndContext + 2×2 grid layout
│ │ ├── Quadrant.tsx # Droppable container + SortableContext
│ │ ├── TaskCard.tsx # Sortable card with drag handle
│ │ └── AddTaskForm.tsx # Inline task creation form
│ ├── hooks/
│ │ └── useLocalStorage.ts # Generic type-safe localStorage hook
│ ├── store/
│ │ └── useTasks.ts # All task CRUD — single source of truth
│ ├── types/
│ │ └── index.ts # Task, QuadrantId, QuadrantMeta types
│ ├── App.tsx # Shell: header, stats bar, Matrix
│ ├── main.tsx # Entry point
│ └── index.css # Tailwind directives + Inter font
├── public/
│ └── favicon.svg
├── index.html
├── vite.config.ts
├── tailwind.config.js
└── tsconfig.json
- Node.js ≥ 18
- npm ≥ 9 (or pnpm / yarn)
git clone https://github.com/sugumaran-nix/ProjectScope.git
cd ProjectScope
npm install
npm run devnpm run build # type-checks then bundles to dist/
npm run preview # preview the production build locallyThe dist/ folder is a fully static site — deploy to Vercel, Netlify, GitHub Pages, or any CDN.
useLocalStorage (localStorage sync + cross-tab)
↓
useTasks (CRUD operations, immutable updates)
↓
TaskContext (single instance at App level)
↓
Matrix (DndContext, drag lifecycle)
↓
Quadrant (SortableContext per quadrant)
↓
TaskCard (useSortable per task)
No external state library — useTasks is wrapped in a React Context called once at the App level.
| Concern | Solution |
|---|---|
| Drag activation | PointerSensor with 8px distance threshold (prevents accidental drags) |
| Keyboard DnD | KeyboardSensor + sortableKeyboardCoordinates |
| Collision detection | closestCorners — works well for multi-container grids |
| Cross-quadrant drops | handleDragEnd checks if over.id is a quadrant id or task id |
| Ghost card | DragOverlay renders a rotated clone outside the normal tree |
MIT