Created
October 15, 2020 12:44
-
-
Save isBatak/2ed2e4ffd57bbea62a6872c46dae0aa7 to your computer and use it in GitHub Desktop.
Tabs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { createContext, useState, useMemo, useContext, cloneElement, isValidElement, FC } from 'react'; | |
import useConstant from '../../../hooks/useConstant'; | |
type TabStateContext = [number, React.Dispatch<React.SetStateAction<number>>]; | |
interface ITabsProps { | |
state?: TabStateContext; | |
} | |
interface IElements { | |
tabs: number; | |
panels: number; | |
} | |
const TabsState = createContext<TabStateContext | null>(null); | |
const Elements = createContext<IElements | null>(null); | |
export const Tabs: FC<ITabsProps> = ({ state: outerState, children }) => { | |
const innerState = useState(0); | |
const elements = useConstant(() => ({ tabs: 0, panels: 0 })); | |
const state = outerState || innerState; | |
return ( | |
<Elements.Provider value={elements}> | |
<TabsState.Provider value={state}>{children}</TabsState.Provider> | |
</Elements.Provider> | |
); | |
}; | |
export const useTabState = () => { | |
const tabState = useContext(TabsState); | |
const elements = useContext(Elements); | |
if (!tabState || !elements) { | |
throw new Error('useTabsState must be used within Tabs'); | |
} | |
const tabIndex = useConstant(() => { | |
const currentIndex = elements.tabs; | |
elements.tabs += 1; | |
return currentIndex; | |
}); | |
const [activeIndex, setActive] = tabState; | |
const onClick = useConstant(() => () => { | |
setActive(tabIndex); | |
}); | |
const state = useMemo( | |
() => ({ | |
isActive: activeIndex === tabIndex, | |
onClick, | |
}), | |
[activeIndex, onClick, tabIndex] | |
); | |
return state; | |
}; | |
export const usePanelState = () => { | |
const tabsState = useContext(TabsState); | |
const elements = useContext(Elements); | |
if (!tabsState || !elements) { | |
throw new Error('usePanelState must be used within Tabs'); | |
} | |
const [activeIndex] = tabsState; | |
const panelIndex = useConstant(() => { | |
const currentIndex = elements.panels; | |
elements.panels += 1; | |
return currentIndex; | |
}); | |
return panelIndex === activeIndex; | |
}; | |
export const Tab: FC = ({ children }) => { | |
const state = useTabState(); | |
if (typeof children === 'function') { | |
return children(state); | |
} | |
return isValidElement(children) ? cloneElement(children, state) : children; | |
}; | |
interface IPanelProps { | |
active: boolean; | |
} | |
export const Panel: FC<IPanelProps> = ({ active, children }) => { | |
const isActive = usePanelState(); | |
if (isActive) { | |
return active ? children : null; | |
} | |
return null; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from 'react'; | |
type ResultBox<T> = { v: T }; | |
export default function useConstant<T>(fn: () => T): T { | |
const ref = React.useRef<ResultBox<T>>(); | |
if (!ref.current) { | |
ref.current = { v: fn() }; | |
} | |
return ref.current.v; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment