import React from 'react'; import styled from 'styled-components'; import type { JSX } from 'react'; import type { CodeBlockItems } from '@redocly/theme/components/CodeBlock/CodeBlock'; import { CodeBlockTabs } from '@redocly/theme/components/CodeBlock/CodeBlockTabs'; import { CopyButton } from '@redocly/theme/components/Buttons/CopyButton'; import { Tooltip } from '@redocly/theme/components/Tooltip/Tooltip'; import { useThemeHooks, useThemeConfig } from '@redocly/theme/core/hooks'; import { DeselectIcon } from '@redocly/theme/icons/DeselectIcon/DeselectIcon'; import { MaximizeIcon } from '@redocly/theme/icons/MaximizeIcon/MaximizeIcon'; import { MinimizeIcon } from '@redocly/theme/icons/MinimizeIcon/MinimizeIcon'; import { SelectIcon } from '@redocly/theme/icons/SelectIcon/SelectIcon'; import { WarningSquareIcon } from '@redocly/theme/icons/WarningSquareIcon/WarningSquareIcon'; import { Button } from '@redocly/theme/components/Button/Button'; import { CodeBlockDropdown } from '@redocly/theme/components/CodeBlock/CodeBlockDropdown'; export type CodeBlockControlsProps = { children?: React.ReactNode; className?: string; title?: React.ReactNode | string; controls?: ControlItems | false; tabs?: CodeBlockItems; dropdown?: CodeBlockItems; }; type ControlItems = { copy?: CopyControlProps; expand?: ControlProps; collapse?: ControlProps; report?: ControlProps; select?: ControlProps; deselect?: ControlProps; }; type ControlProps = { hidden?: boolean; label?: string; tooltipText?: string; onClick?: () => void; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ props?: Record; }; type CopyControlProps = ControlProps & { data?: string; dataSource?: string; dataHash?: string; toasterPlacement?: 'left' | 'right' | 'top' | 'bottom'; toasterText?: string; toasterDuration?: number; handleOutside?: boolean; }; export type ControlItemType = 'text' | 'icon'; export function CodeBlockControls({ children, className, title, controls, tabs, dropdown, }: CodeBlockControlsProps): JSX.Element | null { const { codeSnippet } = useThemeConfig(); const { useTelemetry, useTranslate } = useThemeHooks(); const { translate } = useTranslate(); const telemetry = useTelemetry(); const controlsType = (codeSnippet?.elementFormat as ControlItemType) || 'icon'; const { copy, expand, collapse, select, deselect, report } = controls || { copy: null, expand: null, collapse: null, select: null, deselect: null, report: null, }; const isEmptyTitle = !title && !tabs && !dropdown; const defaultControls = controls ? ( <> {title && {title}} {tabs && } {dropdown && } {report && !report.hidden && !report?.props?.hide ? ( : undefined} aria-label="Report a problem" {...report.props} > {controlsType != 'icon' && (report.props?.buttonText || 'Report')} ) : null} {expand && !codeSnippet?.expand?.hide ? ( : undefined} aria-label="Expand all" onClick={expand?.onClick} > {controlsType !== 'icon' && (expand?.label || 'Expand all')} ) : null} {collapse && !codeSnippet?.collapse?.hide ? ( : undefined} onClick={collapse?.onClick} aria-label="Collapse all" > {controlsType !== 'icon' && (expand?.label || 'Collapse all')} ) : null} {select ? ( : undefined} onClick={select?.onClick} > {controlsType !== 'icon' && select?.label ? select.label : 'Select all'} ) : null} {deselect ? ( : undefined} onClick={deselect?.onClick} > {controlsType !== 'icon' && deselect?.label ? deselect.label : 'Clear all'} ) : null} {copy && !codeSnippet?.copy?.hide ? ( { // If there already is a click handler, events should be handled there, cause they pass additional data if (copy?.onClick) { copy?.onClick?.(); } else { telemetry.sendCopyCodeSnippetClickedMessage([ { id: 'copyCodeSnippetButton', object: 'button', uri: 'urn:redocly:redoc:ui:button:copyCodeSnippetButton', snippetType: 'copy', }, ]); } }} /> ) : null} ) : null; return children || controls ? ( {children ? children : defaultControls} ) : null; } const ContainerWrapper = styled.div<{ $isEmptyTitle: boolean }>` display: grid; flex-direction: row; align-items: center; gap: var(--spacing-sm); font-size: var(--code-block-controls-font-size); font-family: var(--code-block-controls-font-family); background-color: var(--code-block-controls-bg-color); padding: var(--code-block-controls-padding); border-bottom: var(--code-block-controls-border); line-height: var(--line-height-lg); min-height: var(--control-height-base); grid-template-columns: 1fr auto; z-index: 1; ${({ $isEmptyTitle }) => $isEmptyTitle && ` position: absolute; right: 0; width: auto; border: none; background-color: transparent; grid-template-columns: 1fr; margin: 0; `} `; const Title = styled.span` display: flex; align-items: center; color: var(--code-block-controls-text-color); width: 100%; font-weight: var(--code-block-controls-font-weight); padding-left: var(--spacing-xs); `; const ControlsWrapper = styled.div` display: flex; text-align: right; align-items: center; gap: var(--spacing-xxs); justify-content: end; `; const ControlButton = styled(Button)` --button-icon-size: 16px; /* increase icon size for code blocks */ --button-icon-padding: 3px; --button-backdrop-filter: blur(8px); /* backdrop filter when there is text under the button */ & + & { margin-left: 0; } `; const StyledCopyButton = styled(CopyButton)` --button-icon-size: 16px; /* increase icon size for code blocks */ --button-icon-padding: 3px; --button-backdrop-filter: blur(8px); `;