Small optimization for faster loading (gaining ~80ms - average time of a click) It might seem a little over-engineered but there are a lot of edge cases and I couldn't find a simpler solution I also tried to tackle Link Chips but it's more complex so this will be for another PR
139 lines
3.7 KiB
TypeScript
139 lines
3.7 KiB
TypeScript
import styled from '@emotion/styled';
|
|
import { isNonEmptyString } from '@sniptt/guards';
|
|
import { IconComponent } from '@ui/display';
|
|
import { JsonArrow } from '@ui/json-visualizer/components/internal/JsonArrow';
|
|
import { JsonList } from '@ui/json-visualizer/components/internal/JsonList';
|
|
import { JsonNodeLabel } from '@ui/json-visualizer/components/internal/JsonNodeLabel';
|
|
import { JsonNodeValue } from '@ui/json-visualizer/components/internal/JsonNodeValue';
|
|
import { JsonNode } from '@ui/json-visualizer/components/JsonNode';
|
|
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
|
|
import { JsonNodeHighlighting } from '@ui/json-visualizer/types/JsonNodeHighlighting';
|
|
import { ANIMATION } from '@ui/theme';
|
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
import { useState } from 'react';
|
|
import { isDefined } from 'twenty-shared/utils';
|
|
import { JsonValue } from 'type-fest';
|
|
|
|
const StyledContainer = styled.li`
|
|
display: grid;
|
|
list-style-type: none;
|
|
`;
|
|
|
|
const StyledLabelContainer = styled.div`
|
|
display: flex;
|
|
align-items: center;
|
|
gap: ${({ theme }) => theme.spacing(2)};
|
|
`;
|
|
|
|
const StyledElementsCount = styled.span`
|
|
color: ${({ theme }) => theme.font.color.tertiary};
|
|
`;
|
|
|
|
const StyledJsonList = styled(JsonList)``.withComponent(motion.ul);
|
|
|
|
export const JsonNestedNode = ({
|
|
label,
|
|
Icon,
|
|
elements,
|
|
renderElementsCount,
|
|
emptyElementsText,
|
|
depth,
|
|
keyPath,
|
|
highlighting,
|
|
}: {
|
|
label?: string;
|
|
Icon: IconComponent;
|
|
elements: Array<{ id: string | number; label: string; value: JsonValue }>;
|
|
renderElementsCount?: (count: number) => string;
|
|
emptyElementsText: string;
|
|
depth: number;
|
|
keyPath: string;
|
|
highlighting?: JsonNodeHighlighting | undefined;
|
|
}) => {
|
|
const { shouldExpandNodeInitially } = useJsonTreeContextOrThrow();
|
|
|
|
const hideRoot = !isDefined(label);
|
|
|
|
const [isOpen, setIsOpen] = useState(
|
|
shouldExpandNodeInitially({ keyPath, depth }),
|
|
);
|
|
|
|
const renderedChildren = (
|
|
<StyledJsonList
|
|
initial={{
|
|
height: 0,
|
|
opacity: 0,
|
|
overflowY: 'clip',
|
|
}}
|
|
animate={{
|
|
height: 'auto',
|
|
opacity: 1,
|
|
overflowY: 'clip',
|
|
}}
|
|
exit={{
|
|
height: 0,
|
|
opacity: 0,
|
|
overflowY: 'clip',
|
|
}}
|
|
transition={{ duration: ANIMATION.duration.normal }}
|
|
depth={depth}
|
|
>
|
|
{elements.length === 0 ? (
|
|
<JsonNodeValue valueAsString={emptyElementsText} />
|
|
) : (
|
|
elements.map(({ id, label, value }) => {
|
|
const nextKeyPath = isNonEmptyString(keyPath)
|
|
? `${keyPath}.${id}`
|
|
: String(id);
|
|
|
|
return (
|
|
<JsonNode
|
|
key={id}
|
|
label={label}
|
|
value={value}
|
|
depth={depth + 1}
|
|
keyPath={nextKeyPath}
|
|
/>
|
|
);
|
|
})
|
|
)}
|
|
</StyledJsonList>
|
|
);
|
|
|
|
const handleArrowClick = () => {
|
|
setIsOpen(!isOpen);
|
|
};
|
|
|
|
if (hideRoot) {
|
|
return (
|
|
<StyledContainer>
|
|
<AnimatePresence initial={false}>{renderedChildren}</AnimatePresence>
|
|
</StyledContainer>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<StyledContainer>
|
|
<StyledLabelContainer>
|
|
<JsonArrow
|
|
isOpen={isOpen}
|
|
onClick={handleArrowClick}
|
|
variant={highlighting === 'partial-blue' ? 'blue' : undefined}
|
|
/>
|
|
|
|
<JsonNodeLabel label={label} Icon={Icon} />
|
|
|
|
{renderElementsCount && (
|
|
<StyledElementsCount>
|
|
{renderElementsCount(elements.length)}
|
|
</StyledElementsCount>
|
|
)}
|
|
</StyledLabelContainer>
|
|
|
|
<AnimatePresence initial={false}>
|
|
{isOpen && renderedChildren}
|
|
</AnimatePresence>
|
|
</StyledContainer>
|
|
);
|
|
};
|