index.tsx6.9 KB raw2.5 KB gz1 input file total: 6.9 KB raw
// @ts-nocheck
import React, { type ButtonHTMLAttributes, type FunctionComponent, type KeyboardEvent, type ReactNode } from "react";
import { css, cx } from "styled-system/css";

// Bamboo port of the real-project Tabs component (galaxus design system).
// Renders pixel-identically to the next-yak version: responsive copy typography,
// normal/active/hover/focus-visible/disabled states, an animated active underline
// via CSS anchor positioning (per-tab ::after fallback under @supports), a
// ::before WCAG 40px min-target, hidden-scrollbar overflow, and a composed
// FullWidthTabs wrapper. Every `css({...})` object is statically analyzable, so
// the compiler replaces each call with its class-string literal; active-vs-
// inactive picks between those precompiled strings at the call site via cx().

// --- style objects (mirror the yak template's css blocks verbatim) ----------

// font-size/line-height/letter-spacing/font-weight + desktop @media override.
const copyTypography = {
  fontSize: "16px",
  lineHeight: "24px",
  letterSpacing: "0.01em",
  fontWeight: "400",
  "@media (min-width: 992px)": {
    fontSize: "14px",
    lineHeight: "20px",
  },
} as const;

const normalTabStyle = css({
  position: "relative",
  display: "inline-flex",
  alignItems: "center",
  ...copyTypography,
  cursor: "pointer",
  color: "rgba(0, 0, 0, 0.6)",
  backgroundColor: "transparent",
  height: "40px",

  _after: {
    content: '""',
    position: "absolute",
    bottom: "0",
    left: "0",
    right: "0",
    borderBottom: "3px solid transparent",
    transition: "opacity 150ms linear",
  },

  _before: {
    position: "absolute",
    content: '""',
    width: "100%",
    minWidth: "40px",
    top: "0",
    bottom: "0",
    height: "100%",
  },
});

const activeTabStyle = css({
  color: "#000",

  "@supports (anchor-name: --test)": {
    "anchor-name": "--active-tab",
  },

  _after: {
    borderBottomColor: "#eeb524",

    "@supports (anchor-name: --test)": {
      borderBottomColor: "#ddd",
      opacity: "0",
    },
  },
});

const hoverDisabledTabStyle = css({
  "&:hover:not(:disabled)": {
    color: "#000",
    _after: {
      borderBottomColor: "#ddd",
    },
  },
  "&:disabled": {
    color: "rgba(0, 0, 0, 0.26)",
    cursor: "default",
  },
});

const focusVisibleTabStyle = css({
  _focusVisible: {
    outline: "none",
    "&&::after": {
      borderBottom: "3px solid #007bc7",
    },
  },
});

const tabBaseStyle = css({
  borderStyle: "none",
});

interface ITabProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  isActive?: boolean;
  disabled?: boolean;
}

const tabListItemStyle = css({
  display: "block",
  margin: "0",
  padding: "0",
  width: "min-content",
  flexShrink: "0",
  minWidth: "40px",
});

const TabInternal = ({ isActive, disabled, className, ...props }: ITabProps) => (
  <li role="presentation" className={tabListItemStyle}>
    <button
      {...props}
      type="button"
      role="tab"
      disabled={disabled}
      aria-selected={isActive}
      tabIndex={isActive ? 0 : -1}
      className={className}
    />
  </li>
);

const Tab = ({ isActive, ...props }: ITabProps) => (
  <TabInternal
    {...props}
    isActive={isActive}
    className={cx(
      tabBaseStyle,
      normalTabStyle,
      focusVisibleTabStyle,
      isActive ? activeTabStyle : hoverDisabledTabStyle,
    )}
  />
);

const tabListStyle = css({
  position: "relative",
  whiteSpace: "nowrap",
  display: "flex",
  flexWrap: "nowrap",
  overflowX: "auto",
  gap: "24px",
  boxShadow: "inset 0 -1px 0 0 #ddd",
  margin: "0",
  padding: "0",
  listStyle: "none",

  scrollbarWidth: "none",
  "&::-webkit-scrollbar": {
    display: "none",
  },

  "@supports (anchor-name: --test)": {
    _before: {
      content: '""',
      position: "absolute",
      bottom: "0",
      "position-anchor": "--active-tab",
      left: "anchor(left)",
      right: "anchor(right)",
      height: "3px",
      backgroundColor: "#eeb524",
      transition: "left 150ms ease-out, right 150ms ease-out",
    },
  },
});

// Equal-width children. Uses element/descendant selectors (not component refs) so
// every lane is byte-equivalent in structure.
const fullWidthTabsStyle = css({
  "& > li": {
    flex: "1",
    display: "flex",
    justifyContent: "center",
  },
  "& button": {
    flex: "1",
  },
});

interface ITabsProps {
  className?: string;
  children: ReactNode;
}

const Tabs: FunctionComponent<ITabsProps> = ({ children, className }) => (
  <ul role="tablist" onKeyDown={handleKeyDown} className={cx(tabListStyle, className)}>
    {children}
  </ul>
);

const FullWidthTabs: FunctionComponent<ITabsProps> = ({ children, className }) => (
  <Tabs className={cx(fullWidthTabsStyle, className)}>{children}</Tabs>
);

// --- keyboard navigation (roving tabindex) ----------------------------------
const queryTab = (el: Element | null | undefined) =>
  el?.querySelector("button") ?? null;
const firstTab = (list: HTMLUListElement) => queryTab(list.firstElementChild)?.focus();
const lastTab = (list: HTMLUListElement) => queryTab(list.lastElementChild)?.focus();
const nextTab = (list: HTMLUListElement) => {
  const n = queryTab(document.activeElement?.parentElement?.nextElementSibling);
  n ? n.focus() : firstTab(list);
};
const prevTab = (list: HTMLUListElement) => {
  const p = queryTab(document.activeElement?.parentElement?.previousElementSibling);
  p ? p.focus() : lastTab(list);
};
function handleKeyDown(event: KeyboardEvent<HTMLUListElement>) {
  const node = event.currentTarget;
  switch (event.key) {
    case "ArrowRight": nextTab(node); break;
    case "ArrowLeft": prevTab(node); break;
    case "Home": event.preventDefault(); firstTab(node); break;
    case "End": event.preventDefault(); lastTab(node); break;
  }
}

// --- workload ----------------------------------------------------------------
const LABELS = ["Overview", "Specs", "Reviews", "Q&A", "Similar", "Deals", "Support", "More"];
interface Group { id: number; count: number; activeIdx: number; fullWidth: boolean; disabledIdx: number; }

const TabGroup = ({ group }: { group: Group }) => (
  <Tabs>
    {Array.from({ length: group.count }, (_, t) => (
      <Tab
        key={t}
        isActive={t === group.activeIdx}
        disabled={t === group.disabledIdx && t !== group.activeIdx}
      >
        {LABELS[t % LABELS.length]}
      </Tab>
    ))}
  </Tabs>
);

const FullWidthTabGroup = ({ group }: { group: Group }) => (
  <FullWidthTabs>
    {Array.from({ length: group.count }, (_, t) => (
      <Tab
        key={t}
        isActive={t === group.activeIdx}
        disabled={t === group.disabledIdx && t !== group.activeIdx}
      >
        {LABELS[t % LABELS.length]}
      </Tab>
    ))}
  </FullWidthTabs>
);

export default (i: number) => {
  const count = 3 + (i % 6);
  const g: Group = { id: i, count, activeIdx: i % count, fullWidth: i % 3 === 0, disabledIdx: i % 4 === 0 ? count - 1 : -1 };
  return g.fullWidth ? <FullWidthTabGroup group={g} /> : <TabGroup group={g} />;
};