Animate the opening and exiting states of the JSON visualizer (#10965)
I used `overflow-y: clip` instead of `overflow-y: hidden` because of this behavior: > Setting overflow to visible in one direction (i.e. overflow-x or overflow-y) when it isn't set to visible or clip in the other direction results in the visible value behaving as auto. ## Demo https://github.com/user-attachments/assets/b7975c99-58cc-4b63-b420-a54b27752188 Closes https://github.com/twentyhq/core-team-issues/issues/562
This commit is contained in:
committed by
GitHub
parent
981308861d
commit
eb5fb51c1b
@ -5,14 +5,15 @@ import { JsonArrow } from '@ui/json-visualizer/components/internal/JsonArrow';
|
|||||||
import { JsonList } from '@ui/json-visualizer/components/internal/JsonList';
|
import { JsonList } from '@ui/json-visualizer/components/internal/JsonList';
|
||||||
import { JsonNodeLabel } from '@ui/json-visualizer/components/internal/JsonNodeLabel';
|
import { JsonNodeLabel } from '@ui/json-visualizer/components/internal/JsonNodeLabel';
|
||||||
import { JsonNode } from '@ui/json-visualizer/components/JsonNode';
|
import { JsonNode } from '@ui/json-visualizer/components/JsonNode';
|
||||||
|
import { ANIMATION } from '@ui/theme';
|
||||||
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
import { JsonValue } from 'type-fest';
|
import { JsonValue } from 'type-fest';
|
||||||
|
|
||||||
const StyledContainer = styled.li`
|
const StyledContainer = styled.li`
|
||||||
list-style-type: none;
|
|
||||||
display: grid;
|
display: grid;
|
||||||
row-gap: ${({ theme }) => theme.spacing(2)};
|
list-style-type: none;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledLabelContainer = styled.div`
|
const StyledLabelContainer = styled.div`
|
||||||
@ -29,6 +30,8 @@ const StyledEmptyState = styled.div`
|
|||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const StyledJsonList = styled(JsonList)``.withComponent(motion.ul);
|
||||||
|
|
||||||
export const JsonNestedNode = ({
|
export const JsonNestedNode = ({
|
||||||
label,
|
label,
|
||||||
Icon,
|
Icon,
|
||||||
@ -51,7 +54,25 @@ export const JsonNestedNode = ({
|
|||||||
const [isOpen, setIsOpen] = useState(true);
|
const [isOpen, setIsOpen] = useState(true);
|
||||||
|
|
||||||
const renderedChildren = (
|
const renderedChildren = (
|
||||||
<JsonList depth={depth}>
|
<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 ? (
|
{elements.length === 0 ? (
|
||||||
<StyledEmptyState>{emptyElementsText}</StyledEmptyState>
|
<StyledEmptyState>{emptyElementsText}</StyledEmptyState>
|
||||||
) : (
|
) : (
|
||||||
@ -71,7 +92,7 @@ export const JsonNestedNode = ({
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
</JsonList>
|
</StyledJsonList>
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleArrowClick = () => {
|
const handleArrowClick = () => {
|
||||||
@ -79,7 +100,11 @@ export const JsonNestedNode = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (hideRoot) {
|
if (hideRoot) {
|
||||||
return <StyledContainer>{renderedChildren}</StyledContainer>;
|
return (
|
||||||
|
<StyledContainer>
|
||||||
|
<AnimatePresence initial={false}>{renderedChildren}</AnimatePresence>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -96,7 +121,9 @@ export const JsonNestedNode = ({
|
|||||||
)}
|
)}
|
||||||
</StyledLabelContainer>
|
</StyledLabelContainer>
|
||||||
|
|
||||||
{isOpen && renderedChildren}
|
<AnimatePresence initial={false}>
|
||||||
|
{isOpen && renderedChildren}
|
||||||
|
</AnimatePresence>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import styled from '@emotion/styled';
|
|||||||
import { VisibilityHidden } from '@ui/accessibility';
|
import { VisibilityHidden } from '@ui/accessibility';
|
||||||
import { IconChevronDown } from '@ui/display';
|
import { IconChevronDown } from '@ui/display';
|
||||||
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
|
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
|
||||||
|
import { ANIMATION } from '@ui/theme';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
const StyledButton = styled(motion.button)`
|
const StyledButton = styled(motion.button)`
|
||||||
@ -45,7 +46,8 @@ export const JsonArrow = ({
|
|||||||
size={theme.icon.size.md}
|
size={theme.icon.size.md}
|
||||||
color={theme.font.color.secondary}
|
color={theme.font.color.secondary}
|
||||||
initial={false}
|
initial={false}
|
||||||
animate={{ rotate: isOpen ? -180 : 0 }}
|
animate={{ rotate: isOpen ? 0 : -90 }}
|
||||||
|
transition={{ duration: ANIMATION.duration.normal }}
|
||||||
/>
|
/>
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -12,6 +12,10 @@ const StyledList = styled.ul<{ depth: number }>`
|
|||||||
depth > 0 &&
|
depth > 0 &&
|
||||||
css`
|
css`
|
||||||
padding-left: ${theme.spacing(8)};
|
padding-left: ${theme.spacing(8)};
|
||||||
|
|
||||||
|
> :first-of-type {
|
||||||
|
margin-top: ${theme.spacing(2)};
|
||||||
|
}
|
||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user