UNPKG

vueless

Version:

Vue Styleless UI Component Library, powered by Tailwind CSS.

178 lines (139 loc) 4.21 kB
import { onMounted, ref, watch, computed, onBeforeUnmount } from "vue"; import { isSSR } from "../utils/helper"; type ResponsiveConfig<T> = Partial<Record<BreakpointName, T>>; enum BreakpointName { Xs = "xs", Sm = "sm", Md = "md", Lg = "lg", Xl = "xl", "2xl" = "2xl", } enum BreakpointWidth { Xs = 0, Sm = 640, Md = 768, Lg = 1024, Xl = 1280, "2xl" = 1536, } let isInitialized = false; let animationId: number | undefined; const windowWidth = ref(0); const currentBreakpoint = ref(BreakpointName.Xs); const BREAKPOINT_KEYS = Object.keys(BreakpointName) as (keyof typeof BreakpointName)[]; watch(windowWidth, setBreakpoint, { immediate: true }); export function useBreakpoint() { const isPhone = computed(() => { return currentBreakpoint.value === BreakpointName.Xs; }); const isLargePhone = computed(() => { return currentBreakpoint.value === BreakpointName.Sm; }); const isPhoneGroup = computed(() => { return isPhone.value || isLargePhone.value; }); const isPortraitTablet = computed(() => { return currentBreakpoint.value === BreakpointName.Md; }); const isLandscapeTablet = computed(() => { return currentBreakpoint.value === BreakpointName.Lg; }); const isTabletGroup = computed(() => { return isPortraitTablet.value || isLandscapeTablet.value; }); const isDesktop = computed(() => { return currentBreakpoint.value === BreakpointName.Xl; }); const isLargeDesktop = computed(() => { return currentBreakpoint.value === BreakpointName["2xl"]; }); const isDesktopGroup = computed(() => { return isDesktop.value || isLargeDesktop.value; }); onMounted(() => { initBreakpointListener(); }); onBeforeUnmount(() => { window.removeEventListener("resize", resizeListener); }); return { isPhone, isLargePhone, isPhoneGroup, isPortraitTablet, isLandscapeTablet, isTabletGroup, isDesktop, isLargeDesktop, isDesktopGroup, breakpoint: currentBreakpoint, }; } /** * Shorthand function that can be used directly in templates. * Returns the appropriate value based on the current breakpoint. * Vue will track the reactive dependency and re-render when the breakpoint changes. * * @example * ```vue * <template> * <UButton :size="r({ sm: 'sm', md: 'md' })">Click me</UButton> * </template> * * <script setup> * import { r } from "vueless"; * </script> * ``` */ export function r<T>(config: ResponsiveConfig<T>): T { initBreakpointListener(); const definedKeys = BREAKPOINT_KEYS.filter((key) => BreakpointName[key] in config); if (!definedKeys.length) { return undefined as T; } const currentIndex = BREAKPOINT_KEYS.findIndex( (key) => BreakpointName[key] === currentBreakpoint.value, ); const smallestDefinedIndex = BREAKPOINT_KEYS.indexOf(definedKeys[0]); const largestDefinedIndex = BREAKPOINT_KEYS.indexOf(definedKeys[definedKeys.length - 1]); if (currentIndex <= smallestDefinedIndex) { return config[BreakpointName[definedKeys[0]]] as T; } if (currentIndex >= largestDefinedIndex) { return config[BreakpointName[definedKeys[definedKeys.length - 1]]] as T; } for (let i = currentIndex; i >= 0; i--) { const bp = BreakpointName[BREAKPOINT_KEYS[i]]; if (bp in config) { return config[bp] as T; } } return config[BreakpointName[definedKeys[0]]] as T; } function setBreakpoint(newWindowWidth: number) { if (newWindowWidth === undefined) return; for (let i = BREAKPOINT_KEYS.length - 1; i >= 0; i--) { const key = BREAKPOINT_KEYS[i]; if (newWindowWidth >= BreakpointWidth[key]) { currentBreakpoint.value = BreakpointName[key]; return; } } currentBreakpoint.value = BreakpointName.Xs; } function resizeListener() { if (isSSR) return; if (animationId) { window.cancelAnimationFrame(animationId); } animationId = window.requestAnimationFrame(() => { windowWidth.value = window.innerWidth; }); } function initBreakpointListener() { if (isInitialized || isSSR) return; isInitialized = true; windowWidth.value = window.innerWidth; window.addEventListener("resize", resizeListener, { passive: true }); }