skip to Main Content

Currently I can scroll when click and drag on the margin space between the AriaTabs. Once I click on the AriaTab, it’ll register as a click. OnMouseDown and onClick won’t be detected when I click on AriaTab. Wrapping AriaTab inside div breaks the entire component.

Are there any way to add scroll functionality on the tabs without scrollbar?

import { Tabs, TabList, Tab as AriaTab, TabPanel } from 'react-aria-components';

const Tab: React.FC<TabProps> = ({ tabs }) => {

  const tabsRef = useRef<HTMLDivElement>(null);
  const [ isMouseDown, setIsMouseDown ] = useState(false);
  const [ startX, setStartX ] = useState(0);
  const [ scrollLeft, setScrollLeft ] = useState(0);

  const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
    console.log('handleMouseDown registered');
    if (tabsRef.current) {
      setIsMouseDown(true);
      setStartX(e.pageX - tabsRef.current.offsetLeft);
      setScrollLeft(tabsRef.current.scrollLeft);
    }
  };
  const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>)=> {
    if(!isMouseDown) return;
    e.preventDefault();
    if (tabsRef.current) {
      const x = e.pageX - tabsRef.current.offsetLeft;
      const walk = (x-startX)*2;
      tabsRef.current.scrollLeft = scrollLeft-walk;
    }
  }
  const handleMouseMoveCapture = ()=> {
    console.log('handleMouseMoveCapture');
  }
  const handleMouseUp = ()=> {
    setIsMouseDown(false);
  }
  const handleMouseLeave = ()=> {
    setIsMouseDown(false);
  }
 
  const handleTabClick = (e: React.MouseEvent<HTMLDivElement>) => {
    const tabElement = e.target as HTMLElement;
    console.log('handleTabClick')
    if (tabElement.closest(`.${styles['react-aria-Tab']}`)) {
      console.log(`Tab ${tabElement.innerText} clicked`);
    }
  };

  return (
      <Tabs className={styles['react-aria-Tabs']}>
        <div className={styles['scrolling-container']} 
          ref={tabsRef}
          onMouseDown={handleMouseDown}
          onMouseLeave={handleMouseLeave}
          onMouseUp={handleMouseUp}
          onMouseMove={handleMouseMove}
          onMouseMoveCapture={handleMouseMoveCapture}
          onClick={handleTabClick}
        >
        <TabList className={styles['react-aria-TabList']}>
           {tabs.map(tab => (          
            <AriaTab key={tab.id} id={tab.id} className={styles['react-aria-Tab']}>
              {tab.icon && <span className={styles['react-aria-TabIcon']}>{tab.icon}</span>}
              {tab.label}
            </AriaTab>
          ))}              
          </TabList>
          </div>
        {tabs.map(tab => (
          <TabPanel key={tab.id} id={tab.id} className={styles['react-aria-TabPanel']}>
            {tab.content}
          </TabPanel>
        ))}
      </Tabs>
  );
};

2

Answers


  1. Add scroll to tabs:

    import { Tabs, TabList, Tab as AriaTab, TabPanel } from 'react-aria-components';
    
    const Tab = ({ tabs }) => {
      const tabsRef = useRef(null);
    
      const handleMouseDown = (e) => {
        if (tabsRef.current) {
          tabsRef.current.dataset.mouseDownAt = e.pageX;
          tabsRef.current.dataset.scrollLeft = tabsRef.current.scrollLeft;
        }
      };
    
      const handleMouseMove = (e) => {
        if (!tabsRef.current || !tabsRef.current.dataset.mouseDownAt) return;
        const mouseDelta = tabsRef.current.dataset.mouseDownAt - e.pageX;
        tabsRef.current.scrollLeft = tabsRef.current.dataset.scrollLeft - mouseDelta;
      };
    
      const handleMouseUp = () => {
        if (tabsRef.current) {
          delete tabsRef.current.dataset.mouseDownAt;
          delete tabsRef.current.dataset.scrollLeft;
        }
      };
    
      return (
        <Tabs className="react-aria-Tabs">
          <div
            className="scrolling-container"
            ref={tabsRef}
            onMouseDown={handleMouseDown}
            onMouseMove={handleMouseMove}
            onMouseUp={handleMouseUp}
            onMouseLeave={handleMouseUp}
          >
            <TabList className="react-aria-TabList">
              {tabs.map(tab => (
                <AriaTab key={tab.id} id={tab.id} className="react-aria-Tab">
                  {tab.icon && <span className="react-aria-TabIcon">{tab.icon}</span>}
                  {tab.label}
                </AriaTab>
              ))}
            </TabList>
          </div>
          {tabs.map(tab => (
            <TabPanel key={tab.id} id={tab.id} className="react-aria-TabPanel">
              {tab.content}
            </TabPanel>
          ))}
        </Tabs>
      );
    };
    
    export default Tab;
    

    Scroll work when drag mouse. No scrollbar need.

    Login or Signup to reply.
  2. To achieve it, you need to carefully handle mouse events while ensuring they don’t interfere with the default click events on the AriaTab components.

    The key here is to differentiate between a drag event for scrolling and a click event on a tab. Below is the modified code implementing these suggestions.

    import { useRef, useState } from 'react';
    import { Tabs, TabList, Tab as AriaTab, TabPanel } from 'react-aria-components';
    
    const Tab = ({ tabs }) => {
        const tabsRef = useRef(null);
        const [isMouseDown, setIsMouseDown] = useState(false);
        const [startX, setStartX] = useState(0);
        const [scrollLeft, setScrollLeft] = useState(0);
        const [isDragging, setIsDragging] = useState(false);
    
        const handleMouseDown = e => {
            if (tabsRef.current) {
                setIsMouseDown(true);
                setStartX(e.pageX - tabsRef.current.offsetLeft);
                setScrollLeft(tabsRef.current.scrollLeft);
            }
        };
    
        const handleMouseMove = e => {
            if (!isMouseDown) return;
            e.preventDefault();
            if (tabsRef.current) {
                const x = e.pageX - tabsRef.current.offsetLeft;
                const walk = (x - startX) * 2;
                tabsRef.current.scrollLeft = scrollLeft - walk;
                setIsDragging(true);
            }
        };
    
        const handleMouseUp = () => {
            setIsMouseDown(false);
            setTimeout(() => {
                setIsDragging(false);
            }, 0);
        };
    
        const handleMouseLeave = () => {
            setIsMouseDown(false);
        };
    
        const handleTabClick = e => {
            if (isDragging) {
                e.preventDefault();
                return;
            }
            const tabElement = e.target.closest(`.${styles['react-aria-Tab']}`);
            if (tabElement) {
                console.log(`Tab ${tabElement.innerText} clicked`);
            }
        };
    
        return (
            <Tabs className={styles['react-aria-Tabs']}>
                <div
                    className={styles['scrolling-container']}
                    ref={tabsRef}
                    onMouseDown={handleMouseDown}
                    onMouseLeave={handleMouseLeave}
                    onMouseUp={handleMouseUp}
                    onMouseMove={handleMouseMove}
                    onClick={handleTabClick}
                >
                    <TabList className={styles['react-aria-TabList']}>
                        {tabs.map(tab => (
                            <AriaTab key={tab.id} id={tab.id} className={styles['react-aria-Tab']}>
                                {tab.icon && (
                                    <span className={styles['react-aria-TabIcon']}>{tab.icon}</span>
                                )}
                                {tab.label}
                            </AriaTab>
                        ))}
                    </TabList>
                </div>
                {tabs.map(tab => (
                    <TabPanel key={tab.id} id={tab.id} className={styles['react-aria-TabPanel']}>
                        {tab.content}
                    </TabPanel>
                ))}
            </Tabs>
        );
    };
    
    export default Tab;
    

    Make sure your CSS includes styles for the scrolling container, tabs, and other relevant classes

    .scrolling-container {
        overflow-x: auto;
        white-space: nowrap;
        cursor: grab;
    }
    
    .scrolling-container:active {
        cursor: grabbing;
    }
    
    .react-aria-Tab {
        display: inline-block;
        padding: 10px 20px;
        cursor: pointer;
    }
    
    .react-aria-TabIcon {
        margin-right: 8px;
    }
    

    How it works:

    Mouse Down: When the mouse is pressed down, we store the initial position and scroll offset.

    Mouse Move: If the mouse is moved while pressed down, we calculate the new scroll position and set isDragging to true to indicate that a drag operation is in progress.

    Mouse Up and Leave: When the mouse button is released or leaves the container, we reset the dragging state.

    Tab Click: We check if a drag was in progress (isDragging). If so, we prevent the click action, otherwise, the click event on the tab is registered.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search