mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-03 07:42:03 +00:00
deduplicate course tab logic, add pure useCourseTabsState, and sync tab state with URL
This commit is contained in:
parent
5a79523ba0
commit
ccda05df96
@ -3,11 +3,15 @@ import useCourseTabs from './useCourseTabs';
|
||||
import useCoursePayment from './useCoursePayment';
|
||||
import useCourseData from './useCourseData';
|
||||
import useLessons from './useLessons';
|
||||
import useCourseNavigation from './useCourseNavigation';
|
||||
import useCourseTabsState from './useCourseTabsState';
|
||||
|
||||
export {
|
||||
useCourseDecryption,
|
||||
useCourseTabs,
|
||||
useCoursePayment,
|
||||
useCourseData,
|
||||
useLessons
|
||||
useLessons,
|
||||
useCourseNavigation,
|
||||
useCourseTabsState
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import useCourseTabsState from './useCourseTabsState';
|
||||
|
||||
/**
|
||||
* Hook to manage course navigation and tab logic
|
||||
@ -8,19 +9,20 @@ import { useState, useEffect, useMemo } from 'react';
|
||||
*/
|
||||
const useCourseNavigation = (router, isMobileView) => {
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const [sidebarVisible, setSidebarVisible] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState('overview'); // Default to overview tab
|
||||
|
||||
// Memoized function to get the tab map based on view mode
|
||||
const tabMap = useMemo(() => {
|
||||
const baseTabMap = ['overview', 'content', 'qa'];
|
||||
if (isMobileView) {
|
||||
const mobileTabMap = [...baseTabMap];
|
||||
mobileTabMap.splice(2, 0, 'lessons');
|
||||
return mobileTabMap;
|
||||
}
|
||||
return baseTabMap;
|
||||
}, [isMobileView]);
|
||||
|
||||
// Use the base hook for core tab state functionality
|
||||
const {
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
sidebarVisible,
|
||||
setSidebarVisible,
|
||||
tabMap,
|
||||
getActiveTabIndex,
|
||||
getTabItems,
|
||||
toggleSidebar: baseToggleSidebar
|
||||
} = useCourseTabsState({
|
||||
isMobileView
|
||||
});
|
||||
|
||||
// Initialize navigation state based on router
|
||||
useEffect(() => {
|
||||
@ -39,10 +41,10 @@ const useCourseNavigation = (router, isMobileView) => {
|
||||
// Auto-open sidebar on desktop, close on mobile
|
||||
setSidebarVisible(!isMobileView);
|
||||
}
|
||||
}, [router.isReady, router.query, isMobileView]);
|
||||
}, [router.isReady, router.query, isMobileView, setActiveTab, setSidebarVisible]);
|
||||
|
||||
// Function to handle lesson selection
|
||||
const handleLessonSelect = (index) => {
|
||||
const handleLessonSelect = useCallback((index) => {
|
||||
setActiveIndex(index);
|
||||
|
||||
// Update URL without causing a page reload (for bookmarking purposes)
|
||||
@ -54,10 +56,10 @@ const useCourseNavigation = (router, isMobileView) => {
|
||||
setActiveTab('content');
|
||||
setSidebarVisible(false);
|
||||
}
|
||||
};
|
||||
}, [router.query.slug, isMobileView, setActiveTab, setSidebarVisible]);
|
||||
|
||||
// Function to toggle tab
|
||||
const toggleTab = (index) => {
|
||||
// Function to toggle tab with lesson state integration
|
||||
const toggleTab = useCallback((index) => {
|
||||
const tabName = tabMap[index];
|
||||
setActiveTab(tabName);
|
||||
|
||||
@ -65,66 +67,7 @@ const useCourseNavigation = (router, isMobileView) => {
|
||||
if (isMobileView) {
|
||||
setSidebarVisible(tabName === 'lessons');
|
||||
}
|
||||
};
|
||||
|
||||
// Function to toggle sidebar visibility
|
||||
const toggleSidebar = () => {
|
||||
setSidebarVisible(!sidebarVisible);
|
||||
};
|
||||
|
||||
// Map active tab name back to index for MenuTab
|
||||
const getActiveTabIndex = () => {
|
||||
return tabMap.indexOf(activeTab);
|
||||
};
|
||||
|
||||
// Create tab items for MenuTab
|
||||
const getTabItems = () => {
|
||||
const items = [
|
||||
{
|
||||
label: 'Overview',
|
||||
icon: 'pi pi-home',
|
||||
},
|
||||
{
|
||||
label: 'Content',
|
||||
icon: 'pi pi-book',
|
||||
}
|
||||
];
|
||||
|
||||
// Add lessons tab only on mobile
|
||||
if (isMobileView) {
|
||||
items.push({
|
||||
label: 'Lessons',
|
||||
icon: 'pi pi-list',
|
||||
});
|
||||
}
|
||||
|
||||
items.push({
|
||||
label: 'Comments',
|
||||
icon: 'pi pi-comments',
|
||||
});
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
// Add keyboard navigation support for tabs
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'ArrowRight') {
|
||||
const currentIndex = getActiveTabIndex();
|
||||
const nextIndex = (currentIndex + 1) % tabMap.length;
|
||||
toggleTab(nextIndex);
|
||||
} else if (e.key === 'ArrowLeft') {
|
||||
const currentIndex = getActiveTabIndex();
|
||||
const prevIndex = (currentIndex - 1 + tabMap.length) % tabMap.length;
|
||||
toggleTab(prevIndex);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [activeTab, tabMap]);
|
||||
}, [tabMap, isMobileView, setActiveTab, setSidebarVisible]);
|
||||
|
||||
return {
|
||||
activeIndex,
|
||||
@ -135,7 +78,7 @@ const useCourseNavigation = (router, isMobileView) => {
|
||||
setSidebarVisible,
|
||||
handleLessonSelect,
|
||||
toggleTab,
|
||||
toggleSidebar,
|
||||
toggleSidebar: baseToggleSidebar,
|
||||
getActiveTabIndex,
|
||||
getTabItems,
|
||||
tabMap
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { useEffect, useCallback } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import useWindowWidth from '../useWindowWidth';
|
||||
import useCourseTabsState from './useCourseTabsState';
|
||||
|
||||
/**
|
||||
* @deprecated Use useCourseTabsState for pure state or useCourseNavigation for router integration
|
||||
* Hook to manage course tabs, navigation, and sidebar visibility
|
||||
* @param {Object} options - Configuration options
|
||||
* @param {Array} options.tabMap - Optional custom tab map to use
|
||||
@ -13,49 +15,42 @@ const useCourseTabs = (options = {}) => {
|
||||
const router = useRouter();
|
||||
const windowWidth = useWindowWidth();
|
||||
const isMobileView = typeof windowWidth === 'number' ? windowWidth <= 968 : false;
|
||||
// Tab management state
|
||||
const [activeTab, setActiveTab] = useState('overview');
|
||||
const [sidebarVisible, setSidebarVisible] = useState(
|
||||
options.initialSidebarVisible !== undefined ? options.initialSidebarVisible : !isMobileView
|
||||
);
|
||||
|
||||
// Get tab map based on view mode
|
||||
const tabMap = useMemo(() => {
|
||||
const baseTabMap = options.tabMap || ['overview', 'content', 'qa'];
|
||||
if (isMobileView) {
|
||||
const mobileTabMap = [...baseTabMap];
|
||||
// Insert lessons tab before qa in mobile view
|
||||
if (!mobileTabMap.includes('lessons')) {
|
||||
mobileTabMap.splice(2, 0, 'lessons');
|
||||
}
|
||||
return mobileTabMap;
|
||||
}
|
||||
return baseTabMap;
|
||||
}, [isMobileView, options.tabMap]);
|
||||
// Use the base hook for core tab state functionality
|
||||
const {
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
sidebarVisible,
|
||||
setSidebarVisible,
|
||||
tabMap,
|
||||
getActiveTabIndex,
|
||||
getTabItems,
|
||||
toggleSidebar
|
||||
} = useCourseTabsState({
|
||||
tabMap: options.tabMap,
|
||||
initialSidebarVisible: options.initialSidebarVisible,
|
||||
isMobileView
|
||||
});
|
||||
|
||||
// Update tabs and sidebar based on router query
|
||||
useEffect(() => {
|
||||
if (router.isReady) {
|
||||
const { active } = router.query;
|
||||
if (active !== undefined) {
|
||||
const { active, tab } = router.query;
|
||||
|
||||
// If tab is specified in the URL, use that
|
||||
if (tab && tabMap.includes(tab)) {
|
||||
setActiveTab(tab);
|
||||
} else if (active !== undefined) {
|
||||
// If we have an active lesson, switch to content tab
|
||||
setActiveTab('content');
|
||||
} else {
|
||||
// Default to overview tab when no active parameter
|
||||
// Default to overview tab when no parameters
|
||||
setActiveTab('overview');
|
||||
}
|
||||
|
||||
// Auto-open sidebar on desktop, close on mobile
|
||||
setSidebarVisible(!isMobileView);
|
||||
}
|
||||
}, [router.isReady, router.query, isMobileView]);
|
||||
}, [router.isReady, router.query, tabMap, setActiveTab]);
|
||||
|
||||
// Get active tab index
|
||||
const getActiveTabIndex = useCallback(() => {
|
||||
return tabMap.indexOf(activeTab);
|
||||
}, [activeTab, tabMap]);
|
||||
|
||||
// Toggle between tabs
|
||||
// Toggle between tabs with router integration
|
||||
const toggleTab = useCallback((indexOrName) => {
|
||||
const tabName = typeof indexOrName === 'number'
|
||||
? tabMap[indexOrName]
|
||||
@ -67,61 +62,18 @@ const useCourseTabs = (options = {}) => {
|
||||
if (isMobileView) {
|
||||
setSidebarVisible(tabName === 'lessons');
|
||||
}
|
||||
}, [tabMap, isMobileView]);
|
||||
|
||||
// Toggle sidebar visibility
|
||||
const toggleSidebar = useCallback(() => {
|
||||
setSidebarVisible(prev => !prev);
|
||||
}, []);
|
||||
|
||||
// Generate tab items for MenuTab component
|
||||
const getTabItems = useCallback(() => {
|
||||
const items = [
|
||||
{
|
||||
label: 'Overview',
|
||||
icon: 'pi pi-home',
|
||||
},
|
||||
{
|
||||
label: 'Content',
|
||||
icon: 'pi pi-book',
|
||||
}
|
||||
];
|
||||
|
||||
// Add lessons tab only on mobile
|
||||
if (isMobileView) {
|
||||
items.push({
|
||||
label: 'Lessons',
|
||||
icon: 'pi pi-list',
|
||||
});
|
||||
}
|
||||
|
||||
items.push({
|
||||
label: 'Comments',
|
||||
icon: 'pi pi-comments',
|
||||
});
|
||||
|
||||
return items;
|
||||
}, [isMobileView]);
|
||||
|
||||
// Setup keyboard navigation for tabs
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'ArrowRight') {
|
||||
const currentIndex = getActiveTabIndex();
|
||||
const nextIndex = (currentIndex + 1) % tabMap.length;
|
||||
toggleTab(nextIndex);
|
||||
} else if (e.key === 'ArrowLeft') {
|
||||
const currentIndex = getActiveTabIndex();
|
||||
const prevIndex = (currentIndex - 1 + tabMap.length) % tabMap.length;
|
||||
toggleTab(prevIndex);
|
||||
}
|
||||
// Sync URL with tab change using shallow routing
|
||||
const newQuery = {
|
||||
...router.query,
|
||||
tab: tabName === 'overview' ? undefined : tabName
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [getActiveTabIndex, tabMap, toggleTab]);
|
||||
router.push(
|
||||
{ pathname: router.pathname, query: newQuery },
|
||||
undefined,
|
||||
{ shallow: true }
|
||||
);
|
||||
}, [tabMap, isMobileView, router, setActiveTab, setSidebarVisible]);
|
||||
|
||||
return {
|
||||
activeTab,
|
||||
|
140
src/hooks/courses/useCourseTabsState.js
Normal file
140
src/hooks/courses/useCourseTabsState.js
Normal file
@ -0,0 +1,140 @@
|
||||
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
||||
|
||||
/**
|
||||
* Base hook for tab state management with no router or side-effects
|
||||
* This pure hook manages the tab state and sidebar visibility
|
||||
*
|
||||
* @param {Object} options - Configuration options
|
||||
* @param {Array} options.tabMap - Optional custom tab map to use
|
||||
* @param {boolean} options.initialSidebarVisible - Initial sidebar visibility state
|
||||
* @param {boolean} options.isMobileView - Whether the current view is mobile
|
||||
* @returns {Object} Pure tab management utilities and state
|
||||
*/
|
||||
const useCourseTabsState = (options = {}) => {
|
||||
const {
|
||||
tabMap: customTabMap,
|
||||
initialSidebarVisible,
|
||||
isMobileView = false
|
||||
} = options;
|
||||
|
||||
// Tab management state
|
||||
const [activeTab, setActiveTab] = useState('overview');
|
||||
const [sidebarVisible, setSidebarVisible] = useState(
|
||||
initialSidebarVisible !== undefined ? initialSidebarVisible : !isMobileView
|
||||
);
|
||||
|
||||
// Track if we've initialized yet
|
||||
const initialized = useRef(false);
|
||||
|
||||
// Get tab map based on view mode
|
||||
const tabMap = useMemo(() => {
|
||||
const baseTabMap = customTabMap || ['overview', 'content', 'qa'];
|
||||
if (isMobileView) {
|
||||
const mobileTabMap = [...baseTabMap];
|
||||
// Insert lessons tab before qa in mobile view
|
||||
if (!mobileTabMap.includes('lessons')) {
|
||||
mobileTabMap.splice(2, 0, 'lessons');
|
||||
}
|
||||
return mobileTabMap;
|
||||
}
|
||||
return baseTabMap;
|
||||
}, [isMobileView, customTabMap]);
|
||||
|
||||
// Auto-update sidebar visibility based on mobile/desktop
|
||||
useEffect(() => {
|
||||
if (initialized.current) {
|
||||
// Only auto-update sidebar visibility if we're initialized
|
||||
// and the view mode changes
|
||||
setSidebarVisible(!isMobileView);
|
||||
} else {
|
||||
initialized.current = true;
|
||||
}
|
||||
}, [isMobileView]);
|
||||
|
||||
// Get active tab index
|
||||
const getActiveTabIndex = useCallback(() => {
|
||||
return tabMap.indexOf(activeTab);
|
||||
}, [activeTab, tabMap]);
|
||||
|
||||
// Pure toggle between tabs with no side effects
|
||||
const toggleTab = useCallback((indexOrName) => {
|
||||
const tabName = typeof indexOrName === 'number'
|
||||
? tabMap[indexOrName]
|
||||
: indexOrName;
|
||||
|
||||
setActiveTab(tabName);
|
||||
|
||||
// Only show/hide sidebar on mobile - desktop keeps sidebar visible
|
||||
if (isMobileView) {
|
||||
setSidebarVisible(tabName === 'lessons');
|
||||
}
|
||||
}, [tabMap, isMobileView]);
|
||||
|
||||
// Toggle sidebar visibility
|
||||
const toggleSidebar = useCallback(() => {
|
||||
setSidebarVisible(prev => !prev);
|
||||
}, []);
|
||||
|
||||
// Generate tab items for MenuTab component
|
||||
const getTabItems = useCallback(() => {
|
||||
const items = [
|
||||
{
|
||||
label: 'Overview',
|
||||
icon: 'pi pi-home',
|
||||
},
|
||||
{
|
||||
label: 'Content',
|
||||
icon: 'pi pi-book',
|
||||
}
|
||||
];
|
||||
|
||||
// Add lessons tab only on mobile
|
||||
if (isMobileView) {
|
||||
items.push({
|
||||
label: 'Lessons',
|
||||
icon: 'pi pi-list',
|
||||
});
|
||||
}
|
||||
|
||||
items.push({
|
||||
label: 'Comments',
|
||||
icon: 'pi pi-comments',
|
||||
});
|
||||
|
||||
return items;
|
||||
}, [isMobileView]);
|
||||
|
||||
// Setup keyboard navigation for tabs
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'ArrowRight') {
|
||||
const currentIndex = getActiveTabIndex();
|
||||
const nextIndex = (currentIndex + 1) % tabMap.length;
|
||||
toggleTab(nextIndex);
|
||||
} else if (e.key === 'ArrowLeft') {
|
||||
const currentIndex = getActiveTabIndex();
|
||||
const prevIndex = (currentIndex - 1 + tabMap.length) % tabMap.length;
|
||||
toggleTab(prevIndex);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [getActiveTabIndex, tabMap, toggleTab]);
|
||||
|
||||
return {
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
sidebarVisible,
|
||||
setSidebarVisible,
|
||||
toggleTab,
|
||||
toggleSidebar,
|
||||
getActiveTabIndex,
|
||||
getTabItems,
|
||||
tabMap
|
||||
};
|
||||
};
|
||||
|
||||
export default useCourseTabsState;
|
Loading…
x
Reference in New Issue
Block a user