From 4ee97ec0b2927ee62550f114c776f77aca6943f2 Mon Sep 17 00:00:00 2001 From: thatmattlove Date: Sat, 18 Dec 2021 00:29:51 -0700 Subject: [PATCH] cleanup & animation improvement --- .../{util/animated.tsx => animated.ts} | 19 +++-------- .../ui/components/{util => }/dynamic-icon.tsx | 0 hyperglass/ui/components/header/header.tsx | 32 ++++++++----------- hyperglass/ui/components/header/title.tsx | 27 +++++++--------- hyperglass/ui/components/header/titleOnly.tsx | 6 ++-- hyperglass/ui/components/index.ts | 3 +- hyperglass/ui/components/layout/frame.tsx | 21 ++++++++---- hyperglass/ui/components/lookingGlass.tsx | 12 ++----- hyperglass/ui/components/util/index.ts | 2 -- hyperglass/ui/components/util/types.ts | 4 --- hyperglass/ui/hooks/useFormState.ts | 5 +++ 11 files changed, 57 insertions(+), 74 deletions(-) rename hyperglass/ui/components/{util/animated.tsx => animated.ts} (61%) rename hyperglass/ui/components/{util => }/dynamic-icon.tsx (100%) delete mode 100644 hyperglass/ui/components/util/index.ts delete mode 100644 hyperglass/ui/components/util/types.ts diff --git a/hyperglass/ui/components/util/animated.tsx b/hyperglass/ui/components/animated.ts similarity index 61% rename from hyperglass/ui/components/util/animated.tsx rename to hyperglass/ui/components/animated.ts index 39054ec..eec5d51 100644 --- a/hyperglass/ui/components/util/animated.tsx +++ b/hyperglass/ui/components/animated.ts @@ -1,5 +1,5 @@ -import { chakra, Box, forwardRef } from '@chakra-ui/react'; -import { motion, isValidMotionProp } from 'framer-motion'; +import { chakra } from '@chakra-ui/react'; +import { motion } from 'framer-motion'; import type { BoxProps } from '@chakra-ui/react'; import type { CustomDomComponent, Transition, MotionProps } from 'framer-motion'; @@ -10,19 +10,6 @@ type MakeMotionProps

= React.PropsWithChildren< Omit & Omit & { transition?: Transition } >; -/** - * Combined Chakra + Framer Motion component. - * @see https://chakra-ui.com/guides/integrations/with-framer - */ -export const AnimatedDiv = motion( - forwardRef>((props, ref) => { - const chakraProps = Object.fromEntries( - Object.entries(props).filter(([key]) => !isValidMotionProp(key)), - ); - return ; - }), -); - /** * Combine `chakra` and `motion` factories. * @@ -37,3 +24,5 @@ export function motionChakra

( // @ts-expect-error I don't know how to fix this. return motion

(chakra(component, options)); } + +export const AnimatedDiv = motionChakra('div'); diff --git a/hyperglass/ui/components/util/dynamic-icon.tsx b/hyperglass/ui/components/dynamic-icon.tsx similarity index 100% rename from hyperglass/ui/components/util/dynamic-icon.tsx rename to hyperglass/ui/components/dynamic-icon.tsx diff --git a/hyperglass/ui/components/header/header.tsx b/hyperglass/ui/components/header/header.tsx index 9f80bbd..338bc95 100644 --- a/hyperglass/ui/components/header/header.tsx +++ b/hyperglass/ui/components/header/header.tsx @@ -1,21 +1,18 @@ -import { useRef } from 'react'; import { Flex, ScaleFade } from '@chakra-ui/react'; -import { AnimatedDiv } from '~/components'; +import { motionChakra } from '~/components'; import { useBreakpointValue } from '~/context'; -import { useBooleanValue, useFormState } from '~/hooks'; +import { useBooleanValue, useFormInteractive } from '~/hooks'; import { Title } from './title'; -import type { THeader } from './types'; +const Wrapper = motionChakra('header', { + baseStyle: { display: 'flex', px: 4, pt: 6, minH: 16, w: 'full', flex: '0 1 auto' }, +}); -export const Header = (props: THeader): JSX.Element => { - const { resetForm, ...rest } = props; - - const status = useFormState(s => s.status); - - const titleRef = useRef({} as HTMLDivElement); +export const Header = (): JSX.Element => { + const formInteractive = useFormInteractive(); const titleWidth = useBooleanValue( - status === 'results', + formInteractive, { base: '75%', lg: '50%' }, { base: '75%', lg: '75%' }, ); @@ -23,21 +20,18 @@ export const Header = (props: THeader): JSX.Element => { const justify = useBreakpointValue({ base: 'flex-start', lg: 'center' }); return ( - + - - </AnimatedDiv> + </Flex> </ScaleFade> - </Flex> + </Wrapper> ); }; diff --git a/hyperglass/ui/components/header/title.tsx b/hyperglass/ui/components/header/title.tsx index aec01c2..150f0d0 100644 --- a/hyperglass/ui/components/header/title.tsx +++ b/hyperglass/ui/components/header/title.tsx @@ -3,7 +3,7 @@ import { motion } from 'framer-motion'; import { isSafari } from 'react-device-detect'; import { Switch, Case } from 'react-if'; import { useConfig, useMobile } from '~/context'; -import { useBooleanValue, useFormState } from '~/hooks'; +import { useFormState, useFormInteractive } from '~/hooks'; import { SubtitleOnly } from './subtitleOnly'; import { TitleOnly } from './titleOnly'; import { Logo } from './logo'; @@ -11,17 +11,18 @@ import { Logo } from './logo'; import type { TTitle, TTitleWrapper, TDWrapper, TMWrapper } from './types'; const AnimatedVStack = motion(VStack); +const AnimatedFlex = motion(Flex); /** * Title wrapper for mobile devices, breakpoints sm & md. */ const MWrapper = (props: TMWrapper): JSX.Element => { - const status = useFormState(s => s.status); + const formInteractive = useFormInteractive(); return ( <AnimatedVStack layout spacing={1} - alignItems={status === 'results' ? 'center' : 'flex-start'} + alignItems={formInteractive ? 'center' : 'flex-start'} {...props} /> ); @@ -31,13 +32,13 @@ const MWrapper = (props: TMWrapper): JSX.Element => { * Title wrapper for desktop devices, breakpoints lg & xl. */ const DWrapper = (props: TDWrapper): JSX.Element => { - const status = useFormState(s => s.status); + const formInteractive = useFormInteractive(); return ( <AnimatedVStack spacing={1} initial="main" alignItems="center" - animate={status} + animate={formInteractive} transition={{ damping: 15, type: 'spring', stiffness: 100 }} variants={{ results: { scale: 0.5 }, form: { scale: 1 } }} {...props} @@ -108,19 +109,15 @@ export const Title = (props: TTitle): JSX.Element => { const { web } = useConfig(); const { titleMode } = web.text; - const { status, reset } = useFormState(({ status, reset }) => ({ - status, - reset, - })); - - const titleHeight = useBooleanValue(status === 'results', undefined, { md: '20vh' }); + const reset = useFormState(s => s.reset); + const formInteractive = useFormInteractive(); return ( - <Flex + <AnimatedFlex px={0} flexWrap="wrap" flexDir="column" - minH={titleHeight} + animate={{ height: formInteractive ? undefined : '20vh' }} justifyContent="center" /* flexBasis This is a fix for Safari specifically. LMGTFY: Safari flex-basis width. Nutshell: Safari @@ -129,7 +126,7 @@ export const Title = (props: TTitle): JSX.Element => { div up to the parent's max-width. The fix is to hard-code a flex-basis width. */ flexBasis={{ base: '100%', lg: isSafari ? '33%' : '100%' }} - mt={{ md: status === 'results' ? undefined : 'auto' }} + mt={{ md: formInteractive ? undefined : 'auto' }} {...rest} > <Button @@ -156,6 +153,6 @@ export const Title = (props: TTitle): JSX.Element => { </Case> </Switch> </Button> - </Flex> + </AnimatedFlex> ); }; diff --git a/hyperglass/ui/components/header/titleOnly.tsx b/hyperglass/ui/components/header/titleOnly.tsx index 7957937..91907e3 100644 --- a/hyperglass/ui/components/header/titleOnly.tsx +++ b/hyperglass/ui/components/header/titleOnly.tsx @@ -1,12 +1,12 @@ import { Heading } from '@chakra-ui/react'; import { useConfig } from '~/context'; -import { useBooleanValue, useFormState } from '~/hooks'; +import { useBooleanValue, useFormInteractive } from '~/hooks'; import { useTitleSize } from './useTitleSize'; export const TitleOnly = (): JSX.Element => { const { web } = useConfig(); - const status = useFormState(s => s.status); - const margin = useBooleanValue(status === 'results', 0, 2); + const formInteractive = useFormInteractive(); + const margin = useBooleanValue(formInteractive, 0, 2); const sizeSm = useTitleSize(web.text.title, '2xl', []); return ( diff --git a/hyperglass/ui/components/index.ts b/hyperglass/ui/components/index.ts index 4084e00..924556d 100644 --- a/hyperglass/ui/components/index.ts +++ b/hyperglass/ui/components/index.ts @@ -1,8 +1,10 @@ +export * from './animated'; export * from './card'; export * from './codeBlock'; export * from './countdown'; export * from './custom'; export * from './debugger'; +export * from './dynamic-icon'; export * from './favicon'; export * from './footer'; export * from './form'; @@ -23,4 +25,3 @@ export * from './results'; export * from './select'; export * from './submit'; export * from './table'; -export * from './util'; diff --git a/hyperglass/ui/components/layout/frame.tsx b/hyperglass/ui/components/layout/frame.tsx index e0e7eef..c403b94 100644 --- a/hyperglass/ui/components/layout/frame.tsx +++ b/hyperglass/ui/components/layout/frame.tsx @@ -1,5 +1,6 @@ import { useCallback, useRef } from 'react'; import { Flex } from '@chakra-ui/react'; +import { motion } from 'framer-motion'; import { isSafari } from 'react-device-detect'; import { If, Then } from 'react-if'; import { Debugger, Greeting, Footer, Header } from '~/components'; @@ -9,6 +10,8 @@ import { ResetButton } from './resetButton'; import type { TFrame } from './types'; +const AnimatedFlex = motion(Flex); + export const Frame = (props: TFrame): JSX.Element => { const { developerMode } = useConfig(); const { setStatus, reset } = useFormState( @@ -38,19 +41,25 @@ export const Frame = (props: TFrame): JSX.Element => { */ minHeight={isSafari ? '-webkit-fill-available' : '100vh'} > - <Header resetForm={() => handleReset()} /> - <Flex + <Header /> + <AnimatedFlex + layout px={4} py={0} w="100%" as="main" - align="center" flex="1 1 auto" - justify="start" flexDir="column" textAlign="center" - {...props} - /> + alignItems="center" + justifyContent="start" + animate={{ opacity: 1, y: 0 }} + transition={{ duration: 0.3 }} + exit={{ opacity: 0, x: -300 }} + initial={{ opacity: 0, y: 300 }} + > + {props.children} + </AnimatedFlex> <Footer /> <If condition={developerMode}> <Then> diff --git a/hyperglass/ui/components/lookingGlass.tsx b/hyperglass/ui/components/lookingGlass.tsx index a220bd3..9e5e944 100644 --- a/hyperglass/ui/components/lookingGlass.tsx +++ b/hyperglass/ui/components/lookingGlass.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo } from 'react'; import isEqual from 'react-fast-compare'; -import { Flex, ScaleFade, SlideFade } from '@chakra-ui/react'; +import { chakra, Flex, ScaleFade, SlideFade } from '@chakra-ui/react'; import { FormProvider, useForm } from 'react-hook-form'; import { vestResolver } from '@hookform/resolvers/vest'; import vest, { test, enforce } from 'vest'; @@ -9,7 +9,6 @@ import { FormField, HelpModal, QueryType, - AnimatedDiv, QueryTarget, SubmitButton, QueryLocation, @@ -169,17 +168,12 @@ export const LookingGlass = (): JSX.Element => { return ( <FormProvider {...formInstance}> - <AnimatedDiv + <chakra.form p={0} my={4} w="100%" - as="form" mx="auto" textAlign="left" - animate={{ opacity: 1, y: 0 }} - transition={{ duration: 0.3 }} - exit={{ opacity: 0, x: -300 }} - initial={{ opacity: 0, y: 300 }} maxW={{ base: '100%', lg: '75%' }} onSubmit={handleSubmit(submitHandler)} > @@ -232,7 +226,7 @@ export const LookingGlass = (): JSX.Element => { </ScaleFade> </Flex> </FormRow> - </AnimatedDiv> + </chakra.form> </FormProvider> ); }; diff --git a/hyperglass/ui/components/util/index.ts b/hyperglass/ui/components/util/index.ts deleted file mode 100644 index c693265..0000000 --- a/hyperglass/ui/components/util/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './animated'; -export * from './dynamic-icon'; diff --git a/hyperglass/ui/components/util/types.ts b/hyperglass/ui/components/util/types.ts deleted file mode 100644 index 106caa5..0000000 --- a/hyperglass/ui/components/util/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface TIf { - c: boolean; - children?: React.ReactNode; -} diff --git a/hyperglass/ui/hooks/useFormState.ts b/hyperglass/ui/hooks/useFormState.ts index 99c4052..2292e13 100644 --- a/hyperglass/ui/hooks/useFormState.ts +++ b/hyperglass/ui/hooks/useFormState.ts @@ -235,3 +235,8 @@ export function useView(): FormStatus { return ready ? 'results' : 'form'; }, [status, form]); } + +export function useFormInteractive(): boolean { + const { status, selections } = useFormState(({ status, selections }) => ({ status, selections })); + return status === 'results' || selections.queryLocation.length > 0; +}