/* ============================================================================ * Copyright (c) Palo Alto Networks * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * ========================================================================== */ import React, { cloneElement, ReactElement, useEffect, useRef } from "react"; import { sanitizeTabsChildren, type TabItemProps, type TabProps, TabsProvider, useScrollPositionBlocker, useTabsContextValue, } from "@docusaurus/theme-common/internal"; import useIsBrowser from "@docusaurus/useIsBrowser"; import clsx from "clsx"; import { Language } from "../CodeSnippets/code-snippets-types"; export interface Props { action: { [key: string]: React.Dispatch; }; currentLanguage?: Language; languageSet: Language[]; includeVariant?: boolean; } export interface CodeTabsProps extends Props, TabProps { includeSample?: boolean; } function TabList({ action, currentLanguage, languageSet, includeVariant, includeSample, className, block, selectedValue, selectValue, tabValues, }: CodeTabsProps & ReturnType) { const tabRefs = useRef<(HTMLLIElement | null)[]>([]); const tabsScrollContainerRef = useRef(null); const { blockElementScrollPositionUntilNextRender } = useScrollPositionBlocker(); useEffect(() => { const activeTab = tabRefs.current.find( (tab) => tab?.getAttribute("aria-selected") === "true" ); if (activeTab && tabsScrollContainerRef.current) { const container = tabsScrollContainerRef.current; const containerRect = container.getBoundingClientRect(); const activeTabRect = activeTab.getBoundingClientRect(); // Calculate the distance to scroll to align active tab to the left const glowOffset = 3; const scrollOffset = activeTabRect.left - containerRect.left + container.scrollLeft - glowOffset; // Check if the active tab is not already at the left position if (Math.abs(scrollOffset - container.scrollLeft) > 4) { // Adjust the scroll of the container container.scrollLeft = scrollOffset; } } }, []); const handleTabChange = ( event: | React.FocusEvent | React.MouseEvent | React.KeyboardEvent ) => { const newTab = event.currentTarget; const newTabIndex = tabRefs.current.indexOf(newTab); const newTabValue = tabValues[newTabIndex]!.value; if (newTabValue !== selectedValue) { blockElementScrollPositionUntilNextRender(newTab); selectValue(newTabValue); } if (action) { let newLanguage: Language; if (currentLanguage && includeVariant) { newLanguage = languageSet.filter( (lang: Language) => lang.language === currentLanguage.language )[0]; newLanguage.variant = newTabValue; action.setSelectedVariant(newTabValue.toLowerCase()); } else if (currentLanguage && includeSample) { newLanguage = languageSet.filter( (lang: Language) => lang.language === currentLanguage.language )[0]; newLanguage.sample = newTabValue; action.setSelectedSample(newTabValue); } else { newLanguage = languageSet.filter( (lang: Language) => lang.language === newTabValue )[0]; action.setSelectedVariant(newLanguage.variants[0].toLowerCase()); action.setSelectedSample(newLanguage.sample); } action.setLanguage(newLanguage); } }; const handleKeydown = (event: React.KeyboardEvent) => { let focusElement: HTMLLIElement | null = null; switch (event.key) { case "Enter": { handleTabChange(event); break; } case "ArrowRight": { const nextTab = tabRefs.current.indexOf(event.currentTarget) + 1; focusElement = tabRefs.current[nextTab] ?? tabRefs.current[0]!; break; } case "ArrowLeft": { const prevTab = tabRefs.current.indexOf(event.currentTarget) - 1; focusElement = tabRefs.current[prevTab] ?? tabRefs.current[tabRefs.current.length - 1]!; break; } default: break; } focusElement?.focus(); }; return (
    {tabValues.map(({ value, label, attributes }) => (
  • { if (tabControl) { tabRefs.current.push(tabControl); } }} onKeyDown={handleKeydown} onClick={handleTabChange} {...attributes} className={clsx( "tabs__item", "openapi-tabs__code-item", attributes?.className as string, { active: selectedValue === value, } )} > {label ?? value}
  • ))}
); } function TabContent({ lazy, children, selectedValue, }: CodeTabsProps & ReturnType): React.JSX.Element | null { const childTabs = (Array.isArray(children) ? children : [children]).filter( Boolean ) as ReactElement[]; if (lazy) { const selectedTabItem = childTabs.find( (tabItem) => tabItem.props.value === selectedValue ); if (!selectedTabItem) { // fail-safe or fail-fast? not sure what's best here return null; } return cloneElement(selectedTabItem, { className: "margin-top--md" }); } return (
{childTabs}
); } function TabsComponent(props: CodeTabsProps & Props): React.JSX.Element { const tabs = useTabsContextValue(props); const { className } = props; return (
); } export default function CodeTabs( props: CodeTabsProps & Props ): React.JSX.Element { const isBrowser = useIsBrowser(); return ( {sanitizeTabsChildren(props.children)} ); }