Keep/_bmad-output/planning-artifacts/12-1-fix-masonry-drag-and-drop.md
sepehr ddb67ba9e5 fix: unify theme system - fix theme switching persistence
- Unified localStorage key to 'theme-preference' across all components
- Fixed header.tsx using wrong localStorage key ('theme' instead of 'theme-preference')
- Added localStorage hybrid persistence for instant theme changes
- Removed router.refresh() which was causing stale data revert
- Replaced Blue theme with Sepia
- Consolidated auth() calls to prevent race conditions
- Updated UserSettingsData types to include all themes
2026-01-18 22:33:41 +01:00

509 lines
15 KiB
Markdown

# Story 12.1: Fix Masonry Grid Drag & Drop and Responsive Layout
Status: planning
## Story
As a **user**,
I want **a responsive masonry grid where notes can be easily dragged and dropped while maintaining their sizes**,
so that **I can organize my notes efficiently on any screen size, similar to Google Keep**.
## Acceptance Criteria
1. **Given** a user is viewing notes in the masonry grid,
2. **When** the user drags a note to reorder it,
3. **Then** the system should:
- Allow smooth drag and drop of notes without losing their positions
- Maintain the exact size (small, medium, large) of each note during drag and after drop
- Provide visual feedback during drag (opacity change, placeholder)
- Save the new order to the database
- Work seamlessly on both desktop and mobile devices
4. **Given** the user is viewing notes on different screen sizes,
5. **When** the browser window is resized,
6. **Then** the system should:
- Automatically adjust the number of columns to fit the available width
- Display more columns on larger screens (e.g., 2-4 columns on desktop)
- Display fewer columns on smaller screens (e.g., 1-2 columns on mobile)
- Maintain the masonry layout where items fill available vertical space
- Not break the layout or cause overlapping items
7. **Given** notes have different sizes (small, medium, large),
8. **When** the grid is rendered,
9. **Then** the system should:
- Respect the size property of each note (small, medium, large)
- Display small notes as compact cards
- Display medium notes as standard cards
- Display large notes as expanded cards
- Arrange items in a true masonry pattern (no gaps, items stack vertically)
## Tasks / Subtasks
- [x] Analyze current implementation
- [x] Review Muuri configuration in masonry-grid.tsx
- [x] Check note size handling (small, medium, large)
- [x] Identify drag & drop issues
- [x] Identify responsive layout issues
- [x] Research best practices
- [x] Study Google Keep's masonry layout behavior
- [x] Research Muuri layout options and responsive configuration
- [x] Document optimal settings for responsive masonry grids
- [x] Create detailed fix plan
- [x] Document all issues found
- [x] Create step-by-step correction plan
- [x] Define responsive breakpoints
- [x] Define note size dimensions
- [ ] Implement fixes
- [ ] Fix responsive layout configuration
- [ ] Fix drag & drop behavior
- [ ] Ensure note sizes are properly applied
- [ ] Test on multiple screen sizes
- [ ] Testing and validation
- [ ] Test drag & drop on desktop
- [ ] Test drag & drop on mobile
- [ ] Test responsive behavior
- [ ] Verify note sizes are maintained
- [ ] Verify layout matches Google Keep behavior
## Dev Notes
### Problem Analysis
**Current Implementation:**
- Using Muuri library for masonry grid layout
- Notes have size property: 'small' | 'medium' | 'large'
- Layout options include drag settings but not optimized for responsiveness
- Grid uses absolute positioning with width: 100% but no column count management
**Issues Identified:**
1. **Responsive Layout Issues:**
- No defined column counts for different screen sizes
- Grid doesn't adjust number of columns when window resizes
- Items may overlap or leave gaps
- Layout breaks on mobile devices
2. **Drag & Drop Issues:**
- Items may not maintain their positions during drag
- Visual feedback is minimal
- Drag handle only visible on mobile, but desktop dragging may interfere with content interaction
- Auto-scroll settings may not be optimal
3. **Note Size Issues:**
- Note sizes (small, medium, large) are defined but may not be applied correctly to CSS
- No visual distinction between sizes
- Size changes during drag may cause layout shifts
### Google Keep Reference Behavior
**Google Keep Layout Characteristics:**
- Fixed card width (e.g., 240px on desktop, variable on mobile)
- Height varies based on content + size setting
- Responsive columns:
- Mobile (320px-480px): 1 column
- Tablet (481px-768px): 2 columns
- Desktop (769px-1200px): 3-4 columns
- Large Desktop (1201px+): 4-5 columns
- Cards have rounded corners, shadow on hover
- Smooth animations for drag and resize
**Google Keep Drag & Drop:**
- Entire card is draggable on desktop
- Long press to drag on mobile
- Visual feedback: opacity reduction, shadow increase
- Placeholder shows drop position
- Auto-scroll when dragging near edges
- Items reorder smoothly with animation
### Solution Architecture
**Responsive Layout Strategy:**
Option 1: CSS Grid + Muuri for Drag/Drop
```css
.masonry-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 16px;
}
```
- Pros: Native CSS responsive behavior
- Cons: Muuri may conflict with CSS Grid positioning
Option 2: Muuri with Responsive Configuration (RECOMMENDED)
```javascript
const getColumns = (width) => {
if (width < 640) return 1;
if (width < 1024) return 2;
if (width < 1280) return 3;
return 4;
};
```
- Pros: Muuri handles all positioning and drag/drop
- Cons: Requires JavaScript to update on resize
**Drag & Drop Improvements:**
- Improve visual feedback during drag
- Optimize auto-scroll speed
- Add transition animations
- Ensure mobile touch support
**Note Size Implementation:**
```css
.note-card[data-size="small"] {
min-height: 150px;
}
.note-card[data-size="medium"] {
min-height: 200px;
}
.note-card[data-size="large"] {
min-height: 300px;
}
```
### Implementation Plan
#### Step 1: Define Responsive Breakpoints and Dimensions
Create a configuration file for layout settings:
```typescript
// keep-notes/config/masonry-layout.ts
export interface MasonryLayoutConfig {
breakpoints: {
mobile: number; // < 640px
tablet: number; // 640px - 1024px
desktop: number; // 1024px - 1280px
largeDesktop: number; // > 1280px
};
columns: {
mobile: number;
tablet: number;
desktop: number;
largeDesktop: number;
};
noteSizes: {
small: { minHeight: number; width: number };
medium: { minHeight: number; width: number };
large: { minHeight: number; width: number };
};
gap: number;
gutter: number;
}
export const DEFAULT_LAYOUT: MasonryLayoutConfig = {
breakpoints: {
mobile: 640,
tablet: 1024,
desktop: 1280,
largeDesktop: 1920,
},
columns: {
mobile: 1,
tablet: 2,
desktop: 3,
largeDesktop: 4,
},
noteSizes: {
small: { minHeight: 150, width: 240 },
medium: { minHeight: 200, width: 240 },
large: { minHeight: 300, width: 240 },
},
gap: 16,
gutter: 16,
};
```
#### Step 2: Update Muuri Configuration
Modify `masonry-grid.tsx` to use responsive configuration:
```typescript
// Dynamic column calculation based on window width
const getLayoutOptions = (containerWidth: number) => {
const columns = calculateColumns(containerWidth);
const itemWidth = (containerWidth - (columns - 1) * DEFAULT_LAYOUT.gap) / columns;
return {
dragEnabled: true,
dragHandle: isMobile ? '.muuri-drag-handle' : undefined,
dragContainer: document.body,
dragStartPredicate: {
distance: 10,
delay: 0,
},
dragPlaceholder: {
enabled: true,
createElement: (item: any) => {
const el = item.getElement().cloneNode(true);
el.style.opacity = '0.4';
el.style.transform = 'scale(1.05)';
return el;
},
},
dragAutoScroll: {
targets: [window],
speed: (item: any, target: any, intersection: any) => {
return intersection * 30; // Faster auto-scroll
},
threshold: 50, // Start auto-scroll earlier
smoothStop: true,
},
layoutDuration: 300,
layoutEasing: 'cubic-bezier(0.25, 1, 0.5, 1)',
fillGaps: true,
horizontal: false,
alignRight: false,
alignBottom: false,
rounding: false,
};
};
// Calculate columns based on container width
const calculateColumns = (width: number) => {
if (width < DEFAULT_LAYOUT.breakpoints.mobile) return DEFAULT_LAYOUT.columns.mobile;
if (width < DEFAULT_LAYOUT.breakpoints.tablet) return DEFAULT_LAYOUT.columns.tablet;
if (width < DEFAULT_LAYOUT.breakpoints.desktop) return DEFAULT_LAYOUT.columns.desktop;
return DEFAULT_LAYOUT.columns.largeDesktop;
};
```
#### Step 3: Apply Note Sizes with CSS
Add CSS classes for different note sizes:
```css
/* keep-notes/components/masonry-grid.css */
.masonry-item-content .note-card[data-size="small"] {
min-height: 150px;
}
.masonry-item-content .note-card[data-size="medium"] {
min-height: 200px;
}
.masonry-item-content .note-card[data-size="large"] {
min-height: 300px;
}
/* Responsive adjustments */
@media (max-width: 640px) {
.masonry-item-content .note-card {
width: 100%;
}
.masonry-item-content .note-card[data-size="small"] {
min-height: 120px;
}
.masonry-item-content .note-card[data-size="medium"] {
min-height: 160px;
}
.masonry-item-content .note-card[data-size="large"] {
min-height: 240px;
}
}
/* Drag state improvements */
.masonry-item.muuri-item-dragging .note-card {
transform: scale(1.02);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
}
.masonry-item.muuri-item-releasing .note-card {
transition: transform 0.2s ease-out, box-shadow 0.2s ease-out;
}
```
#### Step 4: Add Resize Handler for Responsive Updates
Add resize listener to update layout when window size changes:
```typescript
useEffect(() => {
const handleResize = () => {
if (!pinnedMuuri.current || !othersMuuri.current) return;
const containerWidth = window.innerWidth - 32; // Subtract padding
const columns = calculateColumns(containerWidth);
// Update Muuri settings
[pinnedMuuri.current, othersMuuri.current].forEach(grid => {
if (grid) {
grid.refreshItems().layout();
}
});
};
const debouncedResize = debounce(handleResize, 150);
window.addEventListener('resize', debouncedResize);
return () => {
window.removeEventListener('resize', debouncedResize);
};
}, []);
```
#### Step 5: Update NoteCard to Display Size Attribute
Ensure NoteCard component renders with data-size attribute:
```typescript
// In NoteCard component
<Card
data-testid="note-card"
data-note-id={note.id}
data-size={note.size} // Add this
// ... other props
>
```
#### Step 6: Test on Multiple Devices
**Test Matrix:**
1. **Mobile (< 640px)**
- 1 column layout
- Drag handle visible
- Notes stack vertically
- Touch interaction works
2. **Tablet (640px - 1024px)**
- 2 column layout
- Desktop drag behavior
- Notes align in columns
3. **Desktop (1024px - 1280px)**
- 3 column layout
- Smooth drag and drop
- Responsive to window resize
4. **Large Desktop (> 1280px)**
- 4 column layout
- Optimal use of space
- No layout issues
### Files to Create
- `keep-notes/config/masonry-layout.ts` - Layout configuration
- `keep-notes/components/masonry-grid.css` - Masonry-specific styles
### Files to Modify
- `keep-notes/components/masonry-grid.tsx` - Update Muuri configuration and add resize handler
- `keep-notes/components/note-card.tsx` - Add data-size attribute
- `keep-notes/app/globals.css` - Add note size styles if not in separate CSS file
### Testing Checklist
**Responsive Behavior:**
- [ ] Layout adjusts columns when resizing window
- [ ] No items overlap or create gaps
- [ ] Mobile shows 1 column
- [ ] Tablet shows 2 columns
- [ ] Desktop shows 3-4 columns
- [ ] Layout matches Google Keep behavior
**Drag & Drop Behavior:**
- [ ] Notes can be dragged smoothly
- [ ] Visual feedback during drag (opacity, shadow)
- [ ] Placeholder shows drop position
- [ ] Auto-scroll works when dragging near edges
- [ ] Order is saved after drop
- [ ] Notes maintain their positions
- [ ] Works on both desktop and mobile
**Note Sizes:**
- [ ] Small notes display compactly
- [ ] Medium notes display with standard height
- [ ] Large notes display with expanded height
- [ ] Sizes are maintained during drag
- [ ] Sizes persist after drop
- [ ] Size changes update layout correctly
**Cross-Browser:**
- [ ] Chrome: Works correctly
- [ ] Firefox: Works correctly
- [ ] Safari: Works correctly
- [ ] Edge: Works correctly
### Performance Considerations
- Debounce resize events to avoid excessive re-layouts
- Use requestAnimationFrame for smooth animations
- Avoid re-initializing Muuri on resize, use refreshItems() instead
- Optimize drag placeholder creation to avoid expensive DOM operations
### Accessibility Considerations
- Ensure drag handles are keyboard accessible
- Add ARIA attributes for drag state
- Provide visual feedback for screen readers
- Maintain focus management during drag
### References
- **Muuri Documentation:** https://github.com/haltu/muuri
- **Google Keep UI Reference:** https://keep.google.com
- **CSS Masonry Layout:** https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Masonry_Layout
- **Responsive Design Patterns:** https://www.smashingmagazine.com/2018/05/learning-layouts-with-css-grid/
## Dev Agent Record
### Initial Analysis (2026-01-18)
**Problems Identified:**
1. Muuri configuration lacks responsive column management
2. No resize handler to update layout on window resize
3. Note sizes (small, medium, large) are not visually applied via CSS
4. Drag & drop feedback could be improved
5. Mobile drag handle optimization needed
**Solution Approach:**
- Implement responsive column calculation based on window width
- Add resize listener with debounce to update layout
- Apply note sizes via CSS data attributes
- Improve drag & drop visual feedback
- Test thoroughly on multiple devices
### Implementation Progress
- [x] Analyze current implementation
- [x] Research best practices
- [x] Create detailed fix plan
- [ ] Implement fixes
- [ ] Test and validate
### Agent Model Used
claude-sonnet-4.5-20250929
### Completion Notes List
- [x] Analyzed Muuri configuration in masonry-grid.tsx
- [x] Reviewed note size handling (small, medium, large)
- [x] Identified drag & drop issues
- [x] Identified responsive layout issues
- [x] Studied Google Keep's masonry layout behavior
- [x] Researched Muuri layout options and responsive configuration
- [x] Documented optimal settings for responsive masonry grids
- [x] Created comprehensive fix plan with step-by-step instructions
- [x] Defined responsive breakpoints
- [x] Defined note size dimensions
- [ ] Fix responsive layout configuration
- [ ] Fix drag & drop behavior
- [ ] Ensure note sizes are properly applied
- [ ] Test on multiple screen sizes
### File List
**Files to Create:**
- `keep-notes/config/masonry-layout.ts`
- `keep-notes/components/masonry-grid.css`
**Files to Modify:**
- `keep-notes/components/masonry-grid.tsx`
- `keep-notes/components/note-card.tsx`
- `keep-notes/app/globals.css` (optional, depending on CSS organization)