feat: add TemplateGrid and TitleBar components with tests
- Implemented TemplateGrid component for displaying templates in a grid layout. - Added calculateCardWidth helper function for dynamic card sizing. - Created TitleBar component for displaying the title and points with interaction. - Added unit tests for TemplateGrid and TitleBar components to ensure proper functionality. - Introduced useStickyTabs and useTabNavigation hooks with tests for managing sticky tab behavior and navigation logic. - Implemented useTemplateFilter hook for filtering templates based on video content. - Added comprehensive tests for all new hooks and components to validate behavior and edge cases.
This commit is contained in:
90
hooks/use-tab-navigation.ts
Normal file
90
hooks/use-tab-navigation.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { useState, useMemo, useCallback, useEffect } from 'react'
|
||||
import { CategoryTemplate } from '@repo/sdk'
|
||||
|
||||
export interface Category {
|
||||
id: string
|
||||
name: string
|
||||
templates?: CategoryTemplate[]
|
||||
}
|
||||
|
||||
export interface UseTabNavigationOptions {
|
||||
categories: Category[]
|
||||
initialCategoryId?: string | null
|
||||
}
|
||||
|
||||
export interface UseTabNavigationReturn {
|
||||
activeIndex: number
|
||||
selectedCategoryId: string | null
|
||||
currentCategory: Category | undefined
|
||||
tabs: string[]
|
||||
selectTab: (index: number) => void
|
||||
selectCategoryById: (categoryId: string) => void
|
||||
}
|
||||
|
||||
export function useTabNavigation(options: UseTabNavigationOptions): UseTabNavigationReturn {
|
||||
const { categories, initialCategoryId } = options
|
||||
|
||||
// State for active tab index
|
||||
const [activeIndex, setActiveIndex] = useState<number>(() => {
|
||||
if (initialCategoryId) {
|
||||
const index = categories.findIndex(c => c.id === initialCategoryId)
|
||||
return index >= 0 ? index : 0
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
// State for selected category ID
|
||||
const [selectedCategoryId, setSelectedCategoryId] = useState<string | null>(() => {
|
||||
if (initialCategoryId) {
|
||||
const exists = categories.some(c => c.id === initialCategoryId)
|
||||
if (exists) {
|
||||
return initialCategoryId
|
||||
}
|
||||
}
|
||||
return categories.length > 0 ? categories[0].id : null
|
||||
})
|
||||
|
||||
// Auto-select first category when categories change and no selection
|
||||
useEffect(() => {
|
||||
if (categories.length > 0 && !selectedCategoryId) {
|
||||
setSelectedCategoryId(categories[0].id)
|
||||
setActiveIndex(0)
|
||||
}
|
||||
}, [categories, selectedCategoryId])
|
||||
|
||||
// Generate tabs array from category names
|
||||
const tabs = useMemo(() => {
|
||||
return categories.map(category => category.name)
|
||||
}, [categories])
|
||||
|
||||
// Get current category based on selectedCategoryId
|
||||
const currentCategory = useMemo(() => {
|
||||
return categories.find(c => c.id === selectedCategoryId)
|
||||
}, [categories, selectedCategoryId])
|
||||
|
||||
// Select tab by index
|
||||
const selectTab = useCallback((index: number) => {
|
||||
if (index >= 0 && index < categories.length) {
|
||||
setActiveIndex(index)
|
||||
setSelectedCategoryId(categories[index].id)
|
||||
}
|
||||
}, [categories])
|
||||
|
||||
// Select category by ID
|
||||
const selectCategoryById = useCallback((categoryId: string) => {
|
||||
const index = categories.findIndex(c => c.id === categoryId)
|
||||
if (index >= 0) {
|
||||
setActiveIndex(index)
|
||||
setSelectedCategoryId(categoryId)
|
||||
}
|
||||
}, [categories])
|
||||
|
||||
return {
|
||||
activeIndex,
|
||||
selectedCategoryId,
|
||||
currentCategory,
|
||||
tabs,
|
||||
selectTab,
|
||||
selectCategoryById,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user