Keep/keep-notes/config/masonry-layout.ts
sepehr d59ec592eb fix(grid): repair muuri drag&drop and visual styles
- Fix Muuri integration: add data-draggable and improve DOM sync
- Fix Drag Visuals: remove opacity/rotation/scale in NoteCard and CSS to prevent 'gray/crooked' look
- Feat(layout): switch to padding-based spacing strategy (16px gap)
- Fix(css): correct media queries to maintain consistent spacing
2026-01-24 17:53:40 +01:00

149 lines
4.3 KiB
TypeScript

/**
* Masonry Layout Configuration
*
* Configuration for responsive masonry grid layout similar to Google Keep
* Defines breakpoints, columns, and note sizes for different screen sizes
*/
export interface MasonryLayoutConfig {
breakpoints: {
mobile: number; // < 480px
smallTablet: number; // 480px - 768px
tablet: number; // 768px - 1024px
desktop: number; // 1024px - 1280px
largeDesktop: number; // 1280px - 1600px
extraLarge: number; // > 1600px
};
columns: {
mobile: number;
smallTablet: number;
tablet: number;
desktop: number;
largeDesktop: number;
extraLarge: number;
};
noteSizes: {
small: { minHeight: number; width: number };
medium: { minHeight: number; width: number };
large: { minHeight: number; width: number };
};
gap: number;
gutter: number;
}
/**
* Default layout configuration based on Google Keep's behavior
*
* Responsive breakpoints:
* - Mobile (< 480px): 1 column
* - Small Tablet (480px - 768px): 2 columns
* - Tablet (768px - 1024px): 2 columns
* - Desktop (1024px - 1280px): 3 columns
* - Large Desktop (1280px - 1600px): 4 columns
* - Extra Large Desktop (> 1600px): 5 columns
*
* Note sizes:
* - Small: Compact cards (150px min height)
* - Medium: Standard cards (200px min height)
* - Large: Expanded cards (300px min height)
*/
export const DEFAULT_LAYOUT: MasonryLayoutConfig = {
breakpoints: {
mobile: 480,
smallTablet: 768,
tablet: 1024,
desktop: 1280,
largeDesktop: 1600,
extraLarge: 1920,
},
columns: {
mobile: 1,
smallTablet: 2,
tablet: 2,
desktop: 3,
largeDesktop: 4,
extraLarge: 5, // This is just a fallback, calculation is dynamic now
},
noteSizes: {
small: { minHeight: 150, width: 210 }, // Narrower for better density
medium: { minHeight: 200, width: 240 },
large: { minHeight: 300, width: 240 },
},
gap: 24, // Further increased horizontal gap
gutter: 24,
};
/**
* Calculate the number of columns based on container width
*
* @param width - Container width in pixels
* @returns Number of columns to use
*/
export function calculateColumns(width: number): number {
const { noteSizes, gap, breakpoints } = DEFAULT_LAYOUT;
// Use small note width (240px) as minimum column width basis
const minColumnWidth = noteSizes.small.width;
// For very small screens (mobile), force 1 column
if (width < breakpoints.mobile) return 1;
// For larger screens, calculate max columns that fit
// Formula: (Width + Gap) / (ColumnWidth + Gap)
const columns = Math.floor((width + gap) / (minColumnWidth + gap));
// Ensure at least 1 column
return Math.max(1, columns);
}
/**
* Calculate item width based on container width and number of columns
*
* @param containerWidth - Total container width in pixels
* @param columns - Number of columns to use
* @returns Item width in pixels
*/
export function calculateItemWidth(containerWidth: number, columns: number): number {
// Return full column width
// Gaps are now handled by padding inside the masonry-item CSS
return containerWidth / columns;
}
/**
* Get note size dimensions (height and width) based on size type
*
* @param size - Note size ('small' | 'medium' | 'large')
* @returns Note dimensions in pixels
*/
export function getNoteSizeDimensions(size: 'small' | 'medium' | 'large') {
return DEFAULT_LAYOUT.noteSizes[size];
}
/**
* Check if current viewport is mobile
*
* @returns true if viewport width is less than mobile breakpoint
*/
export function isMobileViewport(): boolean {
return typeof window !== 'undefined' && window.innerWidth < DEFAULT_LAYOUT.breakpoints.mobile;
}
/**
* Check if current viewport is tablet
*
* @returns true if viewport width is between mobile and desktop breakpoints
*/
export function isTabletViewport(): boolean {
if (typeof window === 'undefined') return false;
const width = window.innerWidth;
return width >= DEFAULT_LAYOUT.breakpoints.mobile && width < DEFAULT_LAYOUT.breakpoints.tablet;
}
/**
* Check if current viewport is desktop
*
* @returns true if viewport width is greater than or equal to tablet breakpoint
*/
export function isDesktopViewport(): boolean {
return typeof window !== 'undefined' && window.innerWidth >= DEFAULT_LAYOUT.breakpoints.tablet;
}