import React, { EventHandler, MouseEventHandler, ReactElement, createContext, useContext, useEffect, useMemo, useRef, useState, } from "react"; import Tab, { TabProps } from "../tab"; import classNames from "classnames"; export type TabUnderlineStyle = "default" | "full-width"; interface TabsContextProps { currentTabIndex: number; tabs: TabProps[]; onChange: (index: number) => void; children: ReactElement; expandedTabs?: boolean; tabUnderlineStyle?: TabUnderlineStyle; } const Context = createContext({ currentTabIndex: 0, tabs: [], onChange: (index) => {}, children: <>, expandedTabs: false, tabUnderlineStyle: "default", }); const useTabsContext = () => useContext(Context); const TabsPanel = () => { const { currentTabIndex, tabs, onChange, expandedTabs, tabUnderlineStyle } = useTabsContext(); const viewportRef = useRef(null); const contentRef = useRef(null); const addListeners = () => { window.addEventListener("mousemove", onMouseMove); }; const removeListeners = () => { window.removeEventListener("mousemove", onMouseMove); }; useEffect(() => { addListeners(); return removeListeners; }, []); const [dragging, setDragging] = useState(false); const [startDraggingX, setStartDraggingX] = useState(0); const [startScrollPosition, setStartScrollPosition] = useState(0); const [mouseX, setMouseX] = useState(0); const currentScrollValue = useMemo(() => { return mouseX - startDraggingX; }, [startDraggingX, mouseX]); useEffect(() => { if (!dragging) { return; } const viewportEl = viewportRef?.current; const currentScrollPosition = viewportEl?.scrollLeft ?? 0; viewportEl?.scrollTo({ left: startScrollPosition - currentScrollValue, }); }, [currentScrollValue, dragging]); const disableSelection = () => { document.body.style.userSelect = "none"; }; const enableSelection = () => { document.body.style.userSelect = "inherit"; }; useEffect(() => { if (dragging) { disableSelection(); } else { enableSelection(); } return enableSelection; }, [dragging]); const startDragging = (e: React.MouseEvent) => { setDragging(true); setStartDraggingX(e.clientX); setStartScrollPosition(viewportRef?.current?.scrollLeft ?? 0); }; const stopDragging = () => { setDragging(false); }; const onMouseDown: MouseEventHandler = ( e: React.MouseEvent ) => { startDragging(e); window.addEventListener("mouseup", onMouseUp); }; const onMouseUp = (e: MouseEvent) => { stopDragging(); window.removeEventListener("mouseup", onMouseUp); }; const onMouseMove = (e: MouseEvent) => { setMouseX(e.clientX); }; const scrollToElement = (index: number) => { const viewportEl = viewportRef?.current; const contentEl = contentRef?.current; const clickedChild = contentEl?.children[index]; if (!viewportEl || !clickedChild) { return; } const childBounds = clickedChild.getBoundingClientRect(); const viewportBounds = viewportEl.getBoundingClientRect(); const contentBounds = contentEl.getBoundingClientRect(); const { x: viewportX, width: viewportWidth } = viewportBounds; const { x: childX, width: childWidth } = childBounds; const { x: contentX, width: contentWidth } = contentBounds; const viewportEndX = viewportX + viewportWidth; const childEndX = childX + childWidth; const childStartDistance = childX - contentX; console.log({ contentX, childX, childStartDistance, }); if (childEndX > viewportEndX) { viewportEl.scrollTo({ left: childStartDistance - childWidth, }); return; } if (childX < viewportX) { viewportEl.scrollTo({ left: childStartDistance, }); } }; return (
{tabs.map((tab, index) => { const active = index == currentTabIndex; const handleClick = () => { if (tab.onClick) { tab.onClick(); } onChange(index); }; return ( ); })}
); }; const TabsCurrent = () => { const { children } = useTabsContext(); return children; }; interface TabsProps { initalTabIndex?: number; onChange?: (index: number) => void; tabs: TabProps[]; children: string | ReactElement | ReactElement[]; expandedTabs?: boolean; tabUnderlineStyle?: TabUnderlineStyle; } const Tabs = ({ initalTabIndex = 0, onChange, tabs, children, expandedTabs, tabUnderlineStyle, }: TabsProps) => { const [currentTabIndex, setCurrentTabIndex] = useState(initalTabIndex); const currentTabChildren = useMemo(() => { const currentTab = tabs[currentTabIndex]; const children = currentTab?.children ?? tabs[0].children; return children; }, [currentTabIndex]); let contextValues: TabsContextProps = useMemo( () => ({ currentTabIndex, onChange: (newIndex: number) => { setCurrentTabIndex(newIndex); if (onChange) { onChange(newIndex); } }, tabs, children: currentTabChildren, expandedTabs, tabUnderlineStyle, }), [ currentTabIndex, setCurrentTabIndex, tabs, currentTabChildren, expandedTabs, tabUnderlineStyle, ] ); return {children}; }; export { Tabs, TabsPanel, TabsCurrent };