ERP-node/frontend/hooks/useDeviceOrientation.ts

212 lines
6.9 KiB
TypeScript
Raw Normal View History

"use client";
import { useState, useEffect, useMemo } from "react";
// ========================================
// 타입 정의
// ========================================
export type DeviceType = "mobile" | "tablet";
export type OrientationType = "landscape" | "portrait";
export interface ResponsiveMode {
device: DeviceType;
orientation: OrientationType;
isLandscape: boolean;
modeKey: "tablet_landscape" | "tablet_portrait" | "mobile_landscape" | "mobile_portrait";
}
// ========================================
// 브레이크포인트 (화면 너비 기준)
// GRID_BREAKPOINTS와 일치해야 함!
// ========================================
const BREAKPOINTS = {
// mobile_portrait: ~479px (4칸)
// mobile_landscape: 480~767px (6칸)
// tablet_portrait: 768~1023px (8칸)
// tablet_landscape: 1024px~ (12칸)
TABLET_MIN: 768, // 768px 이상이면 tablet
};
/**
*
*
* - 4
* - tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait
* - resize orientation
*
* @returns ResponsiveMode
*/
export function useResponsiveMode(): ResponsiveMode {
const [mode, setMode] = useState<ResponsiveMode>({
device: "tablet",
orientation: "landscape",
isLandscape: true,
modeKey: "tablet_landscape",
});
useEffect(() => {
if (typeof window === "undefined") return;
const detectMode = (): ResponsiveMode => {
const width = window.innerWidth;
const height = window.innerHeight;
// 디바이스 타입 결정 (화면 너비 기준)
const device: DeviceType = width >= BREAKPOINTS.TABLET_MIN ? "tablet" : "mobile";
// 방향 결정 (가로/세로 비율)
const isLandscape = width > height;
const orientation: OrientationType = isLandscape ? "landscape" : "portrait";
// 모드 키 생성
const modeKey = `${device}_${orientation}` as ResponsiveMode["modeKey"];
return { device, orientation, isLandscape, modeKey };
};
// 초기값 설정
setMode(detectMode());
const handleChange = () => {
setTimeout(() => {
setMode(detectMode());
}, 100);
};
// 이벤트 리스너 등록
window.addEventListener("resize", handleChange);
window.addEventListener("orientationchange", handleChange);
// matchMedia로 orientation 변경 감지
const landscapeQuery = window.matchMedia("(orientation: landscape)");
if (landscapeQuery.addEventListener) {
landscapeQuery.addEventListener("change", handleChange);
}
return () => {
window.removeEventListener("resize", handleChange);
window.removeEventListener("orientationchange", handleChange);
if (landscapeQuery.removeEventListener) {
landscapeQuery.removeEventListener("change", handleChange);
}
};
}, []);
return mode;
}
/**
* (orientation)
*
* - /
* - window.matchMedia와 orientationchange
* - SSR (typeof window !== 'undefined')
*
* @returns isLandscape - true: , false:
*/
export function useDeviceOrientation(): boolean {
const [isLandscape, setIsLandscape] = useState<boolean>(false);
useEffect(() => {
if (typeof window === "undefined") return;
const detectOrientation = (): boolean => {
if (window.matchMedia) {
const landscapeQuery = window.matchMedia("(orientation: landscape)");
return landscapeQuery.matches;
}
return window.innerWidth > window.innerHeight;
};
setIsLandscape(detectOrientation());
const handleOrientationChange = () => {
setTimeout(() => {
setIsLandscape(detectOrientation());
}, 100);
};
const landscapeQuery = window.matchMedia("(orientation: landscape)");
if (landscapeQuery.addEventListener) {
landscapeQuery.addEventListener("change", handleOrientationChange);
} else if (landscapeQuery.addListener) {
landscapeQuery.addListener(handleOrientationChange);
}
window.addEventListener("orientationchange", handleOrientationChange);
window.addEventListener("resize", handleOrientationChange);
return () => {
if (landscapeQuery.removeEventListener) {
landscapeQuery.removeEventListener("change", handleOrientationChange);
} else if (landscapeQuery.removeListener) {
landscapeQuery.removeListener(handleOrientationChange);
}
window.removeEventListener("orientationchange", handleOrientationChange);
window.removeEventListener("resize", handleOrientationChange);
};
}, []);
return isLandscape;
}
/**
*
*
*
* @param initialOverride - (undefined면 )
* @returns [isLandscape, setIsLandscape, isAutoDetect]
*/
export function useDeviceOrientationWithOverride(
initialOverride?: boolean
): [boolean, (value: boolean | undefined) => void, boolean] {
const autoDetectedIsLandscape = useDeviceOrientation();
const [manualOverride, setManualOverride] = useState<boolean | undefined>(initialOverride);
const isLandscape = manualOverride !== undefined ? manualOverride : autoDetectedIsLandscape;
const isAutoDetect = manualOverride === undefined;
const setOrientation = (value: boolean | undefined) => {
setManualOverride(value);
};
return [isLandscape, setOrientation, isAutoDetect];
}
/**
* +
* /
*/
export function useResponsiveModeWithOverride(
initialDeviceOverride?: DeviceType,
initialOrientationOverride?: boolean
): {
mode: ResponsiveMode;
setDevice: (device: DeviceType | undefined) => void;
setOrientation: (isLandscape: boolean | undefined) => void;
isAutoDetect: boolean;
} {
const autoMode = useResponsiveMode();
const [deviceOverride, setDeviceOverride] = useState<DeviceType | undefined>(initialDeviceOverride);
const [orientationOverride, setOrientationOverride] = useState<boolean | undefined>(initialOrientationOverride);
const mode = useMemo((): ResponsiveMode => {
const device = deviceOverride ?? autoMode.device;
const isLandscape = orientationOverride ?? autoMode.isLandscape;
const orientation: OrientationType = isLandscape ? "landscape" : "portrait";
const modeKey = `${device}_${orientation}` as ResponsiveMode["modeKey"];
return { device, orientation, isLandscape, modeKey };
}, [autoMode, deviceOverride, orientationOverride]);
const isAutoDetect = deviceOverride === undefined && orientationOverride === undefined;
return {
mode,
setDevice: setDeviceOverride,
setOrientation: setOrientationOverride,
isAutoDetect,
};
}