Keep/keep-notes/config/masonry-layout.ts
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

148 lines
4.2 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: 10, // Tighter gap closer to Google Keep
gutter: 10,
};
/**
* 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 {
const { gap } = DEFAULT_LAYOUT;
return (containerWidth - (columns - 1) * gap) / 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;
}