import React, { useEffect, useRef, useState } from 'react'; import styled, { css } from 'styled-components'; import type { JSX } from 'react'; import type { CodeWalkthroughFile } from '@redocly/config'; import { useThemeHooks, useRenderableFiles, type RenderableFile } from '@redocly/theme/core/hooks'; import { OverflowMenuVerticalIcon } from '@redocly/theme/icons/OverflowMenuVerticalIcon/OverflowMenuVerticalIcon'; import { Dropdown } from '@redocly/theme/components/Dropdown/Dropdown'; import { DropdownMenu } from '@redocly/theme/components/Dropdown/DropdownMenu'; import { DropdownMenuItem } from '@redocly/theme/components/Dropdown/DropdownMenuItem'; import { DownloadIcon } from '@redocly/theme/icons/DownloadIcon/DownloadIcon'; import { Button } from '@redocly/theme/components/Button/Button'; export type CodePanelHeaderProps = { files: CodeWalkthroughFile[]; handleTabSwitch: (name: string) => void; activeTabName: string; onDownloadCode: () => void; }; export function CodePanelHeader({ files, handleTabSwitch, activeTabName, onDownloadCode, }: CodePanelHeaderProps): JSX.Element { const renderableFiles = useRenderableFiles(files); const { useTranslate } = useThemeHooks(); const { translate } = useTranslate(); const tabRefs = useRef([]); const tabsWrapperRef = useRef(null); const [hiddenFiles, setHiddenFiles] = useState([]); useEffect(() => { const activeTab = tabRefs.current.find((tab) => tab?.dataset.name === activeTabName); const tabsWrapper = tabsWrapperRef.current; if (activeTab && tabsWrapper) { const { left: wrapperLeft, right: wrapperRight } = tabsWrapper.getBoundingClientRect(); const { left: tabLeft, right: tabRight } = activeTab.getBoundingClientRect(); const tabHidden = tabLeft < wrapperLeft || tabRight > wrapperRight; if (tabHidden) { activeTab.scrollIntoView({ block: 'nearest', inline: 'nearest' }); } } const calculateHiddenFiles = () => { if (!tabsWrapperRef.current) return; const { left: wrapperLeft, right: wrapperRight } = tabsWrapperRef.current.getBoundingClientRect(); const hidden: RenderableFile[] = []; for (let i = 0; i < renderableFiles.length; i++) { const tab = tabRefs.current[i]; if (!tab) continue; const { left: tabLeft, right: tabRight } = tab.getBoundingClientRect(); const visible = tabLeft >= wrapperLeft && tabRight <= wrapperRight; if (!visible) { hidden.push(renderableFiles[i]); } } setHiddenFiles(hidden); }; calculateHiddenFiles(); window.addEventListener('resize', calculateHiddenFiles); return () => window.removeEventListener('resize', calculateHiddenFiles); }, [activeTabName, files, renderableFiles]); return ( {renderableFiles.map( ({ path, basename, fileIcon, parentFolder, isNameDuplicate, inRootDir }, i) => { return ( { tabRefs.current[i] = el as HTMLButtonElement; }} data-name={path} active={path === activeTabName} key={i} onClick={() => handleTabSwitch(path)} > {fileIcon} {basename} {isNameDuplicate && !inRootDir ? {parentFolder} : null} ); }, )} {hiddenFiles.length ? ( } alignment="end"> {hiddenFiles.map( ({ path, basename, fileIcon, isNameDuplicate, inRootDir, parentFolder }, i) => { return ( handleTabSwitch(path)} prefix={fileIcon} content={ isNameDuplicate && !inRootDir ? `${parentFolder}/${basename}` : basename } /> ); }, )} ) : null} ); } const CodePanelHeaderWrapper = styled.div` display: flex; align-items: center; justify-content: space-between; padding: var(--spacing-xs) var(--spacing-xs) var(--spacing-xs) var(--spacing-sm); max-width: 100%; `; const TabsWrapper = styled.div` display: flex; position: relative; min-width: 0; `; const Gradient = styled.div` position: absolute; right: 0; width: var(--spacing-base); height: var(--code-panel-header-height); background: var(--bg-raised-gradient); `; const Tabs = styled.div` display: flex; overflow-x: hidden; padding-right: var(--spacing-base); &::-webkit-scrollbar { display: none; } `; const Dirname = styled.span` font-size: var(--font-size-sm); color: var(--text-color-description); `; const ActionBar = styled.div` display: flex; `; const Tab = styled.button<{ active: boolean }>` --icon-width: 16px; --icon-height: 16px; display: inline-flex; align-items: center; padding: 0 var(--spacing-sm); background-color: transparent; height: var(--code-panel-header-height); border-radius: var(--border-radius); cursor: pointer; gap: var(--spacing-xs); color: var(--text-color-secondary); white-space: nowrap; scroll-margin-right: var(--spacing-base); ${({ active }) => active ? css` color: var(--text-color-primary); background-color: var(--tab-bg-color-filled); ` : css` &:hover { color: var(--text-color-primary); } `} `; // eslint-disable-next-line no-warning-comments // code-walk-todo: figure it our why we could not do it differently const StyledDropdownMenu = styled(DropdownMenu)` --md-list-left-padding: var(--dropdown-menu-padding); `; const StyledOverflowMenuVerticalIcon = styled(OverflowMenuVerticalIcon)` && { outline: none; box-sizing: content-box; padding: 5px; } `;