From eb5fb51c1bec4ccf4ceedfb9e4f9905867416f95 Mon Sep 17 00:00:00 2001 From: Baptiste Devessier Date: Tue, 18 Mar 2025 12:05:10 +0100 Subject: [PATCH] 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 --- .../components/JsonNestedNode.tsx | 39 ++++++++++++++++--- .../components/internal/JsonArrow.tsx | 4 +- .../components/internal/JsonList.tsx | 4 ++ 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/packages/twenty-ui/src/json-visualizer/components/JsonNestedNode.tsx b/packages/twenty-ui/src/json-visualizer/components/JsonNestedNode.tsx index 29c08d3fa..fe9775f8f 100644 --- a/packages/twenty-ui/src/json-visualizer/components/JsonNestedNode.tsx +++ b/packages/twenty-ui/src/json-visualizer/components/JsonNestedNode.tsx @@ -5,14 +5,15 @@ 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 { JsonNode } from '@ui/json-visualizer/components/JsonNode'; +import { ANIMATION } from '@ui/theme'; +import { AnimatePresence, motion } from 'framer-motion'; import { useState } from 'react'; import { isDefined } from 'twenty-shared'; import { JsonValue } from 'type-fest'; const StyledContainer = styled.li` - list-style-type: none; display: grid; - row-gap: ${({ theme }) => theme.spacing(2)}; + list-style-type: none; `; const StyledLabelContainer = styled.div` @@ -29,6 +30,8 @@ const StyledEmptyState = styled.div` color: ${({ theme }) => theme.font.color.tertiary}; `; +const StyledJsonList = styled(JsonList)``.withComponent(motion.ul); + export const JsonNestedNode = ({ label, Icon, @@ -51,7 +54,25 @@ export const JsonNestedNode = ({ const [isOpen, setIsOpen] = useState(true); const renderedChildren = ( - + {elements.length === 0 ? ( {emptyElementsText} ) : ( @@ -71,7 +92,7 @@ export const JsonNestedNode = ({ ); }) )} - + ); const handleArrowClick = () => { @@ -79,7 +100,11 @@ export const JsonNestedNode = ({ }; if (hideRoot) { - return {renderedChildren}; + return ( + + {renderedChildren} + + ); } return ( @@ -96,7 +121,9 @@ export const JsonNestedNode = ({ )} - {isOpen && renderedChildren} + + {isOpen && renderedChildren} + ); }; diff --git a/packages/twenty-ui/src/json-visualizer/components/internal/JsonArrow.tsx b/packages/twenty-ui/src/json-visualizer/components/internal/JsonArrow.tsx index 6b4bf720b..46ed51793 100644 --- a/packages/twenty-ui/src/json-visualizer/components/internal/JsonArrow.tsx +++ b/packages/twenty-ui/src/json-visualizer/components/internal/JsonArrow.tsx @@ -3,6 +3,7 @@ import styled from '@emotion/styled'; import { VisibilityHidden } from '@ui/accessibility'; import { IconChevronDown } from '@ui/display'; import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow'; +import { ANIMATION } from '@ui/theme'; import { motion } from 'framer-motion'; const StyledButton = styled(motion.button)` @@ -45,7 +46,8 @@ export const JsonArrow = ({ size={theme.icon.size.md} color={theme.font.color.secondary} initial={false} - animate={{ rotate: isOpen ? -180 : 0 }} + animate={{ rotate: isOpen ? 0 : -90 }} + transition={{ duration: ANIMATION.duration.normal }} /> ); diff --git a/packages/twenty-ui/src/json-visualizer/components/internal/JsonList.tsx b/packages/twenty-ui/src/json-visualizer/components/internal/JsonList.tsx index 55c9103e5..9fb23958d 100644 --- a/packages/twenty-ui/src/json-visualizer/components/internal/JsonList.tsx +++ b/packages/twenty-ui/src/json-visualizer/components/internal/JsonList.tsx @@ -12,6 +12,10 @@ const StyledList = styled.ul<{ depth: number }>` depth > 0 && css` padding-left: ${theme.spacing(8)}; + + > :first-of-type { + margin-top: ${theme.spacing(2)}; + } `} `;