- 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
15 KiB
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
-
Given a user is viewing notes in the masonry grid,
-
When the user drags a note to reorder it,
-
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
-
Given the user is viewing notes on different screen sizes,
-
When the browser window is resized,
-
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
-
Given notes have different sizes (small, medium, large),
-
When the grid is rendered,
-
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
- Analyze current implementation
- Review Muuri configuration in masonry-grid.tsx
- Check note size handling (small, medium, large)
- Identify drag & drop issues
- Identify responsive layout issues
- Research best practices
- Study Google Keep's masonry layout behavior
- Research Muuri layout options and responsive configuration
- Document optimal settings for responsive masonry grids
- Create detailed fix plan
- Document all issues found
- Create step-by-step correction plan
- Define responsive breakpoints
- 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:
-
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
-
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
-
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
.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)
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:
.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:
// 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:
// 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:
/* 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:
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:
// 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:
-
Mobile (< 640px)
- 1 column layout
- Drag handle visible
- Notes stack vertically
- Touch interaction works
-
Tablet (640px - 1024px)
- 2 column layout
- Desktop drag behavior
- Notes align in columns
-
Desktop (1024px - 1280px)
- 3 column layout
- Smooth drag and drop
- Responsive to window resize
-
Large Desktop (> 1280px)
- 4 column layout
- Optimal use of space
- No layout issues
Files to Create
keep-notes/config/masonry-layout.ts- Layout configurationkeep-notes/components/masonry-grid.css- Masonry-specific styles
Files to Modify
keep-notes/components/masonry-grid.tsx- Update Muuri configuration and add resize handlerkeep-notes/components/note-card.tsx- Add data-size attributekeep-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:
- Muuri configuration lacks responsive column management
- No resize handler to update layout on window resize
- Note sizes (small, medium, large) are not visually applied via CSS
- Drag & drop feedback could be improved
- 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
- Analyze current implementation
- Research best practices
- Create detailed fix plan
- Implement fixes
- Test and validate
Agent Model Used
claude-sonnet-4.5-20250929
Completion Notes List
- Analyzed Muuri configuration in masonry-grid.tsx
- Reviewed note size handling (small, medium, large)
- Identified drag & drop issues
- Identified responsive layout issues
- Studied Google Keep's masonry layout behavior
- Researched Muuri layout options and responsive configuration
- Documented optimal settings for responsive masonry grids
- Created comprehensive fix plan with step-by-step instructions
- Defined responsive breakpoints
- 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.tskeep-notes/components/masonry-grid.css
Files to Modify:
keep-notes/components/masonry-grid.tsxkeep-notes/components/note-card.tsxkeep-notes/app/globals.css(optional, depending on CSS organization)