export function Tabs()

in src/tabs-motion/tabs.tsx [107:321]


export function Tabs({
  activeKey = '0',
  disabled = false,
  children,
  fill = FILL.intrinsic,
  activateOnFocus = true,
  onChange,
  orientation = ORIENTATION.horizontal,
  overrides = {},
  renderAll = false,
  // @ts-ignore
  uid: customUid = null,
  endEnhancer,
}: TabsProps) {
  // Create unique id prefix for this tabs component
  const generatedUid = useUID();
  const uid = customUid || generatedUid;

  // Unpack overrides
  const {
    Root: RootOverrides,
    TabList: TabListOverrides,
    TabHighlight: TabHighlightOverrides,
    TabBorder: TabBorderOverrides,
  } = overrides;
  const [Root, RootProps] = getOverrides(RootOverrides, StyledRoot);
  const [TabList, TabListProps] = getOverrides(TabListOverrides, StyledTabList);
  const [TabHighlight, TabHighlightProps] = getOverrides(TabHighlightOverrides, StyledTabHighlight);
  const [TabBorder, TabBorderProps] = getOverrides(TabBorderOverrides, StyledTabBorder);
  const [EndEnhancerContainer, endEnhancerContainerProps] = getOverrides(
    overrides.EndEnhancerContainer,
    StyledEndEnhancerContainer
  );
  const [TabBar, tabBarProps] = getOverrides(overrides.TabBar, StyledTabBar);

  // Count key updates
  // We disable a few things until after first mount:
  // - the highlight animation, avoiding an initial slide-in
  // - smooth scrolling active tab into view
  const [keyUpdated, setKeyUpdated] = React.useState(0);
  React.useEffect(() => {
    setKeyUpdated(keyUpdated + 1);
  }, [activeKey]);

  // Positioning the highlight.
  const activeTabRef = React.useRef<HTMLElement>();
  const [highlightLayout, setHighlightLayout] = React.useState({
    length: 0,
    distance: 0,
  });

  // Create a shared, memoized callback for tabs to call on resize.
  const updateHighlight = React.useCallback(() => {
    if (activeTabRef.current) {
      setHighlightLayout(getLayoutParams(activeTabRef.current, orientation));
    }
  }, [activeTabRef.current, orientation]);

  // Update highlight on key, orientation and children changes.
  React.useEffect(updateHighlight, [activeTabRef.current, orientation, children]);

  // Scroll active tab into view when the parent has scrollbar on mount and
  // on key change (smooth scroll). Note, if the active key changes while
  // the tab is not in view, the page will scroll it into view.
  // TODO: replace with custom scrolling logic.
  React.useEffect(() => {
    // Flow needs this condition pulled out.
    if (activeTabRef.current) {
      if (
        isHorizontal(orientation)
          ? // @ts-expect-error todo(flow->ts) maybe parentElement?
            activeTabRef.current.parentNode.scrollWidth >
            // @ts-expect-error todo(flow->ts) maybe parentElement?
            activeTabRef.current.parentNode.clientWidth
          : // @ts-expect-error todo(flow->ts) maybe parentElement?
            activeTabRef.current.parentNode.scrollHeight >
            // @ts-expect-error todo(flow->ts) maybe parentElement?
            activeTabRef.current.parentNode.clientHeight
      ) {
        if (keyUpdated > 1) {
          activeTabRef.current.scrollIntoView({
            behavior: 'smooth',
            block: 'nearest',
            inline: 'nearest',
          });
        } else {
          scrollParentToCentreTarget(activeTabRef.current);
        }
      }
    }
  }, [activeTabRef.current]);

  // Collect shared styling props
  const sharedStylingProps = {
    $orientation: orientation,
    $fill: fill,
  };

  // Helper for parsing directional keys
  // TODO(WPT-6473): move to universal keycode aliases
  const [, theme] = useStyletron();
  const parseKeyDown = React.useCallback(
    (event: { keyCode: number }) => {
      if (isHorizontal(orientation)) {
        if (isRTL(theme.direction)) {
          switch (event.keyCode) {
            case 39:
              return KEYBOARD_ACTION.previous;
            case 37:
              return KEYBOARD_ACTION.next;
            default:
              return null;
          }
        } else {
          switch (event.keyCode) {
            case 37:
              return KEYBOARD_ACTION.previous;
            case 39:
              return KEYBOARD_ACTION.next;
            default:
              return null;
          }
        }
      } else {
        switch (event.keyCode) {
          case 38:
            return KEYBOARD_ACTION.previous;
          case 40:
            return KEYBOARD_ACTION.next;
          default:
            return null;
        }
      }
    },
    [orientation, theme.direction]
  );

  return (
    <Root {...sharedStylingProps} {...RootProps}>
      <TabBar $hasEndEnhancer={Boolean(endEnhancer)} $orientation={orientation} {...tabBarProps}>
        <TabList
          data-baseweb="tab-list"
          role="tablist"
          aria-orientation={orientation}
          {...sharedStylingProps}
          {...TabListProps}
        >
          {/* @ts-ignore */}
          {React.Children.map(children, (child: React.ReactElement, index) => {
            if (!child) return;
            return (
              <InternalTab
                childKey={child.key}
                childIndex={index}
                activeKey={activeKey}
                orientation={orientation}
                activeTabRef={activeTabRef}
                updateHighlight={updateHighlight}
                parseKeyDown={parseKeyDown}
                activateOnFocus={activateOnFocus}
                uid={uid}
                disabled={disabled}
                sharedStylingProps={sharedStylingProps}
                onChange={onChange}
                {...child.props}
              />
            );
          })}
          <TabHighlight
            data-baseweb="tab-highlight"
            $length={highlightLayout.length}
            $distance={highlightLayout.distance}
            // This avoids the tab sliding in from the side on mount
            $animate={keyUpdated > 1}
            aria-hidden="true"
            role="presentation"
            {...sharedStylingProps}
            {...TabHighlightProps}
          />
        </TabList>

        {orientation === ORIENTATION.horizontal &&
          endEnhancer !== null &&
          endEnhancer !== undefined && (
            <EndEnhancerContainer {...endEnhancerContainerProps} $orientation={orientation}>
              <RenderEnhancer Enhancer={endEnhancer} />
            </EndEnhancerContainer>
          )}
      </TabBar>

      <TabBorder
        data-baseweb="tab-border"
        aria-hidden="true"
        role="presentation"
        {...sharedStylingProps}
        {...TabBorderProps}
      />
      {/* @ts-ignore */}
      {React.Children.map(children, (child: React.ReactElement, index) => {
        if (!child) return;
        return (
          <InternalTabPanel
            childKey={child.key}
            childIndex={index}
            activeKey={activeKey}
            uid={uid}
            sharedStylingProps={sharedStylingProps}
            renderAll={renderAll}
            {...child.props}
          />
        );
      })}
    </Root>
  );
}