feat: prepare vim-sudoku for unified API implementation
- Archive previous implementation attempt (src -> src_failed) - Create comprehensive IMPLEMENTATION_PLAN.md with phased approach - Create IMPLEMENTATION.md to track implementation progress - Update example app to match vim-maze patterns: - Simplify UI to desktop-only (no mobile support) - Add score metrics display (time, keystrokes, final score) - Update instructions for sudoku-specific keys (hjkl, i for edit) - Remove unnecessary mobile hooks Phase 5 complete. Ready to begin Phase 10 (Setup & Core Types).
This commit is contained in:
@@ -0,0 +1,68 @@
|
|||||||
|
# vim-sudoku Unified API Implementation Log
|
||||||
|
|
||||||
|
This document tracks the step-by-step implementation of the unified API architecture for vim-sudoku.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
We are porting vim-sudoku from the old architecture (src_old) to follow the unified API patterns established by vim-maze. This enables consistent architecture across all VIMazing games and better platform integration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Iteration 1: Preparation & Planning
|
||||||
|
|
||||||
|
**Date**: 2025-01-22
|
||||||
|
|
||||||
|
### Goals
|
||||||
|
- Review existing implementations (vim-maze, src_old, src_failed)
|
||||||
|
- Create implementation plan
|
||||||
|
- Prepare example app for new API
|
||||||
|
|
||||||
|
### Tasks Completed
|
||||||
|
|
||||||
|
#### 1. Reviewed Architecture
|
||||||
|
- Analyzed vim-maze unified API implementation
|
||||||
|
- Examined src_old working implementation
|
||||||
|
- Identified src_failed attempt issues
|
||||||
|
- Documented key differences between vim-maze and vim-sudoku
|
||||||
|
|
||||||
|
#### 2. Created Implementation Plan
|
||||||
|
- Created `IMPLEMENTATION_PLAN.md` with 7 phases (numbered x10 for flexibility)
|
||||||
|
- Defined file structure matching vim-maze patterns
|
||||||
|
- Documented key design decisions
|
||||||
|
- Established type mappings between vim-maze and vim-sudoku
|
||||||
|
- Listed success criteria
|
||||||
|
|
||||||
|
#### 3. Prepared Example App (Phase 5)
|
||||||
|
- Updated `example/src/App.tsx` to match vim-maze structure
|
||||||
|
- Simplified to desktop-only (no mobile support)
|
||||||
|
- Added score metrics display (time, keystrokes, final score)
|
||||||
|
- Updated game status messages for sudoku context
|
||||||
|
- Changed navigation instructions (hjkl + i for edit mode)
|
||||||
|
- Verified `useKeyBindings` already matches vim-maze
|
||||||
|
|
||||||
|
### File Changes
|
||||||
|
```
|
||||||
|
vim-sudoku/
|
||||||
|
├── IMPLEMENTATION_PLAN.md [CREATED] - Detailed implementation roadmap
|
||||||
|
├── IMPLEMENTATION.md [CREATED] - This file, tracking progress
|
||||||
|
├── example/src/App.tsx [MODIFIED] - Simplified, sudoku-focused UI
|
||||||
|
└── src/ → src_failed/ [RENAMED] - Previous failed attempt archived
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Decisions
|
||||||
|
|
||||||
|
1. **No Mobile Support**: Sudoku with vim bindings requires keyboard, mobile not practical
|
||||||
|
2. **Simple Metrics**: Start with time, keystrokes, and final score (no efficiency yet)
|
||||||
|
3. **Phase Numbering**: Use x10 numbering (10, 20, 30...) to allow insertions
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
- Phase 10: Setup & Core Types
|
||||||
|
- Create clean src/ directory structure
|
||||||
|
- Define all type definitions
|
||||||
|
- Set up module exports
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Iteration 2: [Pending]
|
||||||
|
|
||||||
|
*To be filled as implementation progresses...*
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
# vim-sudoku Unified API Implementation Plan
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Port vim-sudoku to the unified API architecture established by vim-maze, following the composable hook pattern with `useGame()` as the single entry point.
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
1. Follow unified API architecture from UNIFIED_API.md
|
||||||
|
2. Maintain vim-maze patterns and conventions
|
||||||
|
3. Preserve all working functionality from src_old
|
||||||
|
4. Support platform integration via optional platformHook parameter
|
||||||
|
5. Enable code reuse across games (vim-maze, vim-sudoku, vim-snake)
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Core Composition
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Inside useGame:
|
||||||
|
const board = useBoard();
|
||||||
|
const gameStatusManager = useGameStatus();
|
||||||
|
const { cursor, keyManager } = useCursor(board, gameStatusManager);
|
||||||
|
const scoreManager = useScore({ board, gameStatusManager, keyManager });
|
||||||
|
|
||||||
|
// Returns unified GameManager interface
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── index.ts # Main package exports
|
||||||
|
├── types.ts # Re-exports all types from modules
|
||||||
|
├── useGame.ts # Main orchestrator hook
|
||||||
|
├── useGameStatus/
|
||||||
|
│ ├── index.ts # Public exports
|
||||||
|
│ ├── types.ts # GameStatus, GameStatusManager
|
||||||
|
│ └── useGameStatus.ts # Game state management
|
||||||
|
├── useBoard/
|
||||||
|
│ ├── index.ts # Public exports
|
||||||
|
│ ├── types.ts # SudokuBoard, SudokuCell, Difficulty, BoardManager
|
||||||
|
│ ├── useBoard.ts # Board management hook
|
||||||
|
│ ├── SudokuGenerator.ts # Puzzle generation (from src_old/lib/Sudoku.ts)
|
||||||
|
│ └── SudokuRenderer.ts # DOM rendering for sudoku grid
|
||||||
|
├── useCursor/
|
||||||
|
│ ├── index.ts # Public exports
|
||||||
|
│ ├── types.ts # CursorManager, CursorMode, VimMotionSystem, CellEditor
|
||||||
|
│ ├── useCursor.ts # Main cursor/input hook
|
||||||
|
│ ├── useCellEditor.ts # Cell editing logic (analog to useHero)
|
||||||
|
│ ├── useCursorRender.ts # Cursor rendering on DOM (analog to useHeroRender)
|
||||||
|
│ ├── useVimMotions.ts # Vim keybinding system
|
||||||
|
│ └── useGameKeys.ts # Game-specific key handling
|
||||||
|
└── useScore/
|
||||||
|
├── index.ts # Public exports
|
||||||
|
├── types.ts # TimerManager, ScoreManager
|
||||||
|
├── useScore.ts # Main scoring hook
|
||||||
|
└── useTimer.ts # Timer state management
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
### Phase 5: Prepare Example App
|
||||||
|
|
||||||
|
- [x] Update example/src/App.tsx to match vim-maze structure
|
||||||
|
- [x] Update App to display score metrics (time, keystrokes, final score)
|
||||||
|
- [x] Update App to show game status messages
|
||||||
|
- [x] Update App instructions for sudoku-specific keys (i for edit)
|
||||||
|
- [x] Keep it simple - no mobile support for now
|
||||||
|
|
||||||
|
### Phase 10: Setup & Core Types
|
||||||
|
|
||||||
|
- [ ] Create clean src/ directory structure
|
||||||
|
- [ ] Define all types in module-specific types.ts files
|
||||||
|
- [ ] Create index.ts exports for each module
|
||||||
|
- [ ] Set up main types.ts re-export file
|
||||||
|
|
||||||
|
### Phase 20: useBoard Hook
|
||||||
|
|
||||||
|
- [ ] Port SudokuGenerator from src_old/lib/Sudoku.ts
|
||||||
|
- [ ] Port SudokuRenderer from src_old rendering logic
|
||||||
|
- [ ] Implement useBoard hook with refs pattern
|
||||||
|
- containerRef for DOM
|
||||||
|
- boardRef for SudokuBoard (2D array of cells)
|
||||||
|
- initialBoardRef for puzzle state
|
||||||
|
- solutionRef for solved state
|
||||||
|
- [ ] Methods: renderBoard, generateBoard, getCell, setCell, canEditCell, isComplete, isFullyValid
|
||||||
|
|
||||||
|
### Phase 30: useGameStatus Hook
|
||||||
|
|
||||||
|
- [ ] Implement GameStatus state machine
|
||||||
|
- 'waiting' → 'started' → 'game-won' / 'game-over'
|
||||||
|
- 'paused' state support
|
||||||
|
- [ ] Methods: startGame, togglePause, quitGame, setGameStatus
|
||||||
|
|
||||||
|
### Phase 40: useCursor Hook Composition
|
||||||
|
|
||||||
|
#### 40.1 useCellEditor (analog to useHero)
|
||||||
|
- [ ] Manage cursor position (row, col)
|
||||||
|
- [ ] Handle cell editing operations
|
||||||
|
- [ ] Validate edits against isGiven cells
|
||||||
|
- [ ] Trigger validation on cell changes
|
||||||
|
- [ ] Handle win condition detection
|
||||||
|
|
||||||
|
#### 40.2 useVimMotions
|
||||||
|
- [ ] Port vim motion system from vim-maze
|
||||||
|
- [ ] Support: h/j/k/l, ^/$, g/G, . (repeat)
|
||||||
|
- [ ] Support: i (insert), r (replace), x (delete)
|
||||||
|
- [ ] Count prefix handling (e.g., 5j)
|
||||||
|
- [ ] Mode transitions (normal ↔ edit)
|
||||||
|
|
||||||
|
#### 40.3 useGameKeys
|
||||||
|
- [ ] Keyboard event listener
|
||||||
|
- [ ] Route keys through vim motion processor
|
||||||
|
- [ ] Execute cursor movements
|
||||||
|
- [ ] Execute cell editing commands
|
||||||
|
- [ ] Track keystroke history
|
||||||
|
- [ ] Handle game-specific keys (q, p, space)
|
||||||
|
|
||||||
|
#### 40.4 useCursorRender (analog to useHeroRender)
|
||||||
|
- [ ] Render cursor highlight on current cell
|
||||||
|
- [ ] Show edit mode indicator
|
||||||
|
- [ ] Flash animations (valid/invalid/locked)
|
||||||
|
- [ ] Highlight conflict cells
|
||||||
|
|
||||||
|
#### 40.5 useCursor Main Hook
|
||||||
|
- [ ] Compose cellEditor, vimMotions, gameKeys, cursorRender
|
||||||
|
- [ ] Return unified CursorManager interface
|
||||||
|
- [ ] Split keyManager for useGame composition
|
||||||
|
|
||||||
|
### Phase 50: useScore Hook
|
||||||
|
|
||||||
|
- [ ] Port timer from src_old/useScore/hooks/useTimer.ts
|
||||||
|
- [ ] Track keystrokes from keyManager.keyLog
|
||||||
|
- [ ] Calculate efficiency metrics
|
||||||
|
- [ ] Detect win/loss conditions based on completion
|
||||||
|
- [ ] Compute final score on game-won
|
||||||
|
|
||||||
|
### Phase 60: useGame Orchestrator
|
||||||
|
|
||||||
|
- [ ] Compose all hooks (board, cursor, gameStatus, score)
|
||||||
|
- [ ] Handle initial board generation
|
||||||
|
- [ ] Coordinate rendering updates
|
||||||
|
- [ ] Expose unified GameManager interface
|
||||||
|
- [ ] Support optional platformHook parameter
|
||||||
|
|
||||||
|
### Phase 70: Testing & Refinement
|
||||||
|
|
||||||
|
- [ ] Test with example app
|
||||||
|
- [ ] Verify all vim motions work
|
||||||
|
- [ ] Verify edit mode (i/r/x) works
|
||||||
|
- [ ] Verify validation and conflicts work
|
||||||
|
- [ ] Verify win condition triggers
|
||||||
|
- [ ] Test platform hook integration
|
||||||
|
- [ ] Update UNIFIED_API.md with sudoku specifics
|
||||||
|
|
||||||
|
## Key Design Decisions
|
||||||
|
|
||||||
|
### 1. Board Representation
|
||||||
|
|
||||||
|
**Choice**: Use 2D array of SudokuCell objects
|
||||||
|
```typescript
|
||||||
|
type SudokuCell = {
|
||||||
|
value: string; // '1'-'9' or ''
|
||||||
|
isGiven: boolean; // true for puzzle clues
|
||||||
|
hasConflict: boolean; // validation state
|
||||||
|
};
|
||||||
|
type SudokuBoard = SudokuCell[][];
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rationale**: Easier to work with than 81-char string, matches vim-maze pattern of using structured data.
|
||||||
|
|
||||||
|
### 2. Cell Editing (analog to Hero Movement)
|
||||||
|
|
||||||
|
**Choice**: useCellEditor hook manages cell editing state
|
||||||
|
- Similar to useHero managing hero position in maze
|
||||||
|
- Handles validation before setting values
|
||||||
|
- Triggers game status changes on completion
|
||||||
|
|
||||||
|
**Rationale**: Keeps editing logic separate from cursor movement, following vim-maze separation of concerns.
|
||||||
|
|
||||||
|
### 3. Edit Mode vs Insert Mode
|
||||||
|
|
||||||
|
**Choice**: Two modes - 'normal' and 'edit'
|
||||||
|
- Normal mode: vim navigation + commands (i/r/x/d)
|
||||||
|
- Edit mode: digit entry (1-9) + erase (x/Backspace)
|
||||||
|
- i = insert (enter edit mode on empty cell)
|
||||||
|
- r = replace (enter edit mode on filled cell)
|
||||||
|
- Escape/Enter = exit edit mode
|
||||||
|
|
||||||
|
**Rationale**: Matches vim philosophy and src_old behavior.
|
||||||
|
|
||||||
|
### 4. Rendering Strategy
|
||||||
|
|
||||||
|
**Choice**: Keep rendering separate from state
|
||||||
|
- SudokuRenderer handles DOM generation
|
||||||
|
- useCursorRender handles cursor highlighting
|
||||||
|
- useBoard calls renderer with board data
|
||||||
|
|
||||||
|
**Rationale**: Matches vim-maze pattern (MazeRenderer + useHeroRender).
|
||||||
|
|
||||||
|
### 5. Validation Approach
|
||||||
|
|
||||||
|
**Choice**: Validate on setCell, show conflicts visually
|
||||||
|
- Real-time validation when setting digits
|
||||||
|
- Flash animations for feedback
|
||||||
|
- Highlight conflicting cells temporarily
|
||||||
|
- Don't prevent invalid moves (optional strictMode)
|
||||||
|
|
||||||
|
**Rationale**: Provides immediate feedback without blocking player exploration.
|
||||||
|
|
||||||
|
## Type Mappings (vim-maze → vim-sudoku)
|
||||||
|
|
||||||
|
| vim-maze | vim-sudoku | Notes |
|
||||||
|
|----------|------------|-------|
|
||||||
|
| MazeCell[][] | SudokuBoard | Board structure |
|
||||||
|
| Coord | Coord | Same (row, col) |
|
||||||
|
| useHero | useCellEditor | Cell editing vs movement |
|
||||||
|
| useHeroRender | useCursorRender | Visual feedback |
|
||||||
|
| MazeGenerator | SudokuGenerator | Puzzle generation |
|
||||||
|
| MazeRenderer | SudokuRenderer | Grid rendering |
|
||||||
|
| hero.moveTo | cellEditor.setValue | Action semantics |
|
||||||
|
| 'entrance'/'exit'/'key' | 'isGiven'/'value' | Cell metadata |
|
||||||
|
|
||||||
|
## Differences from vim-maze
|
||||||
|
|
||||||
|
1. **No pathfinding**: Simple grid bounds checking
|
||||||
|
2. **Edit mode**: Need mode toggle for digit entry
|
||||||
|
3. **Validation**: Real-time conflict detection
|
||||||
|
4. **Win condition**: Complete + valid vs reach exit
|
||||||
|
5. **No animation**: Cursor moves instantly (no multi-step)
|
||||||
|
6. **Board generation**: On-demand vs one-time initialization
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
- [ ] All functionality from src_old preserved
|
||||||
|
- [ ] Follows unified API architecture
|
||||||
|
- [ ] Types match vim-maze patterns
|
||||||
|
- [ ] Hook composition mirrors vim-maze
|
||||||
|
- [ ] Example app works identically to src_old version
|
||||||
|
- [ ] Platform hook integration works
|
||||||
|
- [ ] Code is clean, documented, and maintainable
|
||||||
|
- [ ] UNIFIED_API.md updated with vim-sudoku section
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Keep game.css from src_old for styling
|
||||||
|
- Port sudoku generation algorithm directly (working code)
|
||||||
|
- Maintain existing vim keybindings (h/j/k/l, ^/$, g/G, ., i/r/x)
|
||||||
|
- Preserve flash animations for visual feedback
|
||||||
|
- Follow AGENTS.md conventions throughout
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
# 📋 npm Publishing Checklist for @vimazing/vim-sudoku
|
|
||||||
|
|
||||||
## 🔴 Critical (Must Fix Before Publishing)
|
|
||||||
|
|
||||||
- [ ] **Fix TypeScript build errors** - Package won't compile
|
|
||||||
- [ ] `Sudoku.ts`: operator type issues (lines 77, 93, 95)
|
|
||||||
- [ ] `useBoardState.ts`: remove unused `useCallback`, `useMemo` imports
|
|
||||||
- [ ] `types.ts`: remove non-existent `BoardString` export
|
|
||||||
- [ ] Remove unused variables: `idx`, `max_square`
|
|
||||||
|
|
||||||
- [ ] **Add LICENSE file** - Required for MIT license in package.json
|
|
||||||
|
|
||||||
- [ ] **Update package.json author** - Currently `you@example.com`
|
|
||||||
|
|
||||||
## 🟡 Important (Should Fix)
|
|
||||||
|
|
||||||
- [ ] **Update README.md**
|
|
||||||
- [ ] Remove "coming soon" for win detection (✅ implemented!)
|
|
||||||
- [ ] Document new `validateMoves` option in `useGame`
|
|
||||||
- [ ] Update scoring formula to include move penalty
|
|
||||||
|
|
||||||
- [ ] **Fix tsconfig.json** - Conflicting `declaration` and `emitDeclarationOnly` flags
|
|
||||||
|
|
||||||
- [ ] **Verify exports** - Check `src/index.ts` exports everything needed
|
|
||||||
|
|
||||||
- [ ] **Add CHANGELOG.md** - Document version history and changes
|
|
||||||
|
|
||||||
- [ ] **Add package.json keywords** - Improve npm discoverability (sudoku, vim, react, game, puzzle)
|
|
||||||
|
|
||||||
- [ ] **Update .npmignore** - Verify `example/` is excluded
|
|
||||||
|
|
||||||
## 🟢 Nice to Have
|
|
||||||
|
|
||||||
- [ ] **Test locally** - Run `npm pack` and inspect tarball contents
|
|
||||||
|
|
||||||
- [ ] **Add npm badges** - Version and downloads badges in README
|
|
||||||
|
|
||||||
- [ ] **Add engines field** - Specify Node.js version requirements
|
|
||||||
|
|
||||||
- [ ] **GitHub topics** - Add repository tags for discoverability
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Total: 18 tasks** (6 critical, 6 important, 6 nice-to-have)
|
|
||||||
|
|
||||||
**Status:** Ready to start working on critical issues 🚀
|
|
||||||
+49
-9
@@ -1,17 +1,57 @@
|
|||||||
import { useGame } from "../../src/useGame";
|
import { useGame } from "../../src/useGame";
|
||||||
import { useKeyBindings } from './useKeyBindings';
|
import { useKeyBindings } from "./useKeyBindings";
|
||||||
import "../../src/game.css";
|
import '../../src/game.css';
|
||||||
|
|
||||||
export default function App() {
|
function App() {
|
||||||
const { containerRef } = useGame({}, useKeyBindings);
|
const gameManager = useGame({ difficulty: 'medium' }, useKeyBindings);
|
||||||
|
const { containerRef, gameStatus, scoreManager } = gameManager;
|
||||||
|
const { timeValue, keystrokes, finalScore } = scoreManager;
|
||||||
|
|
||||||
|
const formatTime = (ms: number) => {
|
||||||
|
const seconds = Math.floor(ms / 1000);
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const secs = seconds % 60;
|
||||||
|
return `${minutes}:${secs.toString().padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: 16 }}>
|
<div className="flex flex-col items-center justify-around min-h-[100vh]">
|
||||||
<h1>VIMazing Sudoku</h1>
|
<h1 className="text-6xl">VIMazing vim-sudoku example</h1>
|
||||||
<div className="relative w-fit mx-auto">
|
<div className="mx-auto my-4 w-fit bg-muted rounded-lg p-4">
|
||||||
<div ref={containerRef} />
|
{(gameStatus === "started" || gameStatus === "game-won") && (
|
||||||
|
<div className="flex gap-8 text-sm font-mono items-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-muted-foreground">Time</div>
|
||||||
|
<div className="text-lg font-bold">{formatTime(timeValue)}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-muted-foreground">Keystrokes</div>
|
||||||
|
<div className="text-lg font-bold">{keystrokes}</div>
|
||||||
|
</div>
|
||||||
|
{gameStatus === "game-won" && finalScore && (
|
||||||
|
<>
|
||||||
|
<div className="w-px h-12 bg-muted-foreground/20" />
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-muted-foreground">Final Score</div>
|
||||||
|
<div className="text-3xl font-bold bg-gradient-to-r from-green-400 to-emerald-500 bg-clip-text text-transparent">{finalScore}</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="relative mx-auto my-4 w-fit" id="sudoku_container">
|
||||||
|
<div ref={containerRef} className="relative" />
|
||||||
|
</div>
|
||||||
|
<div className="text-center text-2xl text-muted-foreground">
|
||||||
|
{gameStatus === "waiting" && <p>Press <kbd className="px-2 py-1 bg-muted rounded">space</kbd> to start</p>}
|
||||||
|
{gameStatus === "started" && <p>Use <kbd className="px-2 py-1 bg-muted rounded">hjkl</kbd> to navigate • Press <kbd className="px-2 py-1 bg-muted rounded">i</kbd> to edit • Press <kbd className="px-2 py-1 bg-muted rounded">q</kbd> to quit</p>}
|
||||||
|
{gameStatus === "game-over" && <p>Game Over! Press <kbd className="px-2 py-1 bg-muted rounded">space</kbd> to restart</p>}
|
||||||
|
{gameStatus === "game-won" && <p>You Won! Press <kbd className="px-2 py-1 bg-muted rounded">space</kbd> to play again</p>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import * as React from "react";
|
|
||||||
|
|
||||||
const MOBILE_BREAKPOINT = 768;
|
|
||||||
|
|
||||||
export function useIsMobile() {
|
|
||||||
const [isMobile, setIsMobile] = React.useState<boolean>(
|
|
||||||
typeof window !== "undefined"
|
|
||||||
? window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`).matches
|
|
||||||
: false
|
|
||||||
);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
|
||||||
|
|
||||||
const onChange = (event: MediaQueryListEvent) => {
|
|
||||||
setIsMobile(event.matches);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set initial value
|
|
||||||
setIsMobile(mql.matches);
|
|
||||||
|
|
||||||
// Add listener
|
|
||||||
mql.addEventListener("change", onChange);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
return () => mql.removeEventListener("change", onChange);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return isMobile;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user