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;
}
`;