- 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
509 lines
15 KiB
Markdown
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)
|