import React, { useEffect } from 'react'; import styled from 'styled-components'; import type { JSX } from 'react'; import { Button } from '@redocly/theme/components/Button/Button'; import { ChevronDownIcon } from '@redocly/theme/icons/ChevronDownIcon/ChevronDownIcon'; export function JsonValue(props: { /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ value: any; level: number; standalone?: boolean; // if true, will render the opening and closing punctuation (when it's an item inside array or top-level) defaultExpandLevel: number; expandAllSignal: boolean | undefined; }): React.ReactNode { const { standalone, defaultExpandLevel, level, value, expandAllSignal } = props; const indent = ' '.repeat(level * 2); const [isExpandedState, setExpanded] = React.useState( expandAllSignal ?? level < defaultExpandLevel, ); const isExpanded = expandAllSignal !== undefined && level > 0 ? expandAllSignal : isExpandedState; useEffect(() => { if (expandAllSignal !== undefined) { if (expandAllSignal === false && level === 0) { return; } setExpanded(expandAllSignal); } }, [expandAllSignal, level]); const keys: string[] = (value && Object.keys(value).filter((key) => value[key] !== undefined)) || []; // filter out undefined properties const valueType = typeof value; const isPrimitive = (valueType !== 'object' && value !== null) || keys.length === 0; if (isPrimitive) { return ( <> {standalone ? indent : null} {JSON.stringify(value)} ); } const openingPunctuation = Array.isArray(value) ? '[' : '{'; const closingPunctuation = Array.isArray(value) ? ']' : '}'; const valueElement = Array.isArray(value) ? value.map((item, index) => ( {index < value.length - 1 ? ',\n' : null} )) : keys.map((key, index) => ( {index < keys.length - 1 ? ',\n' : null} )); const openingPunctuationElement = standalone ? ( <> {indent} {level > 0 ? ( setExpanded((v) => !v)} isExpanded={isExpanded} /> ) : null} {openingPunctuation} {isExpanded ? '\n' : null} ) : null; const closingPunctuationElement = standalone ? ( <> {isExpanded ? '\n' : ''} {isExpanded ? indent : ''} {closingPunctuation} ) : null; return ( <> {openingPunctuationElement} {isExpanded || !standalone ? valueElement : ' … '} {closingPunctuationElement} ); } function JsonKeyValue(props: { name: string; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ value: any; level: number; defaultExpandLevel: number; expandAllSignal: boolean | undefined; }): React.ReactNode { const { defaultExpandLevel, level, name, value, expandAllSignal } = props; const indent = ' '.repeat(level * 2); const keys: string[] = (value && Object.keys(value).filter((key) => value[key] !== undefined)) || []; // filter out undefined properties const isCollapsible = typeof value === 'object' && value !== null && keys.length > 0; const [isExpandedState, setExpanded] = React.useState( expandAllSignal ?? level < defaultExpandLevel, ); const isExpanded = expandAllSignal !== undefined ? expandAllSignal : isExpandedState; useEffect(() => { if (expandAllSignal !== undefined) { setExpanded(expandAllSignal); } }, [expandAllSignal]); const openingPunctuation = Array.isArray(value) ? '[' : '{'; const closingPunctuation = Array.isArray(value) ? ']' : '}'; const valueElement = isCollapsible ? ( <> {openingPunctuation} {isExpanded ? ( <> {'\n'} {'\n'} ) : ( ' … ' )} {isExpanded ? indent : null} {closingPunctuation} ) : ( ); return ( <> {indent} {isCollapsible ? ( setExpanded((v) => !v)} isExpanded={isExpanded} /> ) : null} {`"${name}"`}: {valueElement} ); } function ExpandIcon({ isExpanded, onClick, }: { onClick: () => void; isExpanded: boolean; }): JSX.Element { return ( } /> ); } const ExpandButton = styled(Button)` position: absolute; left: -10px; user-select: none; & > svg { transform: rotate(270deg); } &.expanded > svg { transform: none; } &&.button-size-small { --button-icon-size: 14px; --button-icon-padding: 3px; margin-left: 0; } `;