486 lines
14 KiB
TypeScript
486 lines
14 KiB
TypeScript
|
|
/**
|
|||
|
|
* 响应式设计 Hook
|
|||
|
|
* 提供屏幕尺寸、断点、方向、平台检测等响应式信息
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import { useState, useEffect, useMemo, useCallback } from 'react';
|
|||
|
|
import { Dimensions, ScaledSize, Platform } from 'react-native';
|
|||
|
|
|
|||
|
|
// ==================== 断点定义 ====================
|
|||
|
|
export const BREAKPOINTS = {
|
|||
|
|
mobile: 0, // 手机
|
|||
|
|
tablet: 768, // 平板竖屏
|
|||
|
|
desktop: 1024, // 平板横屏/桌面
|
|||
|
|
wide: 1440, // 宽屏桌面
|
|||
|
|
} as const;
|
|||
|
|
|
|||
|
|
export type BreakpointKey = keyof typeof BREAKPOINTS;
|
|||
|
|
export type BreakpointValue = typeof BREAKPOINTS[BreakpointKey];
|
|||
|
|
|
|||
|
|
// ==================== 更细粒度的断点定义 ====================
|
|||
|
|
export const FINE_BREAKPOINTS = {
|
|||
|
|
xs: 0, // 超小屏手机
|
|||
|
|
sm: 375, // 小屏手机 (iPhone SE, 小屏安卓)
|
|||
|
|
md: 414, // 中屏手机 (iPhone Pro Max, 大屏安卓)
|
|||
|
|
lg: 768, // 平板竖屏 / 大折叠屏手机展开
|
|||
|
|
xl: 1024, // 平板横屏 / 小桌面
|
|||
|
|
'2xl': 1280, // 桌面
|
|||
|
|
'3xl': 1440, // 大桌面
|
|||
|
|
'4xl': 1920, // 超大屏
|
|||
|
|
} as const;
|
|||
|
|
|
|||
|
|
export type FineBreakpointKey = keyof typeof FINE_BREAKPOINTS;
|
|||
|
|
|
|||
|
|
// ==================== 响应式值类型 ====================
|
|||
|
|
export type ResponsiveValue<T> = T | Partial<Record<FineBreakpointKey, T>>;
|
|||
|
|
|
|||
|
|
// ==================== 返回值类型 ====================
|
|||
|
|
export interface ResponsiveInfo {
|
|||
|
|
// 基础尺寸
|
|||
|
|
width: number;
|
|||
|
|
height: number;
|
|||
|
|
|
|||
|
|
// 基础断点
|
|||
|
|
breakpoint: BreakpointKey;
|
|||
|
|
isMobile: boolean;
|
|||
|
|
isTablet: boolean;
|
|||
|
|
isDesktop: boolean;
|
|||
|
|
isWide: boolean;
|
|||
|
|
isWideScreen: boolean;
|
|||
|
|
|
|||
|
|
// 细粒度断点
|
|||
|
|
fineBreakpoint: FineBreakpointKey;
|
|||
|
|
isXS: boolean;
|
|||
|
|
isSM: boolean;
|
|||
|
|
isMD: boolean;
|
|||
|
|
isLG: boolean;
|
|||
|
|
isXL: boolean;
|
|||
|
|
is2XL: boolean;
|
|||
|
|
is3XL: boolean;
|
|||
|
|
is4XL: boolean;
|
|||
|
|
|
|||
|
|
// 方向
|
|||
|
|
orientation: 'portrait' | 'landscape';
|
|||
|
|
isPortrait: boolean;
|
|||
|
|
isLandscape: boolean;
|
|||
|
|
|
|||
|
|
// 平台检测
|
|||
|
|
platform: {
|
|||
|
|
OS: 'ios' | 'android' | 'windows' | 'macos' | 'web';
|
|||
|
|
isWeb: boolean;
|
|||
|
|
isIOS: boolean;
|
|||
|
|
isAndroid: boolean;
|
|||
|
|
isNative: boolean;
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 获取当前断点 ====================
|
|||
|
|
function getBreakpoint(width: number): BreakpointKey {
|
|||
|
|
if (width >= BREAKPOINTS.wide) {
|
|||
|
|
return 'wide';
|
|||
|
|
}
|
|||
|
|
if (width >= BREAKPOINTS.desktop) {
|
|||
|
|
return 'desktop';
|
|||
|
|
}
|
|||
|
|
if (width >= BREAKPOINTS.tablet) {
|
|||
|
|
return 'tablet';
|
|||
|
|
}
|
|||
|
|
return 'mobile';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 获取细粒度断点 ====================
|
|||
|
|
function getFineBreakpoint(width: number): FineBreakpointKey {
|
|||
|
|
if (width >= FINE_BREAKPOINTS['4xl']) return '4xl';
|
|||
|
|
if (width >= FINE_BREAKPOINTS['3xl']) return '3xl';
|
|||
|
|
if (width >= FINE_BREAKPOINTS['2xl']) return '2xl';
|
|||
|
|
if (width >= FINE_BREAKPOINTS.xl) return 'xl';
|
|||
|
|
if (width >= FINE_BREAKPOINTS.lg) return 'lg';
|
|||
|
|
if (width >= FINE_BREAKPOINTS.md) return 'md';
|
|||
|
|
if (width >= FINE_BREAKPOINTS.sm) return 'sm';
|
|||
|
|
return 'xs';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 获取屏幕方向 ====================
|
|||
|
|
function getOrientation(width: number, height: number): 'portrait' | 'landscape' {
|
|||
|
|
return width > height ? 'landscape' : 'portrait';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== useWindowDimensions Hook ====================
|
|||
|
|
/**
|
|||
|
|
* 获取窗口尺寸,支持屏幕旋转响应
|
|||
|
|
* 替代 Dimensions.get('window'),提供实时尺寸更新
|
|||
|
|
*/
|
|||
|
|
function useWindowDimensions(): ScaledSize {
|
|||
|
|
const [dimensions, setDimensions] = useState(() => Dimensions.get('window'));
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
const subscription = Dimensions.addEventListener('change', ({ window }) => {
|
|||
|
|
setDimensions(window);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return () => {
|
|||
|
|
subscription.remove();
|
|||
|
|
};
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
return dimensions;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== useResponsive Hook ====================
|
|||
|
|
/**
|
|||
|
|
* 响应式设计 Hook
|
|||
|
|
* 提供屏幕尺寸、断点、方向、平台检测等响应式信息
|
|||
|
|
*
|
|||
|
|
* @returns ResponsiveInfo 响应式信息对象
|
|||
|
|
*
|
|||
|
|
* @example
|
|||
|
|
* const {
|
|||
|
|
* width, height,
|
|||
|
|
* breakpoint, isMobile, isTablet, isDesktop, isWide,
|
|||
|
|
* fineBreakpoint, isXS, isSM, isMD, isLG, isXL,
|
|||
|
|
* orientation, isPortrait, isLandscape,
|
|||
|
|
* platform: { isWeb, isIOS, isAndroid }
|
|||
|
|
* } = useResponsive();
|
|||
|
|
*/
|
|||
|
|
export function useResponsive(): ResponsiveInfo {
|
|||
|
|
const windowDimensions = useWindowDimensions();
|
|||
|
|
const { width, height } = windowDimensions;
|
|||
|
|
|
|||
|
|
const breakpoint = getBreakpoint(width);
|
|||
|
|
const fineBreakpoint = getFineBreakpoint(width);
|
|||
|
|
const orientation = getOrientation(width, height);
|
|||
|
|
|
|||
|
|
const isMobile = breakpoint === 'mobile';
|
|||
|
|
const isTablet = breakpoint === 'tablet';
|
|||
|
|
const isDesktop = breakpoint === 'desktop';
|
|||
|
|
const isWide = breakpoint === 'wide';
|
|||
|
|
const isWideScreen = isTablet || isDesktop || isWide;
|
|||
|
|
|
|||
|
|
const isXS = fineBreakpoint === 'xs';
|
|||
|
|
const isSM = fineBreakpoint === 'sm';
|
|||
|
|
const isMD = fineBreakpoint === 'md';
|
|||
|
|
const isLG = fineBreakpoint === 'lg';
|
|||
|
|
const isXL = fineBreakpoint === 'xl';
|
|||
|
|
const is2XL = fineBreakpoint === '2xl';
|
|||
|
|
const is3XL = fineBreakpoint === '3xl';
|
|||
|
|
const is4XL = fineBreakpoint === '4xl';
|
|||
|
|
|
|||
|
|
const isPortrait = orientation === 'portrait';
|
|||
|
|
const isLandscape = orientation === 'landscape';
|
|||
|
|
|
|||
|
|
const platform = useMemo(() => ({
|
|||
|
|
OS: Platform.OS,
|
|||
|
|
isWeb: Platform.OS === 'web',
|
|||
|
|
isIOS: Platform.OS === 'ios',
|
|||
|
|
isAndroid: Platform.OS === 'android',
|
|||
|
|
isNative: Platform.OS !== 'web',
|
|||
|
|
}), []);
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
width,
|
|||
|
|
height,
|
|||
|
|
breakpoint,
|
|||
|
|
isMobile,
|
|||
|
|
isTablet,
|
|||
|
|
isDesktop,
|
|||
|
|
isWide,
|
|||
|
|
isWideScreen,
|
|||
|
|
fineBreakpoint,
|
|||
|
|
isXS,
|
|||
|
|
isSM,
|
|||
|
|
isMD,
|
|||
|
|
isLG,
|
|||
|
|
isXL,
|
|||
|
|
is2XL,
|
|||
|
|
is3XL,
|
|||
|
|
is4XL,
|
|||
|
|
orientation,
|
|||
|
|
isPortrait,
|
|||
|
|
isLandscape,
|
|||
|
|
platform,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 响应式值选择器 ====================
|
|||
|
|
/**
|
|||
|
|
* 根据当前断点从响应式值对象中选择合适的值
|
|||
|
|
*
|
|||
|
|
* @param value - 响应式值,可以是单一值或断点映射对象
|
|||
|
|
* @param currentBreakpoint - 当前细粒度断点
|
|||
|
|
* @returns 选中的值
|
|||
|
|
*
|
|||
|
|
* @example
|
|||
|
|
* const padding = useResponsiveValue({ xs: 8, md: 16, lg: 24 });
|
|||
|
|
* // 在 xs 屏幕返回 8,md 屏幕返回 16,lg 及以上返回 24
|
|||
|
|
*/
|
|||
|
|
export function useResponsiveValue<T>(value: ResponsiveValue<T>): T {
|
|||
|
|
const { fineBreakpoint } = useResponsive();
|
|||
|
|
|
|||
|
|
return useMemo(() => {
|
|||
|
|
// 如果不是对象,直接返回
|
|||
|
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|||
|
|
return value as T;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const breakpointOrder: FineBreakpointKey[] = ['4xl', '3xl', '2xl', 'xl', 'lg', 'md', 'sm', 'xs'];
|
|||
|
|
const valueMap = value as Partial<Record<FineBreakpointKey, T>>;
|
|||
|
|
|
|||
|
|
// 从当前断点开始向下查找
|
|||
|
|
const currentIndex = breakpointOrder.indexOf(fineBreakpoint);
|
|||
|
|
for (let i = currentIndex; i < breakpointOrder.length; i++) {
|
|||
|
|
const bp = breakpointOrder[i];
|
|||
|
|
if (bp in valueMap) {
|
|||
|
|
return valueMap[bp]!;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果没找到,返回 xs 的值或第一个值
|
|||
|
|
return (valueMap.xs ?? Object.values(valueMap)[0]) as T;
|
|||
|
|
}, [value, fineBreakpoint]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 响应式样式生成器 ====================
|
|||
|
|
/**
|
|||
|
|
* 根据断点生成响应式样式
|
|||
|
|
*
|
|||
|
|
* @param styles - 响应式样式对象
|
|||
|
|
* @returns 当前断点对应的样式
|
|||
|
|
*
|
|||
|
|
* @example
|
|||
|
|
* const containerStyle = useResponsiveStyle({
|
|||
|
|
* padding: { xs: 8, md: 16, lg: 24 },
|
|||
|
|
* fontSize: { xs: 14, lg: 16 }
|
|||
|
|
* });
|
|||
|
|
*/
|
|||
|
|
export function useResponsiveStyle<T extends Record<string, ResponsiveValue<unknown>>>(
|
|||
|
|
styles: T
|
|||
|
|
): { [K in keyof T]: T[K] extends ResponsiveValue<infer V> ? V : never } {
|
|||
|
|
const { fineBreakpoint } = useResponsive();
|
|||
|
|
|
|||
|
|
return useMemo(() => {
|
|||
|
|
const result = {} as { [K in keyof T]: T[K] extends ResponsiveValue<infer V> ? V : never };
|
|||
|
|
|
|||
|
|
for (const key in styles) {
|
|||
|
|
const value = styles[key];
|
|||
|
|
|
|||
|
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|||
|
|
(result as Record<string, unknown>)[key] = value;
|
|||
|
|
} else {
|
|||
|
|
const valueMap = value as Partial<Record<FineBreakpointKey, unknown>>;
|
|||
|
|
const breakpointOrder: FineBreakpointKey[] = ['4xl', '3xl', '2xl', 'xl', 'lg', 'md', 'sm', 'xs'];
|
|||
|
|
const currentIndex = breakpointOrder.indexOf(fineBreakpoint);
|
|||
|
|
|
|||
|
|
let selectedValue: unknown = undefined;
|
|||
|
|
for (let i = currentIndex; i < breakpointOrder.length; i++) {
|
|||
|
|
const bp = breakpointOrder[i];
|
|||
|
|
if (bp in valueMap) {
|
|||
|
|
selectedValue = valueMap[bp];
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
(result as Record<string, unknown>)[key] = selectedValue ?? valueMap.xs ?? Object.values(valueMap)[0];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
}, [styles, fineBreakpoint]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 断点比较工具 ====================
|
|||
|
|
/**
|
|||
|
|
* 检查当前断点是否大于等于目标断点
|
|||
|
|
*
|
|||
|
|
* @param current - 当前断点
|
|||
|
|
* @param target - 目标断点
|
|||
|
|
* @returns 是否满足条件
|
|||
|
|
*/
|
|||
|
|
export function isBreakpointGTE(
|
|||
|
|
current: FineBreakpointKey,
|
|||
|
|
target: FineBreakpointKey
|
|||
|
|
): boolean {
|
|||
|
|
const order: FineBreakpointKey[] = ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl', '4xl'];
|
|||
|
|
return order.indexOf(current) >= order.indexOf(target);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查当前断点是否小于目标断点
|
|||
|
|
*
|
|||
|
|
* @param current - 当前断点
|
|||
|
|
* @param target - 目标断点
|
|||
|
|
* @returns 是否满足条件
|
|||
|
|
*/
|
|||
|
|
export function isBreakpointLT(
|
|||
|
|
current: FineBreakpointKey,
|
|||
|
|
target: FineBreakpointKey
|
|||
|
|
): boolean {
|
|||
|
|
return !isBreakpointGTE(current, target);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 断点范围检查 Hook ====================
|
|||
|
|
/**
|
|||
|
|
* 检查当前是否在指定断点范围内
|
|||
|
|
*
|
|||
|
|
* @param options - 断点范围选项
|
|||
|
|
* @returns 是否在范围内
|
|||
|
|
*
|
|||
|
|
* @example
|
|||
|
|
* const isMediumUp = useBreakpointGTE('md');
|
|||
|
|
* const isMobileOnly = useBreakpointLT('lg');
|
|||
|
|
*/
|
|||
|
|
export function useBreakpointGTE(target: FineBreakpointKey): boolean {
|
|||
|
|
const { fineBreakpoint } = useResponsive();
|
|||
|
|
return isBreakpointGTE(fineBreakpoint, target);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function useBreakpointLT(target: FineBreakpointKey): boolean {
|
|||
|
|
const { fineBreakpoint } = useResponsive();
|
|||
|
|
return isBreakpointLT(fineBreakpoint, target);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function useBreakpointBetween(
|
|||
|
|
min: FineBreakpointKey,
|
|||
|
|
max: FineBreakpointKey
|
|||
|
|
): boolean {
|
|||
|
|
const { fineBreakpoint } = useResponsive();
|
|||
|
|
return isBreakpointGTE(fineBreakpoint, min) && !isBreakpointGTE(fineBreakpoint, max);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 平台检测 Hook ====================
|
|||
|
|
/**
|
|||
|
|
* 平台检测 Hook
|
|||
|
|
* 提供便捷的平台检测方法
|
|||
|
|
*
|
|||
|
|
* @example
|
|||
|
|
* const { isWeb, isIOS, isAndroid, isNative } = usePlatform();
|
|||
|
|
*/
|
|||
|
|
export function usePlatform() {
|
|||
|
|
const { platform } = useResponsive();
|
|||
|
|
return platform;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 媒体查询模拟 ====================
|
|||
|
|
/**
|
|||
|
|
* 模拟 CSS 媒体查询
|
|||
|
|
*
|
|||
|
|
* @param query - 查询条件
|
|||
|
|
* @returns 是否匹配
|
|||
|
|
*
|
|||
|
|
* @example
|
|||
|
|
* const isMinWidth768 = useMediaQuery({ minWidth: 768 });
|
|||
|
|
* const isMaxWidth1024 = useMediaQuery({ maxWidth: 1024 });
|
|||
|
|
* const isPortrait = useMediaQuery({ orientation: 'portrait' });
|
|||
|
|
*/
|
|||
|
|
interface MediaQueryOptions {
|
|||
|
|
minWidth?: number;
|
|||
|
|
maxWidth?: number;
|
|||
|
|
minHeight?: number;
|
|||
|
|
maxHeight?: number;
|
|||
|
|
orientation?: 'portrait' | 'landscape';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function useMediaQuery(query: MediaQueryOptions): boolean {
|
|||
|
|
const { width, height, orientation: currentOrientation } = useResponsive();
|
|||
|
|
|
|||
|
|
return useMemo(() => {
|
|||
|
|
if (query.minWidth !== undefined && width < query.minWidth) return false;
|
|||
|
|
if (query.maxWidth !== undefined && width > query.maxWidth) return false;
|
|||
|
|
if (query.minHeight !== undefined && height < query.minHeight) return false;
|
|||
|
|
if (query.maxHeight !== undefined && height > query.maxHeight) return false;
|
|||
|
|
if (query.orientation !== undefined && currentOrientation !== query.orientation) return false;
|
|||
|
|
return true;
|
|||
|
|
}, [width, height, currentOrientation, query]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 列数计算工具 ====================
|
|||
|
|
/**
|
|||
|
|
* 根据容器宽度计算合适的列数
|
|||
|
|
*
|
|||
|
|
* @param containerWidth - 容器宽度
|
|||
|
|
* @param options - 配置选项
|
|||
|
|
* @returns 列数
|
|||
|
|
*
|
|||
|
|
* @example
|
|||
|
|
* const columns = useColumnCount({
|
|||
|
|
* xs: 1,
|
|||
|
|
* sm: 2,
|
|||
|
|
* md: 3,
|
|||
|
|
* lg: 4
|
|||
|
|
* });
|
|||
|
|
*/
|
|||
|
|
export function useColumnCount(
|
|||
|
|
columnConfig: Partial<Record<FineBreakpointKey, number>> = {}
|
|||
|
|
): number {
|
|||
|
|
const { fineBreakpoint } = useResponsive();
|
|||
|
|
|
|||
|
|
const defaultConfig: Record<FineBreakpointKey, number> = {
|
|||
|
|
xs: 1,
|
|||
|
|
sm: 1,
|
|||
|
|
md: 2,
|
|||
|
|
lg: 3,
|
|||
|
|
xl: 4,
|
|||
|
|
'2xl': 4,
|
|||
|
|
'3xl': 5,
|
|||
|
|
'4xl': 6,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return useMemo(() => {
|
|||
|
|
const config = { ...defaultConfig, ...columnConfig };
|
|||
|
|
const breakpointOrder: FineBreakpointKey[] = ['4xl', '3xl', '2xl', 'xl', 'lg', 'md', 'sm', 'xs'];
|
|||
|
|
const currentIndex = breakpointOrder.indexOf(fineBreakpoint);
|
|||
|
|
|
|||
|
|
for (let i = currentIndex; i < breakpointOrder.length; i++) {
|
|||
|
|
const bp = breakpointOrder[i];
|
|||
|
|
if (config[bp] !== undefined) {
|
|||
|
|
return config[bp];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return 1;
|
|||
|
|
}, [fineBreakpoint, columnConfig]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 间距计算工具 ====================
|
|||
|
|
/**
|
|||
|
|
* 响应式间距 Hook
|
|||
|
|
*
|
|||
|
|
* @param spacingConfig - 间距配置
|
|||
|
|
* @returns 当前断点对应的间距值
|
|||
|
|
*
|
|||
|
|
* @example
|
|||
|
|
* const gap = useResponsiveSpacing({ xs: 8, md: 16, lg: 24 });
|
|||
|
|
*/
|
|||
|
|
export function useResponsiveSpacing(
|
|||
|
|
spacingConfig: Partial<Record<FineBreakpointKey, number>> = {}
|
|||
|
|
): number {
|
|||
|
|
const { fineBreakpoint } = useResponsive();
|
|||
|
|
|
|||
|
|
const defaultConfig: Record<FineBreakpointKey, number> = {
|
|||
|
|
xs: 8,
|
|||
|
|
sm: 8,
|
|||
|
|
md: 12,
|
|||
|
|
lg: 16,
|
|||
|
|
xl: 20,
|
|||
|
|
'2xl': 24,
|
|||
|
|
'3xl': 32,
|
|||
|
|
'4xl': 40,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return useMemo(() => {
|
|||
|
|
const config = { ...defaultConfig, ...spacingConfig };
|
|||
|
|
const breakpointOrder: FineBreakpointKey[] = ['4xl', '3xl', '2xl', 'xl', 'lg', 'md', 'sm', 'xs'];
|
|||
|
|
const currentIndex = breakpointOrder.indexOf(fineBreakpoint);
|
|||
|
|
|
|||
|
|
for (let i = currentIndex; i < breakpointOrder.length; i++) {
|
|||
|
|
const bp = breakpointOrder[i];
|
|||
|
|
if (config[bp] !== undefined) {
|
|||
|
|
return config[bp];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return defaultConfig.xs;
|
|||
|
|
}, [fineBreakpoint, spacingConfig]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default useResponsive;
|