/** * 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; }