Skip to content

Commit

Permalink
feat(motion): add initial presets code (#2360)
Browse files Browse the repository at this point in the history
* feat: init motion presets rfc

* docs: add poc video

* docs: add comparison table and pocs

* feat: add gsap poc

* add layout animations poc with blade components

* feat: add basic API decision

* feat: add api decisions and memes

* feat: add api decisions and memes

* feat: remove unrelated changes

* docs: add morph note

* feat: add video example

* docs: add note for previews

* docs: add more videos

* fix: images

* fix: code alignments

* docs: fix widths of cols

* feat: add chat interface demo

* typo

* fix: width of previews

* feat: update all token values

* feat: motion, migrate internal motion tokens

* fix: ts check

* fix: ts

* fix: switch delay

* feat: add base entry exit presets

* fix: example card alignment

* feat: add stagger component

* feat: add animateInteractions

* refactor: use common BaseMotionBox

* refactor: move stagger and animateinteraction check

* fix: durations map

* feat: add morph and scale preset

* feat: add Slide

* refactor: remove unused code add todo

* docs: update animationInteractions docs

* feat: add css bezier function

* feat: add view transitions API note

* feat: add view transitions API note

* feat: add controled scale, enhancer animateinteraction

* feat: replace framer-motion imports with motion/react

* feat: rename framer motion to motion/react

* feat: add framer motion name change note in library table

* docs: add new open questions and conclusions

* fix: change misleading scale heading

* feat: add fade token values

* fix(AnimateInteractions): a11y focus issues

* feat: add token valyes for move

* feat: add slide tokens

* fix: stories

* fix: typecheck

* feat: add refs to components till checkbox

* feat: add refs till radio

* feat: migration to ref till typography

* feat: add withRef story

* fix: scale box

* feat: add shouldUnmountWhenHidden

* feat: handle no unmount transitions in stagger

* feat: add slideFromOffset prop

* refactor: simplify basemotion

* feat: add memo for variants object

* fix: focus on animate interactions

* fix: resolve anurag's comments

* feat: add comments for getOuterMotionRef

* feat: add delay prop

* fix: stagger type

* feat: support framer-motion v4

* feat: add borderRadius and backgroundColor morph support

* feat: remove reduced motion handling
  • Loading branch information
saurabhdaware authored Dec 9, 2024
1 parent d432350 commit 12e1c2f
Show file tree
Hide file tree
Showing 96 changed files with 2,338 additions and 490 deletions.
22 changes: 13 additions & 9 deletions packages/blade/.storybook/react/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { DocsContainer } from '@storybook/addon-docs';
import React from 'react';
import { MINIMAL_VIEWPORTS } from '@storybook/addon-viewport';
import './global.css';
import { domMax, LazyMotion } from 'framer-motion';

export const parameters = {
// disable snapshot by default and then enable it only for kitchen sink
Expand Down Expand Up @@ -180,15 +181,18 @@ export const decorators = [
return (
<ErrorBoundary>
<GlobalStyle />
<BladeProvider
key={`${context.globals.themeTokenName}-${context.globals.colorScheme}`}
themeTokens={getThemeTokens()}
colorScheme={context.globals.colorScheme}
>
<StoryCanvas context={context}>
<Story />
</StoryCanvas>
</BladeProvider>
{/* strict in LazyMotion will make sure we don't use excessive `motion` component in blade components and instead use light weight `m` */}
<LazyMotion strict features={domMax}>
<BladeProvider
key={`${context.globals.themeTokenName}-${context.globals.colorScheme}`}
themeTokens={getThemeTokens()}
colorScheme={context.globals.colorScheme}
>
<StoryCanvas context={context}>
<Story />
</StoryCanvas>
</BladeProvider>
</LazyMotion>
</ErrorBoundary>
);
},
Expand Down
2 changes: 2 additions & 0 deletions packages/blade/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@
"fastify": "4.28.1",
"figures": "3.2.0",
"flat": "5.0.2",
"motion": "11.12.0",
"globby": "14.0.1",
"ismobilejs": "1.1.1",
"jest": "29.6.1",
Expand Down Expand Up @@ -292,6 +293,7 @@
"react": ">=18",
"react-dom": ">=18",
"styled-components": "^5",
"framer-motion": ">=4",
"react-native": "^0.72",
"@floating-ui/react-native": "^0.10.0",
"react-native-reanimated": "^3.4.1",
Expand Down
32 changes: 20 additions & 12 deletions packages/blade/src/components/Accordion/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from 'react';
import type { ReactElement } from 'react';
import { useCallback, useMemo, useState, cloneElement, Children } from 'react';
import type { AccordionContextState } from './AccordionContext';
Expand All @@ -10,6 +11,7 @@ import type { BoxProps } from '~components/Box';
import { size as sizeTokens } from '~tokens/global';
import { makeSize } from '~utils';
import { metaAttribute, MetaConstants } from '~utils/metaAttribute';
import type { BladeElementRef } from '~utils/types';

const MIN_WIDTH: BoxProps['minWidth'] = {
s: makeSize(sizeTokens[200]),
Expand Down Expand Up @@ -61,18 +63,21 @@ const getVariantStyles = (variant: AccordionProps['variant']): BoxProps => {
* Checkout https://blade.razorpay.com/?path=/docs/components-accordion--docs
*
*/
const Accordion = ({
defaultExpandedIndex,
expandedIndex,
onExpandChange,
showNumberPrefix = false,
children,
variant = 'transparent',
size = 'large',
maxWidth,
testID,
...styledProps
}: AccordionProps): ReactElement => {
const _Accordion = (
{
defaultExpandedIndex,
expandedIndex,
onExpandChange,
showNumberPrefix = false,
children,
variant = 'transparent',
size = 'large',
maxWidth,
testID,
...styledProps
}: AccordionProps,
ref: React.Ref<BladeElementRef>,
): ReactElement => {
const [expandedAccordionItemIndex, setExpandedAccordionItemIndex] = useState<number | undefined>(
defaultExpandedIndex,
);
Expand Down Expand Up @@ -118,6 +123,7 @@ const Accordion = ({
return (
<AccordionContext.Provider value={accordionContext}>
<BaseBox
ref={ref as never}
{...metaAttribute({ name: MetaConstants.Accordion, testID })}
{...getStyledProps(styledProps)}
>
Expand All @@ -136,4 +142,6 @@ const Accordion = ({
);
};

const Accordion = React.forwardRef(_Accordion);

export { Accordion };
36 changes: 21 additions & 15 deletions packages/blade/src/components/Alert/Alert.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ReactChild, ReactElement } from 'react';
import { Fragment, useState } from 'react';
import React, { Fragment, useState, forwardRef } from 'react';

import { StyledAlert } from './StyledAlert';
import type { IconComponent } from '~components/Icons';
Expand All @@ -21,7 +21,7 @@ import BaseButton from '~components/Button/BaseButton';
import { BaseLink } from '~components/Link/BaseLink';
import type { FeedbackColors, SubtleOrIntense } from '~tokens/theme/theme';
import { useTheme } from '~components/BladeProvider';
import type { DotNotationSpacingStringToken, TestID } from '~utils/types';
import type { BladeElementRef, DotNotationSpacingStringToken, TestID } from '~utils/types';
import { makeAccessible } from '~utils/makeAccessible';

type PrimaryAction = {
Expand Down Expand Up @@ -124,19 +124,22 @@ const intentIconMap = {
notice: AlertTriangleIcon,
};

const Alert = ({
description,
title,
isDismissible = true,
onDismiss,
emphasis = 'subtle',
isFullWidth = false,
color = 'neutral',
actions,
testID,
icon,
...styledProps
}: AlertProps): ReactElement | null => {
const _Alert = (
{
description,
title,
isDismissible = true,
onDismiss,
emphasis = 'subtle',
isFullWidth = false,
color = 'neutral',
actions,
testID,
icon,
...styledProps
}: AlertProps,
ref: React.Ref<BladeElementRef>,
): ReactElement | null => {
const { theme } = useTheme();
const { matchedDeviceType } = useBreakpoint({ breakpoints: theme.breakpoints });
const [isVisible, setIsVisible] = useState(true);
Expand Down Expand Up @@ -298,6 +301,7 @@ const Alert = ({

return (
<BaseBox
ref={ref as never}
{...a11yProps}
{...metaAttribute({ name: MetaConstants.Alert, testID })}
{...getStyledProps(styledProps)}
Expand Down Expand Up @@ -326,5 +330,7 @@ const Alert = ({
);
};

const Alert = forwardRef(_Alert);

export type { AlertProps };
export { Alert };
36 changes: 20 additions & 16 deletions packages/blade/src/components/Amount/Amount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { AmountTypeProps } from './amountTokens';
import { normalAmountSizes, subtleFontSizes, amountLineHeights } from './amountTokens';
import type { BaseTextProps } from '~components/Typography/BaseText/types';
import BaseBox from '~components/Box/BaseBox';
import type { TestID } from '~utils/types';
import type { BladeElementRef, TestID } from '~utils/types';
import { getPlatformType } from '~utils';
import { metaAttribute, MetaConstants } from '~utils/metaAttribute';
import { getStyledProps } from '~components/Box/styledProps';
Expand Down Expand Up @@ -254,20 +254,23 @@ export const getAmountByParts = ({
}
};

const _Amount = ({
value,
suffix = 'decimals',
type = 'body',
size = 'medium',
weight = 'regular',
isAffixSubtle = true,
isStrikethrough = false,
color,
currencyIndicator = 'currency-symbol',
currency = 'INR',
testID,
...styledProps
}: AmountProps): ReactElement => {
const _Amount = (
{
value,
suffix = 'decimals',
type = 'body',
size = 'medium',
weight = 'regular',
isAffixSubtle = true,
isStrikethrough = false,
color,
currencyIndicator = 'currency-symbol',
currency = 'INR',
testID,
...styledProps
}: AmountProps,
ref: React.Ref<BladeElementRef>,
): ReactElement => {
if (__DEV__) {
if (typeof value !== 'number') {
throwBladeError({
Expand Down Expand Up @@ -326,6 +329,7 @@ const _Amount = ({

return (
<BaseBox
ref={ref as never}
display={(isReactNative ? 'flex' : 'inline-flex') as never}
flexDirection="row"
{...metaAttribute({ name: MetaConstants.Amount, testID })}
Expand Down Expand Up @@ -402,7 +406,7 @@ const _Amount = ({
);
};

const Amount = assignWithoutSideEffects(_Amount, {
const Amount = assignWithoutSideEffects(React.forwardRef(_Amount), {
displayName: 'Amount',
componentId: 'Amount',
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Text } from '~components/Typography';
import { throwBladeError } from '~utils/logger';
import type { AnimateInteractionsProps } from './types';

const AnimateInteractions = (_props: AnimateInteractionsProps): React.ReactElement => {
throwBladeError({
message: 'AnimateInteractions is not yet implemented for native',
moduleName: 'AnimateInteractions',
});

return <Text>AnimateInteractions Component is not available for Native mobile apps.</Text>;
};

export { AnimateInteractions };
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React from 'react';
import type { Meta, StoryFn } from '@storybook/react';
import { Title } from '@storybook/addon-docs';
import { AnimateInteractions } from './';
import type { AnimateInteractionsProps } from './';
import { Sandbox } from '~utils/storybook/Sandbox';
import StoryPageWrapper from '~utils/storybook/StoryPageWrapper';
import { Button } from '~components/Button';
import { Box } from '~components/Box';
import { Fade } from '~components/Fade';
import { Move } from '~components/Move';
import { Text } from '~components/Typography';
import { Card, CardBody } from '~components/Card';

const Page = (): React.ReactElement => {
return (
<StoryPageWrapper
componentName="AnimateInteractions"
componentDescription="AnimateInteractions Motion Component (TODO)"
figmaURL="https://www.figma.com/proto/jubmQL9Z8V7881ayUD95ps/Blade-DSL?type=design&node-id=74864-85897&t=CvaYT53LNc4OYVKa-1&scaling=min-zoom&page-id=21689%3A381614&mode=design"
>
<Title>Usage</Title>
<Sandbox>
{`
const todo = 'todo';
`}
</Sandbox>
</StoryPageWrapper>
);
};

export default {
title: 'Motion/AnimateInteractions',
component: AnimateInteractions,
tags: ['autodocs'],
parameters: {
docs: {
page: Page,
},
},
} as Meta<AnimateInteractionsProps>;

const AnimateInteractionsTemplate: StoryFn<typeof AnimateInteractions> = (args) => {
return (
<Box minHeight="350px">
<AnimateInteractions motionTriggers={args.motionTriggers}>
{args.children}
</AnimateInteractions>
</Box>
);
};

export const Default = AnimateInteractionsTemplate.bind({});
Default.args = {
children: (
<Box display="flex" flexDirection="row" gap="spacing.4">
<Text>Hover me for magic</Text>
<Fade motionTriggers={['on-animate-interactions']}>
<b>I appear depending on interaction on parent container</b>
</Fade>
</Box>
),
};

export const ScaleChildOnCardHover = AnimateInteractionsTemplate.bind({});
ScaleChildOnCardHover.args = {
children: (
<Card
padding="spacing.0"
width="max-content"
backgroundColor="surface.background.gray.moderate"
>
<CardBody>
<Box position="relative" overflow="hidden" paddingBottom="50px">
<Move motionTriggers={['mount']}>
<Box padding="spacing.4">
<Text>Hi I am text inside card. Hover over this card to see magic</Text>
</Box>
</Move>
<Box position="absolute" left="spacing.0" bottom="spacing.0" width="100%">
<Move motionTriggers={['on-animate-interactions']}>
<Box
display="flex"
gap="spacing.4"
justifyContent="flex-end"
padding={['spacing.4', 'spacing.6']}
elevation="highRaised"
>
<Button variant="secondary">Cancel</Button>
<Button>Submit</Button>
</Box>
</Move>
</Box>
</Box>
</CardBody>
</Card>
),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { BaseMotionEnhancerBox } from '~components/BaseMotion';
import { AnimateInteractionsContext } from './AnimateInteractionsProvider';
import { useFocusWithin } from './useFocusWithin';
import React from 'react';
import { useAnimation } from 'framer-motion';
import type { AnimateInteractionsProps } from './types';

export const AnimateInteractions = ({
children,
motionTriggers = ['hover'],
}: AnimateInteractionsProps) => {
const baseMotionRef = React.useRef<HTMLDivElement | null>(null);
const controls = useAnimation();

useFocusWithin(baseMotionRef, {
onFocusWithin: () => {
controls.start('animate');
},
onBlurWithin: () => {
controls.start('exit');
},
});

return (
<AnimateInteractionsContext.Provider value={{ isInsideAnimateInteractionsContainer: true }}>
<BaseMotionEnhancerBox ref={baseMotionRef} motionTriggers={motionTriggers} animate={controls}>
{children}
</BaseMotionEnhancerBox>
</AnimateInteractionsContext.Provider>
);
};
Loading

0 comments on commit 12e1c2f

Please sign in to comment.