From 1f1ca05a41cdbc67264989ed3f63ea6ea02138ed Mon Sep 17 00:00:00 2001 From: Bahaa Tuffaha Date: Fri, 8 Nov 2024 19:26:08 +0200 Subject: [PATCH] 2808: Added Accordion Animation for EventsDateFilter --- native/src/components/Accordion.tsx | 52 ++++++++++++++++++++++ native/src/components/EventsDateFilter.tsx | 13 +++--- web/src/components/Accordion.tsx | 33 ++++++++++++++ web/src/components/DatePicker.tsx | 11 +---- web/src/components/EventsDateFilter.tsx | 13 +++--- web/src/components/FilterToggle.tsx | 1 - 6 files changed, 101 insertions(+), 22 deletions(-) create mode 100644 native/src/components/Accordion.tsx create mode 100644 web/src/components/Accordion.tsx diff --git a/native/src/components/Accordion.tsx b/native/src/components/Accordion.tsx new file mode 100644 index 0000000000..71e58b17c2 --- /dev/null +++ b/native/src/components/Accordion.tsx @@ -0,0 +1,52 @@ +import React, { ReactElement } from 'react' +import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native' +import Animated, { useAnimatedStyle, useDerivedValue, useSharedValue, withTiming } from 'react-native-reanimated' + +// For some reason I couldn't replicate this styling in styled-components +const styles = StyleSheet.create({ + wrapper: { + width: '100%', + position: 'absolute', + display: 'flex', + alignItems: 'center', + }, + animatedView: { + width: '100%', + overflow: 'hidden', + }, +}) + +type AccordionProps = { + isOpen: boolean + style?: StyleProp + children: React.ReactNode + duration?: number + viewKey: string +} + +const defaultDuration = 500 + +const Accordion = ({ isOpen, style, duration = defaultDuration, children, viewKey }: AccordionProps): ReactElement => { + const height = useSharedValue(0) + const derivedHeight = useDerivedValue(() => + withTiming(height.value * Number(isOpen), { + duration, + }), + ) + const bodyStyle = useAnimatedStyle(() => ({ + height: derivedHeight.value, + })) + return ( + + { + height.value = e.nativeEvent.layout.height + }} + style={styles.wrapper}> + {children} + + + ) +} + +export default Accordion diff --git a/native/src/components/EventsDateFilter.tsx b/native/src/components/EventsDateFilter.tsx index 176db38f06..e673373ee2 100644 --- a/native/src/components/EventsDateFilter.tsx +++ b/native/src/components/EventsDateFilter.tsx @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next' import styled from 'styled-components/native' import { CloseIcon } from '../assets' +import Accordion from './Accordion' import CalendarRangeModal from './CalendarRangeModal' import DatePicker from './DatePicker' import FilterToggle from './FilterToggle' @@ -14,7 +15,7 @@ const DateSection = styled.View` display: flex; flex-direction: column; gap: 12px; - margin: 0 5px 15px; + margin: 15px 5px; align-items: center; ` @@ -84,9 +85,9 @@ const EventsDateFilter = ({ setStartDate={setStartDate} currentInput={currentInput.current} /> - - - {showDateFilter && ( + + + <> - )} - + + <> {(startDate || endDate) && ( ` + overflow: hidden; + transition: height 0.3s ease; + height: ${props => props.height}px; + width: 100%; +` + +type AccordionProps = { + isOpen: boolean + children: React.ReactNode +} + +const Accordion = ({ isOpen, children }: AccordionProps): ReactElement => { + const [height, setHeight] = useState(0) + const contentRef = useRef(null) + + useEffect(() => { + if (contentRef.current) { + setHeight(isOpen ? contentRef.current.scrollHeight : 0) + } + }, [isOpen, children]) + + return ( + + {children} + + ) +} + +export default Accordion diff --git a/web/src/components/DatePicker.tsx b/web/src/components/DatePicker.tsx index 201edcfd99..9b29aecc6a 100644 --- a/web/src/components/DatePicker.tsx +++ b/web/src/components/DatePicker.tsx @@ -4,11 +4,8 @@ import { useTranslation } from 'react-i18next' import styled from 'styled-components' import { CalendarTodayIcon } from '../assets' -import dimensions from '../constants/dimensions' -const INPUT_MIN_WIDTH = '316px' const INPUT_HEIGHT = '56px' -const INPUT_MIN_WIDTH_ON_MID_VIEWPORT = '240px' const DateContainer = styled.div` width: fit-content; @@ -16,7 +13,7 @@ const DateContainer = styled.div` ` const StyledInput = styled.input` - min-width: ${INPUT_MIN_WIDTH}; + width: 240px; height: ${INPUT_HEIGHT}; padding: 0 16px; border-radius: 8px; @@ -43,10 +40,6 @@ const StyledInput = styled.input` background-color: ${props => props.theme.colors.themeColorLight}; } } - - @media ${dimensions.mediumViewport} { - min-width: ${INPUT_MIN_WIDTH_ON_MID_VIEWPORT}; - } ` const StyledTitle = styled.span` @@ -84,7 +77,7 @@ const isValidIsoDate = (date: string): boolean => { const DatePicker = ({ title, date, setDate, error }: DatePickerProps): ReactElement => { const { t } = useTranslation('events') const [tempDate, setTempDate] = useState(date?.toISODate() ?? '') - const isInvalidDate = tempDate !== '' && isValidIsoDate(tempDate) === false + const isInvalidDate = !!tempDate && !isValidIsoDate(tempDate) const shownError = error || (isInvalidDate ? t('invalidToDate') : undefined) useEffect(() => { setTempDate(date?.toISODate() ?? '') diff --git a/web/src/components/EventsDateFilter.tsx b/web/src/components/EventsDateFilter.tsx index 5210050c94..79c6e2e034 100644 --- a/web/src/components/EventsDateFilter.tsx +++ b/web/src/components/EventsDateFilter.tsx @@ -5,6 +5,7 @@ import styled from 'styled-components' import { CloseIcon } from '../assets' import dimensions from '../constants/dimensions' +import Accordion from './Accordion' import DatePicker from './DatePicker' import FilterToggle from './FilterToggle' import Button from './base/Button' @@ -13,8 +14,8 @@ import Icon from './base/Icon' const DateSection = styled.div` display: flex; gap: 10px; - margin: 0 5px 15px; - justify-content: center; + margin: 15px 5px; + justify-content: space-evenly; @media ${dimensions.smallViewport} { flex-direction: column; @@ -63,8 +64,8 @@ const EventsDateFilter = ({ return ( <> - - {showDateFilter && ( + + <> - )} - + + {(startDate || endDate) && (