| | |
| |
|
| | import React, { useReducer, useEffect, useMemo, createContext } from 'react'; |
| | import { useLocation } from 'react-router-dom'; |
| | import { isMobile as getIsMobile } from '../../helpers'; |
| |
|
| | |
| | const ACTION_TYPES = { |
| | TOGGLE_SIDER: 'TOGGLE_SIDER', |
| | SET_SIDER: 'SET_SIDER', |
| | SET_MOBILE: 'SET_MOBILE', |
| | SET_SIDER_COLLAPSED: 'SET_SIDER_COLLAPSED', |
| | BATCH_UPDATE: 'BATCH_UPDATE', |
| | }; |
| |
|
| | |
| | const STORAGE_KEYS = { |
| | SIDEBAR_COLLAPSED: 'default_collapse_sidebar', |
| | }; |
| |
|
| | const ROUTE_PATTERNS = { |
| | CONSOLE: '/console', |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | const isConsoleRoute = (pathname) => { |
| | return pathname === ROUTE_PATTERNS.CONSOLE || |
| | pathname.startsWith(ROUTE_PATTERNS.CONSOLE + '/'); |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | const getInitialState = (pathname) => { |
| | const isMobile = getIsMobile(); |
| | const isConsole = isConsoleRoute(pathname); |
| | const isCollapsed = localStorage.getItem(STORAGE_KEYS.SIDEBAR_COLLAPSED) === 'true'; |
| |
|
| | return { |
| | isMobile, |
| | showSider: isConsole && !isMobile, |
| | siderCollapsed: isCollapsed, |
| | isManualSiderControl: false, |
| | }; |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | const styleReducer = (state, action) => { |
| | switch (action.type) { |
| | case ACTION_TYPES.TOGGLE_SIDER: |
| | return { |
| | ...state, |
| | showSider: !state.showSider, |
| | isManualSiderControl: true, |
| | }; |
| |
|
| | case ACTION_TYPES.SET_SIDER: |
| | return { |
| | ...state, |
| | showSider: action.payload, |
| | isManualSiderControl: action.isManualControl ?? false, |
| | }; |
| |
|
| | case ACTION_TYPES.SET_MOBILE: |
| | return { |
| | ...state, |
| | isMobile: action.payload, |
| | }; |
| |
|
| | case ACTION_TYPES.SET_SIDER_COLLAPSED: |
| | |
| | localStorage.setItem(STORAGE_KEYS.SIDEBAR_COLLAPSED, action.payload.toString()); |
| | return { |
| | ...state, |
| | siderCollapsed: action.payload, |
| | }; |
| |
|
| | case ACTION_TYPES.BATCH_UPDATE: |
| | return { |
| | ...state, |
| | ...action.payload, |
| | }; |
| |
|
| | default: |
| | return state; |
| | } |
| | }; |
| |
|
| | |
| | const StyleContext = createContext(null); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | const useWindowResize = (dispatch, state, pathname) => { |
| | useEffect(() => { |
| | const handleResize = () => { |
| | const isMobile = getIsMobile(); |
| | dispatch({ type: ACTION_TYPES.SET_MOBILE, payload: isMobile }); |
| |
|
| | |
| | if (!state.isManualSiderControl && isConsoleRoute(pathname)) { |
| | dispatch({ |
| | type: ACTION_TYPES.SET_SIDER, |
| | payload: !isMobile, |
| | isManualControl: false |
| | }); |
| | } |
| | }; |
| |
|
| | let timeoutId; |
| | const debouncedResize = () => { |
| | clearTimeout(timeoutId); |
| | timeoutId = setTimeout(handleResize, 150); |
| | }; |
| |
|
| | window.addEventListener('resize', debouncedResize); |
| | return () => { |
| | window.removeEventListener('resize', debouncedResize); |
| | clearTimeout(timeoutId); |
| | }; |
| | }, [dispatch, state.isManualSiderControl, pathname]); |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | const useRouteChange = (dispatch, pathname) => { |
| | useEffect(() => { |
| | const isMobile = getIsMobile(); |
| | const isConsole = isConsoleRoute(pathname); |
| |
|
| | dispatch({ |
| | type: ACTION_TYPES.BATCH_UPDATE, |
| | payload: { |
| | showSider: isConsole && !isMobile, |
| | isManualSiderControl: false, |
| | }, |
| | }); |
| | }, [pathname, dispatch]); |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | const useMobileSiderAutoHide = (state, dispatch) => { |
| | useEffect(() => { |
| | |
| | if (state.isMobile && state.showSider && !state.isManualSiderControl) { |
| | dispatch({ type: ACTION_TYPES.SET_SIDER, payload: false }); |
| | } |
| | }, [state.isMobile, state.showSider, state.isManualSiderControl, dispatch]); |
| | }; |
| |
|
| | |
| | |
| | |
| | export const StyleProvider = ({ children }) => { |
| | const location = useLocation(); |
| | const pathname = location.pathname; |
| |
|
| | const [state, dispatch] = useReducer( |
| | styleReducer, |
| | pathname, |
| | getInitialState |
| | ); |
| |
|
| | useWindowResize(dispatch, state, pathname); |
| | useRouteChange(dispatch, pathname); |
| | useMobileSiderAutoHide(state, dispatch); |
| |
|
| | const contextValue = useMemo( |
| | () => ({ state, dispatch }), |
| | [state] |
| | ); |
| |
|
| | return ( |
| | <StyleContext.Provider value={contextValue}> |
| | {children} |
| | </StyleContext.Provider> |
| | ); |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | export const useStyle = () => { |
| | const context = React.useContext(StyleContext); |
| | if (!context) { |
| | throw new Error('useStyle must be used within StyleProvider'); |
| | } |
| | return context; |
| | }; |
| |
|
| | |
| | export const styleActions = { |
| | toggleSider: () => ({ type: ACTION_TYPES.TOGGLE_SIDER }), |
| | setSider: (show, isManualControl = false) => ({ |
| | type: ACTION_TYPES.SET_SIDER, |
| | payload: show, |
| | isManualControl |
| | }), |
| | setMobile: (isMobile) => ({ type: ACTION_TYPES.SET_MOBILE, payload: isMobile }), |
| | setSiderCollapsed: (collapsed) => ({ |
| | type: ACTION_TYPES.SET_SIDER_COLLAPSED, |
| | payload: collapsed |
| | }), |
| | }; |
| |
|