summaryrefslogtreecommitdiff
path: root/shared/components/src/utils/getMediaConditions.ts
diff options
context:
space:
mode:
authorrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
committerrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
commitbce557cc2dc767628bed6aac87301a1be7c5431b (patch)
treeb51a051228d01fe3306cd7626d4a96768aadb944 /shared/components/src/utils/getMediaConditions.ts
init commit
Diffstat (limited to 'shared/components/src/utils/getMediaConditions.ts')
-rw-r--r--shared/components/src/utils/getMediaConditions.ts117
1 files changed, 117 insertions, 0 deletions
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<T extends string | number | symbol = Size> = {
+ [key in T]?: string;
+};
+
+type BasicBreapoints<T extends string | number | symbol> = Record<T, number>;
+
+type BreakpointOptions = { offset?: number };
+
+// eslint-disable-next-line import/prefer-default-export
+export function getMediaConditions<T extends string | number | symbol = Size>(
+ breakpoints: Breakpoints<T>,
+ options?: BreakpointOptions,
+): MediaConditions<T> {
+ 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<T>(breakpoints, viewportSizes, offset);
+}
+
+function viewportSizeToMediaConditions<T extends string | number | symbol>(
+ breakpoints: Breakpoints<T>,
+ viewportSizes?: T[],
+ offset?: number,
+): MediaConditions<T> {
+ viewportSizes ||= Object.keys(breakpoints) as T[];
+ const queries: MediaConditions<T> = {};
+ 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<T extends string>(
+ breakpoints: BasicBreapoints<T>,
+): MediaConditions<T> {
+ const entries = Object.entries(breakpoints) as [T, number][];
+ entries.sort(([, a], [_, b]) => a - b);
+ const transformedBreakpoints: Breakpoints<T> = {};
+
+ 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<T>(
+ transformedBreakpoints,
+ viewportSizes,
+ 0,
+ );
+}