Segment Control

Usage

<SegmentedControl>
    <SegmentedControl.Control active>Dashboard</SegmentedControl.Control>
    <SegmentedControl.Control>Timer</SegmentedControl.Control>
</SegmentedControl>

Snippet

import React, { useCallback, useState } from "react";
import styled from "styled-components";

const Container = styled.div`
  position: relative;
`;

const Nav = styled.nav`
  width: 100%;
  display: flex;
  gap: 10px;
  transition: clip-path 500ms cubic-bezier(0.91, 0.01, 0, 1.01);
  color: var(--subtle);

  & > button {
    padding: 6px;
    min-width: 10ch;
    margin-right: 16px;
    background: transparent;
    border: 0px;
    display: flex;
    color: inherit;
    align-items: center;
    justify-content: center;
  }

  &.overlay {
    display: flex;
    border-bottom: 2px solid black;
    top: 0;
    left: 0;
    position: absolute;
    color: var(--text);
    clip-path: inset(0px 100% 0px 0px);
  }
`;

function getActiveAreaStyle(containerNode, elementNode) {
  const navRect = containerNode.getBoundingClientRect();
  const buttonRect = elementNode.getBoundingClientRect();
  const right = navRect.right - buttonRect.right;
  const left = buttonRect.left - navRect.left;
  return {
    clipPath: `inset(0 ${right}px 0 ${left}px)`,
  };
}

export default function SegmentedControl({ className = "", children }) {
  const [activeBoundStyle, setActiveBoundsStyle] = useState({});

  const initialActiveBounds = React.useCallback((node) => {
    if (node === null) {
      return;
    }
    setActiveBoundsStyle({
      clipPath: "inset(0 100% 0 0)",
    });
  }, []);

  const makeActive = (element) => {
    const overlay = document.querySelector("#overlay-nav");
    if (!overlay) return;
    setActiveBoundsStyle(getActiveAreaStyle(overlay, element));
  };

  const modified = React.Children.map(children, (child) => {
    const modified = React.cloneElement(child, {
      makeActive: makeActive,
    });
    return modified;
  });

  return (
    <>
      <Container>
        <Nav className={`${className}`}>{modified}</Nav>
        <Nav
          id="overlay-nav"
          className={`overlay ${className}`}
          ref={initialActiveBounds}
          style={activeBoundStyle}
        >
          {modified}
        </Nav>
      </Container>
    </>
  );
}

SegmentedControl.Control = ({ makeActive, active, children, onClick }) => {
  const setInitialActive = useCallback(
    (node) => {
      if (active && node) {
        makeActive(node);
      }
    },
    [active]
  );

  return (
    <button
      ref={setInitialActive}
      onClick={(e) => {
        makeActive(e.target);
        onClick?.();
      }}
    >
      {children}
    </button>
  );
};