From bce557cc2dc767628bed6aac87301a1be7c5431b Mon Sep 17 00:00:00 2001 From: rxliuli Date: Tue, 4 Nov 2025 05:03:50 +0800 Subject: init commit --- shared/components/src/utils/getMediaConditions.ts | 117 ++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 shared/components/src/utils/getMediaConditions.ts (limited to 'shared/components/src/utils/getMediaConditions.ts') diff --git a/shared/components/src/utils/getMediaConditions.ts b/shared/components/src/utils/getMediaConditions.ts new file mode 100644 index 0000000..2d5028b --- /dev/null +++ b/shared/components/src/utils/getMediaConditions.ts @@ -0,0 +1,117 @@ +import type { Breakpoints, Size } from '@amp/web-app-components/src/types'; + +export type MediaConditions = { + [key in T]?: string; +}; + +type BasicBreapoints = Record; + +type BreakpointOptions = { offset?: number }; + +// eslint-disable-next-line import/prefer-default-export +export function getMediaConditions( + breakpoints: Breakpoints, + options?: BreakpointOptions, +): MediaConditions { + const viewportOrder = { + xsmall: 0, + small: 1, + medium: 2, + large: 3, + xlarge: 4, + }; + + const offset = options?.offset ?? 0; + const viewportSizes = Object.keys(breakpoints).sort( + (a, b) => viewportOrder[a] - viewportOrder[b], + ) as T[]; + + return viewportSizeToMediaConditions(breakpoints, viewportSizes, offset); +} + +function viewportSizeToMediaConditions( + breakpoints: Breakpoints, + viewportSizes?: T[], + offset?: number, +): MediaConditions { + viewportSizes ||= Object.keys(breakpoints) as T[]; + const queries: MediaConditions = {}; + viewportSizes.reduce((acc, viewport) => { + const { min, max } = { + min: undefined, + max: undefined, + ...breakpoints[viewport], + }; + + if (min && !max) { + acc[viewport] = `(min-width:${min + offset}px)`; + } else if (!min && max) { + acc[viewport] = `(max-width:${max + offset}px)`; + } else if (min && max) { + acc[viewport] = `(min-width:${min + offset}px) and (max-width:${ + max + offset + }px)`; + } + return acc; + }, queries); + return queries; +} + +/** + * Transforms a breakpoints object into media queries that match ranges between each breakpoint and the next. + * + * @param breakpoints - Object with breakpoint names as keys and pixel values as values + * @returns Object with breakpoint names as keys and media query strings as values + * + * @example + * const breakpoints = { XSM: 0, SM: 350, MD: 484, LG: 1000 }; + * const mediaQueries = breakpointsToMediaQueries(breakpoints); + * // Returns: + * // { + * // XSM: '(max-width: 349px)', + * // SM: '(min-width: 350px) and (max-width: 483px)', + * // MD: '(min-width: 484px) and (max-width: 999px)', + * // LG: '(min-width: 1000px)' + * // } + */ +export function breakpointsToMediaQueries( + breakpoints: BasicBreapoints, +): MediaConditions { + const entries = Object.entries(breakpoints) as [T, number][]; + entries.sort(([, a], [_, b]) => a - b); + const transformedBreakpoints: Breakpoints = {}; + + entries.forEach(([breakpointName, minWidth], index) => { + const isFirst = index === 0; + const isLast = index === entries.length - 1; + const nextBreakpointWidth = isLast ? null : entries[index + 1][1]; + + if (isFirst && minWidth === 0) { + // First breakpoint starting at 0: only max-width + if (nextBreakpointWidth !== null) { + transformedBreakpoints[breakpointName] = { + max: nextBreakpointWidth - 1, + }; + } else { + // Edge case: only one breakpoint starting at 0 + transformedBreakpoints[breakpointName] = { min: 0 }; + } + } else if (isLast) { + // Last breakpoint: only min-width + transformedBreakpoints[breakpointName] = { min: minWidth }; + } else { + // Middle breakpoints: min-width and max-width range + transformedBreakpoints[breakpointName] = { + min: minWidth, + max: nextBreakpointWidth! - 1, + }; + } + }); + + const viewportSizes = entries.map(([breakpointName]) => breakpointName); + return viewportSizeToMediaConditions( + transformedBreakpoints, + viewportSizes, + 0, + ); +} -- cgit v1.2.3